diff --git a/libstats/include/stats_event.h b/libstats/include/stats_event.h index 504d0890f..2811f52f4 100644 --- a/libstats/include/stats_event.h +++ b/libstats/include/stats_event.h @@ -53,11 +53,13 @@ struct stats_event; #define ERROR_NO_ATOM_ID 0x2 #define ERROR_OVERFLOW 0x4 #define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 -#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x10 -#define ERROR_INVALID_ANNOTATION_ID 0x20 -#define ERROR_ANNOTATION_ID_TOO_LARGE 0x40 -#define ERROR_TOO_MANY_ANNOTATIONS 0x80 -#define ERROR_TOO_MANY_FIELDS 0x100 +#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 +#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 +#define ERROR_INVALID_ANNOTATION_ID 0x40 +#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 +#define ERROR_TOO_MANY_ANNOTATIONS 0x100 +#define ERROR_TOO_MANY_FIELDS 0x200 +#define ERROR_INVALID_VALUE_TYPE 0x400 /* TYPE IDS */ #define INT32_TYPE 0x00 @@ -88,6 +90,30 @@ void stats_event_write_string8(struct stats_event* event, char* buf, uint32_t nu void stats_event_write_attribution_chain(struct stats_event* event, uint32_t* uids, char** tags, uint32_t* tagLengths, uint32_t numNodes); +/* key_value_pair struct can be constructed as follows: + * struct key_value_pair pair; + * pair.key = key; + * pair.typeId = STRING_TYPE; + * pair.stringValue = buf; + * pair.stringBytes = strlen(buf); + */ +struct key_value_pair { + int32_t key; + uint8_t valueType; // expected to be INT32_TYPE, INT64_TYPE, FLOAT_TYPE, or STRING_TYPE + union { + int32_t int32Value; + int64_t int64Value; + float floatValue; + struct { + char* stringValue; + uint32_t stringBytes; + }; + }; +}; + +void stats_event_add_key_value_pairs(struct stats_event* event, struct key_value_pair* pairs, + uint32_t numPairs); + void stats_event_add_bool_annotation(struct stats_event* event, uint32_t annotationId, bool value); void stats_event_add_int32_annotation(struct stats_event* event, uint32_t annotationId, int32_t value); diff --git a/libstats/stats_event.c b/libstats/stats_event.c index 58558b3ba..5e41d72a6 100644 --- a/libstats/stats_event.c +++ b/libstats/stats_event.c @@ -36,9 +36,7 @@ /* LIMITS */ #define MAX_ANNOTATION_COUNT 15 -#define MAX_ANNOTATION_ID 127 -#define MAX_ATTRIBUTION_NODES 127 -#define MAX_NUM_ELEMENTS 127 +#define MAX_BYTE_VALUE 127 // parsing side requires that lengths fit in 7 bits // The stats_event struct holds the serialized encoding of an event // within a buf. Also includes other required fields. @@ -46,6 +44,7 @@ struct stats_event { byte buf[MAX_EVENT_PAYLOAD]; size_t bufPos; // current write position within the buf size_t lastFieldPos; // location of last field within the buf + byte lastFieldType; // type of last field size_t size; // number of valid bytes within buffer uint32_t numElements; uint32_t atomId; @@ -69,6 +68,7 @@ struct stats_event* stats_event_obtain() { event->bufPos = POS_FIRST_FIELD; event->lastFieldPos = 0; + event->lastFieldType = OBJECT_TYPE; event->size = 0; event->numElements = 0; event->atomId = 0; @@ -144,81 +144,69 @@ static size_t put_byte_array(struct stats_event* event, void* buf, size_t size) return 0; } +static void start_field(struct stats_event* event, byte typeId) { + event->lastFieldPos = event->bufPos; + event->lastFieldType = typeId; + event->bufPos += put_byte(event, typeId); + event->numElements++; +} + void stats_event_write_int32(struct stats_event* event, int32_t value) { if (!event || event->errors) return; - event->lastFieldPos = event->bufPos; - event->bufPos += put_byte(event, INT32_TYPE); + start_field(event, INT32_TYPE); event->bufPos += put_int32(event, value); - event->numElements++; } void stats_event_write_int64(struct stats_event* event, int64_t value) { if (!event || event->errors) return; - event->lastFieldPos = event->bufPos; - event->bufPos += put_byte(event, INT64_TYPE); + start_field(event, INT64_TYPE); event->bufPos += put_int64(event, value); - event->numElements++; } void stats_event_write_float(struct stats_event* event, float value) { if (!event || event->errors) return; - event->lastFieldPos = event->bufPos; - event->bufPos += put_byte(event, FLOAT_TYPE); + start_field(event, FLOAT_TYPE); event->bufPos += put_float(event, value); - event->numElements++; } void stats_event_write_bool(struct stats_event* event, bool value) { if (!event || event->errors) return; - event->lastFieldPos = event->bufPos; - event->bufPos += put_byte(event, BOOL_TYPE); + start_field(event, BOOL_TYPE); event->bufPos += put_bool(event, value); - event->numElements++; } // Buf is assumed to be encoded using UTF8 void stats_event_write_byte_array(struct stats_event* event, uint8_t* buf, uint32_t numBytes) { if (!event || !buf || event->errors) return; - event->lastFieldPos = event->bufPos; - event->bufPos += put_byte(event, BYTE_ARRAY_TYPE); + start_field(event, BYTE_ARRAY_TYPE); event->bufPos += put_int32(event, numBytes); event->bufPos += put_byte_array(event, buf, numBytes); - event->numElements++; } // Buf is assumed to be encoded using UTF8 void stats_event_write_string8(struct stats_event* event, char* buf, uint32_t numBytes) { if (!event || !buf || event->errors) return; - event->lastFieldPos = event->bufPos; - event->bufPos += put_byte(event, STRING_TYPE); + start_field(event, STRING_TYPE); event->bufPos += put_int32(event, numBytes); event->bufPos += put_byte_array(event, buf, numBytes); - event->numElements++; -} - -// Side-effect: modifies event->errors if the attribution chain is too long -static bool is_attribution_chain_too_long(struct stats_event* event, uint32_t numNodes) { - if (numNodes > MAX_ATTRIBUTION_NODES) { - event->errors |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG; - return true; - } - return false; } // Tags are assumed to be encoded using UTF8 void stats_event_write_attribution_chain(struct stats_event* event, uint32_t* uids, char** tags, uint32_t* tagLengths, uint32_t numNodes) { if (!event || event->errors) return; - if (is_attribution_chain_too_long(event, numNodes)) return; + if (numNodes > MAX_BYTE_VALUE) { + event->errors |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG; + return; + } - event->lastFieldPos = event->bufPos; - event->bufPos += put_byte(event, ATTRIBUTION_CHAIN_TYPE); + start_field(event, ATTRIBUTION_CHAIN_TYPE); event->bufPos += put_byte(event, (byte)numNodes); for (int i = 0; i < numNodes; i++) { @@ -226,7 +214,41 @@ void stats_event_write_attribution_chain(struct stats_event* event, uint32_t* ui event->bufPos += put_int32(event, tagLengths[i]); event->bufPos += put_byte_array(event, tags[i], tagLengths[i]); } - event->numElements++; +} + +void stats_event_add_key_value_pairs(struct stats_event* event, struct key_value_pair* pairs, + uint32_t numPairs) { + if (!event || event->errors) return; + if (numPairs > MAX_BYTE_VALUE) { + event->errors |= ERROR_TOO_MANY_KEY_VALUE_PAIRS; + return; + } + + start_field(event, KEY_VALUE_PAIRS_TYPE); + event->bufPos += put_byte(event, (byte)numPairs); + + for (int i = 0; i < numPairs; i++) { + event->bufPos += put_int32(event, pairs[i].key); + event->bufPos += put_byte(event, pairs[i].valueType); + switch (pairs[i].valueType) { + case INT32_TYPE: + event->bufPos += put_int32(event, pairs[i].int32Value); + break; + case INT64_TYPE: + event->bufPos += put_int64(event, pairs[i].int64Value); + break; + case FLOAT_TYPE: + event->bufPos += put_float(event, pairs[i].floatValue); + break; + case STRING_TYPE: + event->bufPos += put_int32(event, pairs[i].stringBytes); + event->bufPos += put_byte_array(event, pairs[i].stringValue, pairs[i].stringBytes); + break; + default: + event->errors |= ERROR_INVALID_VALUE_TYPE; + return; + } + } } // Side-effect: modifies event->errors if annotation does not follow field @@ -240,7 +262,7 @@ static bool does_annotation_follow_field(struct stats_event* event) { // Side-effect: modifies event->errors if annotation id is too large static bool is_valid_annotation_id(struct stats_event* event, uint32_t annotationId) { - if (annotationId > MAX_ANNOTATION_ID) { + if (annotationId > MAX_BYTE_VALUE) { event->errors |= ERROR_ANNOTATION_ID_TOO_LARGE; return false; } @@ -250,15 +272,15 @@ static bool is_valid_annotation_id(struct stats_event* event, uint32_t annotatio // Side-effect: modifies event->errors if field has too many annotations static void increment_annotation_count(struct stats_event* event) { byte fieldType = event->buf[event->lastFieldPos] & 0x0F; - byte oldAnnotationCount = event->buf[event->lastFieldPos] & 0xF0; - byte newAnnotationCount = oldAnnotationCount + 1; + uint32_t oldAnnotationCount = event->buf[event->lastFieldPos] & 0xF0; + uint32_t newAnnotationCount = oldAnnotationCount + 1; if (newAnnotationCount > MAX_ANNOTATION_COUNT) { event->errors |= ERROR_TOO_MANY_ANNOTATIONS; return; } - event->buf[event->lastFieldPos] = ((newAnnotationCount << 4) & 0xF0) | fieldType; + event->buf[event->lastFieldPos] = (((byte)newAnnotationCount << 4) & 0xF0) | fieldType; } void stats_event_add_bool_annotation(struct stats_event* event, uint32_t annotationId, bool value) { @@ -292,13 +314,6 @@ static void build(struct stats_event* event) { // store size before we modify bufPos event->size = event->bufPos; - if (event->numElements > MAX_NUM_ELEMENTS) { - event->errors |= ERROR_TOO_MANY_FIELDS; - } else { - event->bufPos = POS_NUM_ELEMENTS; - put_byte(event, (byte)event->numElements); - } - if (event->timestampNs == 0) { event->errors |= ERROR_NO_TIMESTAMP; } else { @@ -308,6 +323,7 @@ static void build(struct stats_event* event) { event->bufPos = POS_TIMESTAMP; event->bufPos += put_byte(event, INT64_TYPE); event->bufPos += put_int64(event, event->timestampNs); + event->numElements++; } if (event->atomId == 0) { @@ -316,6 +332,14 @@ static void build(struct stats_event* event) { event->bufPos = POS_ATOM_ID; event->bufPos += put_byte(event, INT32_TYPE); event->bufPos += put_int64(event, event->atomId); + event->numElements++; + } + + if (event->numElements > MAX_BYTE_VALUE) { + event->errors |= ERROR_TOO_MANY_FIELDS; + } else { + event->bufPos = POS_NUM_ELEMENTS; + put_byte(event, (byte)event->numElements); } // If there are errors, rewrite buffer