diff --git a/sys/cbor/cbor.c b/sys/cbor/cbor.c index fd7b48969..bbb7c65e7 100644 --- a/sys/cbor/cbor.c +++ b/sys/cbor/cbor.c @@ -78,6 +78,10 @@ if (stream->pos + bytes >= stream->size) { return 0; } \ } while(0) +#define CBOR_ENSURE_SIZE_READ(stream, bytes) do { \ + if (bytes > stream->size) { return 0; } \ +} while(0) + /* Extra defines not related to the protocol itself */ #define CBOR_STREAM_PRINT_BUFFERSIZE 1024 /* bytes */ @@ -91,7 +95,20 @@ #define NAN (0.0/0.0) #endif + +/* pack to force aligned access on ARMv7 (buggy GCC) */ +#pragma GCC diagnostic error "-Wcast-align" +typedef struct __attribute__((packed)) { + unsigned char u8; + union { + uint16_t u16; + uint32_t u32; + uint64_t u64; + } u; +} cast_align_u8_t; + #ifndef CBOR_NO_FLOAT + /** * Convert float @p x to network format */ @@ -323,10 +340,14 @@ static size_t decode_int(const cbor_stream_t *s, size_t offset, uint64_t *val) *val = 0; /* clear val first */ + CBOR_ENSURE_SIZE_READ(s, offset + 1); + unsigned char *in = &s->data[offset]; unsigned char additional_info = CBOR_ADDITIONAL_INFO(s, offset); unsigned char bytes_follow = uint_bytes_follow(additional_info); + CBOR_ENSURE_SIZE_READ(s, offset + 1 + bytes_follow); + switch (bytes_follow) { case 0: *val = (in[0] & CBOR_INFO_MASK); @@ -337,15 +358,15 @@ static size_t decode_int(const cbor_stream_t *s, size_t offset, uint64_t *val) break; case 2: - *val = HTONS(*((uint16_t *)&in[1])); + *val = HTONS(((cast_align_u8_t *)in)->u.u16); break; case 4: - *val = HTONL(*((uint32_t *)&in[1])); + *val = HTONL(((cast_align_u8_t *)in)->u.u32); break; default: - *val = HTONLL(*((uint64_t *)&in[1])); + *val = HTONLL(((cast_align_u8_t *)in)->u.u64); break; } @@ -371,6 +392,8 @@ static size_t encode_bytes(unsigned char major_type, cbor_stream_t *s, const cha static size_t decode_bytes(const cbor_stream_t *s, size_t offset, char *out, size_t length) { + CBOR_ENSURE_SIZE_READ(s, offset + 1); + if ((CBOR_TYPE(s, offset) != CBOR_BYTES && CBOR_TYPE(s, offset) != CBOR_TEXT) || !out) { return 0; } @@ -386,13 +409,43 @@ static size_t decode_bytes(const cbor_stream_t *s, size_t offset, char *out, siz return 0; } + CBOR_ENSURE_SIZE_READ(s, offset + bytes_start + bytes_length); + memcpy(out, &s->data[offset + bytes_start], bytes_length); out[bytes_length] = '\0'; return (bytes_start + bytes_length); } +/* A zero copy version of decode_bytes. + Will not null termiante input, but will tell you the size of what you read. + Great for reading byte strings which could contain nulls inside of unknown size + without forced copies. +*/ +static size_t decode_bytes_no_copy(const cbor_stream_t *s, size_t offset, unsigned char **out, size_t *length) +{ + CBOR_ENSURE_SIZE_READ(s, offset + 1); + + if ((CBOR_TYPE(s, offset) != CBOR_BYTES && CBOR_TYPE(s, offset) != CBOR_TEXT) || !out) { + return 0; + } + + uint64_t bytes_length; + size_t bytes_start = decode_int(s, offset, &bytes_length); + + if (!bytes_start) { + return 0; + } + + CBOR_ENSURE_SIZE_READ(s, offset + bytes_start + bytes_length); + *out = &(s->data[offset + bytes_start]); + *length = bytes_length; + return (bytes_start + bytes_length); +} + size_t cbor_deserialize_int(const cbor_stream_t *stream, size_t offset, int *val) { + CBOR_ENSURE_SIZE_READ(stream, offset + 1); + if ((CBOR_TYPE(stream, offset) != CBOR_UINT && CBOR_TYPE(stream, offset) != CBOR_NEGINT) || !val) { return 0; } @@ -400,6 +453,10 @@ size_t cbor_deserialize_int(const cbor_stream_t *stream, size_t offset, int *val uint64_t buf; size_t read_bytes = decode_int(stream, offset, &buf); + if (!read_bytes) { + return 0; + } + if (CBOR_TYPE(stream, offset) == CBOR_UINT) { *val = buf; /* resolve as CBOR_UINT */ } @@ -521,7 +578,7 @@ size_t cbor_deserialize_float(const cbor_stream_t *stream, size_t offset, float unsigned char *data = &stream->data[offset]; if (*data == CBOR_FLOAT32) { - *val = ntohf(*(uint32_t *)(data + 1)); + *val = ntohf(((cast_align_u8_t *)data)->u.u32); return 5; } @@ -540,6 +597,8 @@ size_t cbor_serialize_float(cbor_stream_t *s, float val) size_t cbor_deserialize_double(const cbor_stream_t *stream, size_t offset, double *val) { + CBOR_ENSURE_SIZE_READ(stream, offset + 1); + if (CBOR_TYPE(stream, offset) != CBOR_7 || !val) { return 0; } @@ -547,7 +606,8 @@ size_t cbor_deserialize_double(const cbor_stream_t *stream, size_t offset, doubl unsigned char *data = &stream->data[offset]; if (*data == CBOR_FLOAT64) { - *val = ntohd(*(uint64_t *)(data + 1)); + CBOR_ENSURE_SIZE_READ(stream, offset + 9); + *val = ntohd(((cast_align_u8_t *)data)->u.u64); return 9; } @@ -568,6 +628,8 @@ size_t cbor_serialize_double(cbor_stream_t *s, double val) size_t cbor_deserialize_byte_string(const cbor_stream_t *stream, size_t offset, char *val, size_t length) { + CBOR_ENSURE_SIZE_READ(stream, offset + 1); + if (CBOR_TYPE(stream, offset) != CBOR_BYTES) { return 0; } @@ -575,6 +637,18 @@ size_t cbor_deserialize_byte_string(const cbor_stream_t *stream, size_t offset, return decode_bytes(stream, offset, val, length); } +size_t cbor_deserialize_byte_string_no_copy(const cbor_stream_t *stream, size_t offset, unsigned char **val, + size_t *length) +{ + CBOR_ENSURE_SIZE_READ(stream, offset + 1); + + if (CBOR_TYPE(stream, offset) != CBOR_BYTES) { + return 0; + } + + return decode_bytes_no_copy(stream, offset, val, length); +} + size_t cbor_serialize_byte_string(cbor_stream_t *stream, const char *val) { return encode_bytes(CBOR_BYTES, stream, val, strlen(val)); @@ -588,6 +662,8 @@ size_t cbor_serialize_byte_stringl(cbor_stream_t *stream, const char *val, size_ size_t cbor_deserialize_unicode_string(const cbor_stream_t *stream, size_t offset, char *val, size_t length) { + CBOR_ENSURE_SIZE_READ(stream, offset + 1); + if (CBOR_TYPE(stream, offset) != CBOR_TEXT) { return 0; } @@ -595,6 +671,18 @@ size_t cbor_deserialize_unicode_string(const cbor_stream_t *stream, size_t offse return decode_bytes(stream, offset, val, length); } +size_t cbor_deserialize_unicode_string_no_copy(const cbor_stream_t *stream, size_t offset, unsigned char **val, + size_t *length) +{ + CBOR_ENSURE_SIZE_READ(stream, offset + 1); + + if (CBOR_TYPE(stream, offset) != CBOR_TEXT) { + return 0; + } + + return decode_bytes_no_copy(stream, offset, val, length); +} + size_t cbor_serialize_unicode_string(cbor_stream_t *stream, const char *val) { return encode_bytes(CBOR_TEXT, stream, val, strlen(val)); diff --git a/sys/include/cbor.h b/sys/include/cbor.h index 9b59dbf97..2b051656f 100644 --- a/sys/include/cbor.h +++ b/sys/include/cbor.h @@ -391,6 +391,22 @@ size_t cbor_deserialize_byte_string(const cbor_stream_t *stream, size_t offset, size_t cbor_serialize_unicode_string(cbor_stream_t *stream, const char *val); +/** + * @brief Deserialize bytes/unicode from @p stream to @p val (without copy) + * + * @param[in] stream The stream to deserialize + * @param[in] offset The offset within the stream where to start deserializing + * @param[out] val Pointer to a char * + * @param[out] length Pointer to a size_t to store the size of the string + * + * @return Number of bytes written into @p val + */ +size_t cbor_deserialize_byte_string_no_copy(const cbor_stream_t *stream, size_t offset, + unsigned char **val, size_t *length); + +size_t cbor_deserialize_unicode_string_no_copy(const cbor_stream_t *stream, size_t offset, + unsigned char **val, size_t *length); + /** * @brief Deserialize unicode string from @p stream to @p val * diff --git a/tests/unittests/tests-cbor/tests-cbor.c b/tests/unittests/tests-cbor/tests-cbor.c index f98cb8cf5..76e2a7f74 100644 --- a/tests/unittests/tests-cbor/tests-cbor.c +++ b/tests/unittests/tests-cbor/tests-cbor.c @@ -279,6 +279,39 @@ static void test_byte_string(void) } } +static void test_byte_string_no_copy(void) +{ + char buffer[128]; + + { + const char *input = ""; + unsigned char data[] = {0x40}; + unsigned char *out; + size_t size; + TEST_ASSERT(cbor_serialize_byte_string(&stream, input)); + CBOR_CHECK_SERIALIZED(stream, data, sizeof(data)); + TEST_ASSERT(cbor_deserialize_byte_string_no_copy(&stream, 0, &out, &size)); + memcpy(buffer, out, size); + buffer[size] = '\0'; + CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING); + } + + cbor_clear(&stream); + + { + const char *input = "a"; + unsigned char data[] = {0x41, 0x61}; + unsigned char *out; + size_t size; + TEST_ASSERT(cbor_serialize_byte_string(&stream, input)); + CBOR_CHECK_SERIALIZED(stream, data, sizeof(data)); + TEST_ASSERT(cbor_deserialize_byte_string_no_copy(&stream, 0, &out, &size)); + memcpy(buffer, out, size); + buffer[size] = '\0'; + CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING); + } +} + static void test_byte_string_invalid(void) { { @@ -318,6 +351,39 @@ static void test_unicode_string(void) } } +static void test_unicode_string_no_copy(void) +{ + char buffer[128]; + + { + const char *input = ""; + unsigned char data[] = {0x60}; + unsigned char *out; + size_t size; + TEST_ASSERT(cbor_serialize_unicode_string(&stream, input)); + CBOR_CHECK_SERIALIZED(stream, data, sizeof(data)); + TEST_ASSERT(cbor_deserialize_unicode_string_no_copy(&stream, 0, &out, &size)); + memcpy(buffer, out, size); + buffer[size] = '\0'; + CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING); + } + + cbor_clear(&stream); + + { + const char *input = "a"; + unsigned char data[] = {0x61, 0x61}; + unsigned char *out; + size_t size; + TEST_ASSERT(cbor_serialize_unicode_string(&stream, input)); + CBOR_CHECK_SERIALIZED(stream, data, sizeof(data)); + TEST_ASSERT(cbor_deserialize_unicode_string_no_copy(&stream, 0, &out, &size)); + memcpy(buffer, out, size); + buffer[size] = '\0'; + CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING); + } +} + static void test_unicode_string_invalid(void) { { @@ -761,8 +827,10 @@ TestRef tests_cbor_all(void) new_TestFixture(test_int64_t), new_TestFixture(test_int64_t_invalid), new_TestFixture(test_byte_string), + new_TestFixture(test_byte_string_no_copy), new_TestFixture(test_byte_string_invalid), new_TestFixture(test_unicode_string), + new_TestFixture(test_unicode_string_no_copy), new_TestFixture(test_unicode_string_invalid), new_TestFixture(test_array), new_TestFixture(test_array_indefinite),