Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate data messages #25

Merged
merged 1 commit into from
Jul 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"msgType": 0,

"recency": 1,
"sgvCount": 2,
"__DEPRECATED__sgvCount": 2,
"sgvs": 3,
"lastSgv": 4,
"trend": 5,
Expand Down
2 changes: 1 addition & 1 deletion src/app_keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ enum {
enum {
__APP_KEYS_FOR_DATA,
APP_KEY_RECENCY,
APP_KEY_SGV_COUNT,
__DEPRECATED__APP_KEY_SGV_COUNT,
APP_KEY_SGVS,
APP_KEY_LAST_SGV,
APP_KEY_TREND,
Expand Down
127 changes: 127 additions & 0 deletions src/app_messages.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "app_keys.h"
#include "app_messages.h"

static const char* type_name(TupleType type) {
switch(type) {
case TUPLE_BYTE_ARRAY: return "byte array";
case TUPLE_CSTRING: return "cstring";
case TUPLE_UINT: return "uint";
case TUPLE_INT: return "int";
default: return "";
}
}

static bool fail_unexpected_type(uint8_t key, TupleType type, const char* expected) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Expected key %d to have type %s, but has type %s", (int)key, expected, type_name(type));
return false;
}

static bool fail_missing_required_value(uint8_t key) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Missing required value for key %d", (int)key);
return false;
}

static bool pass_default_value(uint8_t key) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Missing value for key %d, assigning default", (int)key);
return true;
}

bool get_int32(DictionaryIterator *data, int32_t *dest, uint8_t key, bool required, int32_t fallback) {
Tuple *t = dict_find(data, key);
if (t != NULL) {
if (t->type == TUPLE_INT) {
switch(t->length) {
case 1: *dest = t->value->int8; break;
case 2: *dest = t->value->int16; break;
default: *dest = t->value->int32; break;
}
return true;
} else if (t->type == TUPLE_UINT) {
switch(t->length) {
case 1: *dest = t->value->uint8; break;
case 2: *dest = t->value->uint16; break;
default: *dest = t->value->uint32; break;
}
return true;
} else {
return fail_unexpected_type(key, t->type, "int or uint");
}
} else {
if (required) {
return fail_missing_required_value(key);
} else {
*dest = fallback;
return pass_default_value(key);
}
}
}

bool get_byte_array(DictionaryIterator *data, uint8_t *dest, uint8_t key, size_t max_length, bool required, uint8_t *fallback) {
Tuple *t = dict_find(data, key);
if (t != NULL) {
if (t->type == TUPLE_BYTE_ARRAY) {
memcpy(dest, t->value->data, (t->length < max_length ? t->length : max_length) * sizeof(uint8_t));
return true;
} else {
return fail_unexpected_type(key, t->type, "byte array");
}
} else {
if (required) {
return fail_missing_required_value(key);
} else {
memcpy(dest, fallback, ARRAY_LENGTH(fallback));
return pass_default_value(key);
}
}
}

bool get_byte_array_length(DictionaryIterator *data, uint16_t *dest, uint16_t max_length, uint8_t key) {
// assumes get_byte_array has already succeeded for this key
uint16_t length = dict_find(data, key)->length;
if (max_length == 0) {
*dest = length;
} else {
*dest = length > max_length ? max_length : length;
}
return true;
}

bool get_cstring(DictionaryIterator *data, char *dest, uint8_t key, size_t max_length, bool required, const char* fallback) {
Tuple *t = dict_find(data, key);
if (t != NULL) {
if (t->type == TUPLE_CSTRING) {
strncpy(dest, t->value->cstring, max_length);
return true;
} else {
return fail_unexpected_type(key, t->type, "cstring");
}
} else {
if (required) {
return fail_missing_required_value(key);
} else {
strcpy(dest, fallback);
return pass_default_value(key);
}
}
}

bool validate_data_message(DictionaryIterator *data, DataMessage *out) {
/*
* Validation is not necessary for messages from the PebbleKit JS half of
* Urchin since it is distributed with the C SDK half, but other clients
* (Pancreabble, Loop, xDrip(?)) are not guaranteed to be using exactly the
* same message format as this version of Urchin.
*/
static uint8_t zeroes[GRAPH_MAX_SGV_COUNT];
memset(zeroes, 0, GRAPH_MAX_SGV_COUNT);

return true
&& get_int32(data, &out->recency, APP_KEY_RECENCY, false, 0)
&& get_byte_array(data, out->sgvs, APP_KEY_SGVS, GRAPH_MAX_SGV_COUNT, true, NULL)
&& get_byte_array_length(data, &out->sgv_count, GRAPH_MAX_SGV_COUNT, APP_KEY_SGVS)
&& get_int32(data, &out->last_sgv, APP_KEY_LAST_SGV, true, 0)
&& get_int32(data, &out->trend, APP_KEY_TREND, false, 0)
&& get_int32(data, &out->delta, APP_KEY_DELTA, false, NO_DELTA_VALUE)
&& get_cstring(data, out->status_text, APP_KEY_STATUS_TEXT, STATUS_BAR_MAX_LENGTH, false, "")
&& get_byte_array(data, out->graph_extra, APP_KEY_GRAPH_EXTRA, GRAPH_MAX_SGV_COUNT, false, zeroes);
}
21 changes: 21 additions & 0 deletions src/app_messages.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include <pebble.h>
#include "config.h"

typedef struct __attribute__((__packed__)) DataMessage {
int32_t recency;
uint16_t sgv_count;
uint8_t sgvs[GRAPH_MAX_SGV_COUNT];
int32_t last_sgv;
int32_t trend;
int32_t delta;
char status_text[STATUS_BAR_MAX_LENGTH];
uint8_t graph_extra[GRAPH_MAX_SGV_COUNT];
} DataMessage;

bool get_int32(DictionaryIterator *data, int32_t *dest, uint8_t key, bool required, int32_t fallback);
bool get_byte_array(DictionaryIterator *data, uint8_t *dest, uint8_t key, size_t max_length, bool required, uint8_t *fallback);
bool get_byte_array_length(DictionaryIterator *data, uint16_t *dest, uint16_t max_length, uint8_t key);
bool get_cstring(DictionaryIterator *data, char *dest, uint8_t key, size_t max_length, bool required, const char* fallback);
bool validate_data_message(DictionaryIterator *data, DataMessage *out);
2 changes: 1 addition & 1 deletion src/bg_row_element.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ void bg_row_element_destroy(BGRowElement *el) {
free(el);
}

void bg_row_element_update(BGRowElement *el, DictionaryIterator *data) {
void bg_row_element_update(BGRowElement *el, DataMessage *data) {
last_bg_text_layer_update(el->bg_text, data);
text_layer_set_font(
el->bg_text,
Expand Down
3 changes: 2 additions & 1 deletion src/bg_row_element.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <pebble.h>
#include "app_messages.h"
#include "trend_arrow_component.h"

typedef struct BGRowElement {
Expand All @@ -12,5 +13,5 @@ typedef struct BGRowElement {

BGRowElement* bg_row_element_create(Layer *parent);
void bg_row_element_destroy(BGRowElement *el);
void bg_row_element_update(BGRowElement *el, DictionaryIterator *data);
void bg_row_element_update(BGRowElement *el, DataMessage *data);
void bg_row_element_tick(BGRowElement *el);
57 changes: 41 additions & 16 deletions src/comm.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ static bool update_in_progress;
static AppTimer *request_timer = NULL;
static AppTimer *timeout_timer = NULL;

static void (*data_callback)(DictionaryIterator *received);
static void (*data_callback)(DataMessage *data);
static void (*prefs_callback)(DictionaryIterator *received);
static void schedule_update(uint32_t delay);
static void request_update();
static void timeout_handler();

static DataMessage *last_data_message;

static void clear_timer(AppTimer **timer) {
if (*timer != NULL) {
app_timer_cancel(*timer);
Expand Down Expand Up @@ -65,23 +68,37 @@ static void request_update() {
static void in_received_handler(DictionaryIterator *received, void *context) {
phone_contact = true;
update_in_progress = false;
staleness_update(received);
int msg_type = dict_find(received, APP_KEY_MSG_TYPE)->value->uint8;

time_t now = time(NULL);
staleness_update_message_received(now);

int32_t msg_type;
if (!get_int32(received, &msg_type, APP_KEY_MSG_TYPE, true, 0)) {
schedule_update(BAD_APP_MESSAGE_RETRY_DELAY);
return;
}

if (msg_type == MSG_TYPE_DATA) {
int32_t delay;
if (get_prefs()->update_every_minute) {
delay = 60 * 1000;
static DataMessage d;
if (validate_data_message(received, &d)) {
memcpy(last_data_message, &d, sizeof(DataMessage));
int32_t delay;
if (get_prefs()->update_every_minute) {
delay = 60 * 1000;
} else {
int32_t next_update = (SGV_UPDATE_FREQUENCY_SECONDS - last_data_message->recency) * 1000;
delay = next_update < 0 ? LATE_DATA_UPDATE_FREQUENCY : next_update;
}
schedule_update((uint32_t) delay);
staleness_update_data_received(now, last_data_message->recency);
data_callback(last_data_message);
} else {
uint32_t recency = dict_find(received, APP_KEY_RECENCY)->value->uint32;
int32_t next_update = (SGV_UPDATE_FREQUENCY_SECONDS - recency) * 1000;
delay = next_update < 0 ? LATE_DATA_UPDATE_FREQUENCY : next_update;
schedule_update(BAD_APP_MESSAGE_RETRY_DELAY);
}
schedule_update((uint32_t) delay);
}
if (msg_type == MSG_TYPE_ERROR) {
schedule_update(ERROR_RETRY_DELAY);
} else if (msg_type == MSG_TYPE_PREFERENCES) {
prefs_callback(received);
} else {
data_callback(received);
schedule_update(ERROR_RETRY_DELAY);
}
}

Expand All @@ -104,8 +121,9 @@ static void bluetooth_connection_handler(bool connected) {
}
}

void init_comm(void (*callback)(DictionaryIterator *received)) {
data_callback = callback;
void init_comm(void (*callback_for_data)(DataMessage *data), void (*callback_for_prefs)(DictionaryIterator *received)) {
data_callback = callback_for_data;
prefs_callback = callback_for_prefs;
app_message_register_inbox_received(in_received_handler);
app_message_register_inbox_dropped(in_dropped_handler);
app_message_register_outbox_failed(out_failed_handler);
Expand All @@ -117,10 +135,17 @@ void init_comm(void (*callback)(DictionaryIterator *received)) {
update_in_progress = true;
timeout_timer = app_timer_register(timeout_length(), timeout_handler, NULL);

last_data_message = malloc(sizeof(DataMessage));

app_message_open(inbound_size, outbound_size);

// Request data as soon as Bluetooth reconnects
connection_service_subscribe((ConnectionHandlers) {
.pebble_app_connection_handler = bluetooth_connection_handler
});
}

void deinit_comm() {
app_message_deregister_callbacks();
free(last_data_message);
}
5 changes: 4 additions & 1 deletion src/comm.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <pebble.h>
#include "app_messages.h"

// Size can be up to ~390 when status bar text is 255 bytes long (as of fa6984)
#define CONTENT_SIZE 512
Expand All @@ -13,5 +14,7 @@
#define IN_RETRY_DELAY 100
#define LATE_DATA_UPDATE_FREQUENCY (60*1000)
#define ERROR_RETRY_DELAY (60*1000)
#define BAD_APP_MESSAGE_RETRY_DELAY (60*1000)

void init_comm(void (*callback)(DictionaryIterator *received));
void init_comm(void (*callback_for_data)(DataMessage *data), void (*callback_for_prefs)(DictionaryIterator *received));
void deinit_comm();
1 change: 1 addition & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#define GRAPH_MAX_SGV_COUNT 48
#define GRAPH_POINT_SIZE 3
#define GRAPH_INTERVAL_SIZE_SECONDS (5*60)
#define STATUS_BAR_MAX_LENGTH 256

#define BOLUS_TICK_WIDTH 2
#define BOLUS_TICK_HEIGHT 7
Expand Down
20 changes: 5 additions & 15 deletions src/graph_element.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include "app_keys.h"
#include "config.h"
#include "graph_element.h"
#include "layout.h"
Expand Down Expand Up @@ -166,20 +165,11 @@ void graph_element_destroy(GraphElement *el) {
free(el);
}

void graph_element_update(GraphElement *el, DictionaryIterator *data) {
int count = dict_find(data, APP_KEY_SGV_COUNT)->value->int32;
count = count > GRAPH_MAX_SGV_COUNT ? GRAPH_MAX_SGV_COUNT : count;
((GraphData*)layer_get_data(el->graph_layer))->count = count;
memcpy(
((GraphData*)layer_get_data(el->graph_layer))->sgvs,
(uint8_t*)dict_find(data, APP_KEY_SGVS)->value->data,
count * sizeof(uint8_t)
);
memcpy(
((GraphData*)layer_get_data(el->graph_layer))->extra,
(uint8_t*)dict_find(data, APP_KEY_GRAPH_EXTRA)->value->data,
count * sizeof(uint8_t)
);
void graph_element_update(GraphElement *el, DataMessage *data) {
GraphData *graph_data = layer_get_data(el->graph_layer);
graph_data->count = data->sgv_count;
memcpy(graph_data->sgvs, data->sgvs, data->sgv_count * sizeof(uint8_t));
memcpy(graph_data->extra, data->graph_extra, data->sgv_count * sizeof(uint8_t));
layer_mark_dirty(el->graph_layer);
connection_status_component_refresh(el->conn_status);
}
Expand Down
5 changes: 3 additions & 2 deletions src/graph_element.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <pebble.h>
#include "app_messages.h"
#include "connection_status_component.h"

#define GRAPH_EXTRA_BOLUS_OFFSET 0
Expand All @@ -15,12 +16,12 @@ typedef struct GraphElement {

typedef struct GraphData {
GColor color;
int count;
uint16_t count;
uint8_t* sgvs;
uint8_t* extra;
} GraphData;

GraphElement* graph_element_create(Layer *parent);
void graph_element_destroy(GraphElement *el);
void graph_element_update(GraphElement *el, DictionaryIterator *data);
void graph_element_update(GraphElement *el, DataMessage *data);
void graph_element_tick(GraphElement *el);
1 change: 0 additions & 1 deletion src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ function main(c) {
sendMessage({
msgType: c.MSG_TYPE_DATA,
recency: format.recency(sgvs),
sgvCount: ys.length,
// XXX: divide BG by 2 to fit into 1 byte
sgvs: ys.map(function(y) { return Math.min(255, Math.floor(y / 2)); }),
lastSgv: format.lastSgv(sgvs),
Expand Down
Loading