diff options
-rw-r--r-- | pb.h | 4 | ||||
-rw-r--r-- | pb_decode.c | 143 | ||||
-rw-r--r-- | tests/alltypes_pointer/SConscript | 11 | ||||
-rw-r--r-- | tests/alltypes_pointer/decode_alltypes_pointer.c | 210 |
4 files changed, 362 insertions, 6 deletions
@@ -63,6 +63,10 @@ #include <stddef.h> #include <stdbool.h> #include <string.h> + +#ifdef PB_ENABLE_MALLOC +#include <stdlib.h> +#endif #endif /* Macro for defining packed structures (compiler dependent). diff --git a/pb_decode.c b/pb_decode.c index 30c124dd..4411e26a 100644 --- a/pb_decode.c +++ b/pb_decode.c @@ -465,6 +465,121 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t } } +#ifdef PB_ENABLE_MALLOC +/* Allocate storage for the field and store the pointer at iter->pData. + * array_size is the number of entries to reserve in an array. */ +static bool checkreturn allocate_field(pb_istream_t *stream, pb_field_iterator_t *iter, size_t array_size) +{ + void *ptr = *(void**)iter->pData; + size_t size = array_size * iter->pos->data_size; + + if (ptr == NULL) + { + /* First allocation */ + ptr = malloc(size); + if (ptr == NULL) + PB_RETURN_ERROR(stream, "malloc failed"); + } + else + { + /* Expand previous allocation */ + /* Note: on failure the old pointer will remain in the structure, + * the message must be freed by caller also on error return. */ + ptr = realloc(ptr, size); + if (ptr == NULL) + PB_RETURN_ERROR(stream, "realloc failed"); + } + + *(void**)iter->pData = ptr; + return true; +} +#endif + +static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iterator_t *iter) +{ +#ifndef PB_ENABLE_MALLOC + UNUSED(wire_type); + UNUSED(iter); + PB_RETURN_ERROR(stream, "no malloc support"); +#else + pb_type_t type; + pb_decoder_t func; + + type = iter->pos->type; + func = PB_DECODERS[PB_LTYPE(type)]; + + switch (PB_HTYPE(type)) + { + case PB_HTYPE_REQUIRED: + case PB_HTYPE_OPTIONAL: + if (!allocate_field(stream, iter, 1)) + return false; + return func(stream, iter->pos, iter->pData); + + case PB_HTYPE_REPEATED: + if (wire_type == PB_WT_STRING + && PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Packed array, multiple items come in at once. */ + bool status = true; + size_t *size = (size_t*)iter->pSize; + size_t allocated_size = *size; + void *pItem; + pb_istream_t substream; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left) + { + if (*size + 1 > allocated_size) + { + /* Allocate more storage. This tries to guess the + * number of remaining entries. */ + allocated_size += substream.bytes_left / iter->pos->data_size; + if (*size + 1 > allocated_size) + allocated_size++; /* Division gave zero. */ + + if (!allocate_field(&substream, iter, allocated_size)) + { + status = false; + break; + } + + /* Decode the array entry */ + pItem = (uint8_t*)iter->pData + iter->pos->data_size * (*size); + if (!func(&substream, iter->pos, pItem)) + { + status = false; + break; + } + (*size)++; + } + } + pb_close_string_substream(stream, &substream); + + return status; + } + else + { + /* Normal repeated field, i.e. only one item at a time. */ + size_t *size = (size_t*)iter->pSize; + void *pItem = (uint8_t*)iter->pData + iter->pos->data_size * (*size); + + if (!allocate_field(stream, iter, *size + 1)) + return false; + + + (*size)++; + return func(stream, iter->pos, pItem); + } + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +#endif +} + static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iterator_t *iter) { pb_callback_t *pCallback = (pb_callback_t*)iter->pData; @@ -519,6 +634,9 @@ static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_t case PB_ATYPE_STATIC: return decode_static_field(stream, wire_type, iter); + case PB_ATYPE_POINTER: + return decode_pointer_field(stream, wire_type, iter); + case PB_ATYPE_CALLBACK: return decode_callback_field(stream, wire_type, iter); @@ -597,45 +715,60 @@ static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_str pb_field_iterator_t iter; pb_field_init(&iter, fields, dest_struct); - /* Initialize size/has fields and apply default values */ do { pb_type_t type; type = iter.pos->type; + /* Avoid crash on empty message types (zero fields) */ if (iter.pos->tag == 0) continue; if (PB_ATYPE(type) == PB_ATYPE_STATIC) { - /* Initialize the size field for optional/repeated fields to 0. */ if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL) { + /* Set has_field to false. Still initialize the optional field + * itself also. */ *(bool*)iter.pSize = false; } else if (PB_HTYPE(type) == PB_HTYPE_REPEATED) { + /* Set array count to 0, no need to initialize contents. */ *(size_t*)iter.pSize = 0; - continue; /* Array is empty, no need to initialize contents */ + continue; } - /* Initialize field contents to default value */ if (PB_LTYPE(iter.pos->type) == PB_LTYPE_SUBMESSAGE) { + /* Initialize submessage to defaults */ pb_message_set_to_defaults((const pb_field_t *) iter.pos->ptr, iter.pData); } else if (iter.pos->ptr != NULL) { + /* Initialize to default value */ memcpy(iter.pData, iter.pos->ptr, iter.pos->data_size); } else { + /* Initialize to zeros */ memset(iter.pData, 0, iter.pos->data_size); } } + else if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + /* Initialize the pointer to NULL. */ + *(void**)iter.pData = NULL; + + /* Initialize array count to 0. */ + if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + *(size_t*)iter.pSize = 0; + } + } else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) { - continue; /* Don't overwrite callback */ + /* Don't overwrite callback */ } } while (pb_field_next(&iter)); } diff --git a/tests/alltypes_pointer/SConscript b/tests/alltypes_pointer/SConscript index b577ca24..f0103baa 100644 --- a/tests/alltypes_pointer/SConscript +++ b/tests/alltypes_pointer/SConscript @@ -3,18 +3,27 @@ Import("env") +# We need our own pb_decode.o for the malloc support +strict = env.Clone() +strict.Append(CFLAGS = strict['CORECFLAGS']) +strict.Append(CPPDEFINES = {'PB_ENABLE_MALLOC': 1}); +strict.Object("pb_decode_with_malloc.o", "$NANOPB/pb_decode.c") + c = Copy("$TARGET", "$SOURCE") env.Command("alltypes.proto", "#alltypes/alltypes.proto", c) env.NanopbProto(["alltypes", "alltypes.options"]) enc = env.Program(["encode_alltypes_pointer.c", "alltypes.pb.c", "$COMMON/pb_encode.o"]) +dec = env.Program(["decode_alltypes_pointer.c", "alltypes.pb.c", "pb_decode_with_malloc.o"]) refdec = "$BUILD/alltypes/decode_alltypes$PROGSUFFIX" # Encode and compare results env.RunTest(enc) -env.RunTest("decode_alltypes.output", [refdec, "encode_alltypes_pointer.output"]) +env.RunTest("decode_alltypes.output", [dec, "encode_alltypes_pointer.output"]) +env.RunTest("decode_alltypes_ref.output", [refdec, "encode_alltypes_pointer.output"]) env.Compare(["encode_alltypes_pointer.output", "$BUILD/alltypes/encode_alltypes.output"]) +env.Compare(["encode_alltypes_pointer_ref.output", "$BUILD/alltypes/encode_alltypes.output"]) # Do the same thing with the optional fields present env.RunTest("optionals.output", enc, ARGS = ['1']) diff --git a/tests/alltypes_pointer/decode_alltypes_pointer.c b/tests/alltypes_pointer/decode_alltypes_pointer.c new file mode 100644 index 00000000..32e34c58 --- /dev/null +++ b/tests/alltypes_pointer/decode_alltypes_pointer.c @@ -0,0 +1,210 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <pb_decode.h> +#include "alltypes.pb.h" +#include "test_helpers.h" + +#define TEST(x) if (!(x)) { \ + printf("Test " #x " failed.\n"); \ + return false; \ + } + +/* This function is called once from main(), it handles + the decoding and checks the fields. */ +bool check_alltypes(pb_istream_t *stream, int mode) +{ + AllTypes alltypes; + + /* Fill with garbage to better detect initialization errors */ + memset(&alltypes, 0xAA, sizeof(alltypes)); + + if (!pb_decode(stream, AllTypes_fields, &alltypes)) + return false; + + TEST(*alltypes.req_int32 == -1001); + TEST(*alltypes.req_int64 == -1002); + TEST(*alltypes.req_uint32 == 1003); + TEST(*alltypes.req_uint64 == 1004); + TEST(*alltypes.req_sint32 == -1005); + TEST(*alltypes.req_sint64 == -1006); + TEST(*alltypes.req_bool == true); + + TEST(*alltypes.req_fixed32 == 1008); + TEST(*alltypes.req_sfixed32 == -1009); + TEST(*alltypes.req_float == 1010.0f); + + TEST(*alltypes.req_fixed64 == 1011); + TEST(*alltypes.req_sfixed64 == -1012); + TEST(*alltypes.req_double == 1013.0f); + + TEST(strcmp(alltypes.req_string, "1014") == 0); + TEST(alltypes.req_bytes->size == 4); + TEST(memcmp(alltypes.req_bytes->bytes, "1015", 4) == 0); + TEST(strcmp(alltypes.req_submsg->substuff1, "1016") == 0); + TEST(*alltypes.req_submsg->substuff2 == 1016); + TEST(*alltypes.req_submsg->substuff3 == 3); + TEST(*alltypes.req_enum == MyEnum_Truth); + +#if 0 + TEST(alltypes.rep_int32_count == 5 && alltypes.rep_int32[4] == -2001 && alltypes.rep_int32[0] == 0); + TEST(alltypes.rep_int64_count == 5 && alltypes.rep_int64[4] == -2002 && alltypes.rep_int64[0] == 0); + TEST(alltypes.rep_uint32_count == 5 && alltypes.rep_uint32[4] == 2003 && alltypes.rep_uint32[0] == 0); + TEST(alltypes.rep_uint64_count == 5 && alltypes.rep_uint64[4] == 2004 && alltypes.rep_uint64[0] == 0); + TEST(alltypes.rep_sint32_count == 5 && alltypes.rep_sint32[4] == -2005 && alltypes.rep_sint32[0] == 0); + TEST(alltypes.rep_sint64_count == 5 && alltypes.rep_sint64[4] == -2006 && alltypes.rep_sint64[0] == 0); + TEST(alltypes.rep_bool_count == 5 && alltypes.rep_bool[4] == true && alltypes.rep_bool[0] == false); + + TEST(alltypes.rep_fixed32_count == 5 && alltypes.rep_fixed32[4] == 2008 && alltypes.rep_fixed32[0] == 0); + TEST(alltypes.rep_sfixed32_count == 5 && alltypes.rep_sfixed32[4] == -2009 && alltypes.rep_sfixed32[0] == 0); + TEST(alltypes.rep_float_count == 5 && alltypes.rep_float[4] == 2010.0f && alltypes.rep_float[0] == 0.0f); + + TEST(alltypes.rep_fixed64_count == 5 && alltypes.rep_fixed64[4] == 2011 && alltypes.rep_fixed64[0] == 0); + TEST(alltypes.rep_sfixed64_count == 5 && alltypes.rep_sfixed64[4] == -2012 && alltypes.rep_sfixed64[0] == 0); + TEST(alltypes.rep_double_count == 5 && alltypes.rep_double[4] == 2013.0 && alltypes.rep_double[0] == 0.0); + + TEST(alltypes.rep_string_count == 5 && strcmp(alltypes.rep_string[4], "2014") == 0 && alltypes.rep_string[0][0] == '\0'); + TEST(alltypes.rep_bytes_count == 5 && alltypes.rep_bytes[4].size == 4 && alltypes.rep_bytes[0].size == 0); + TEST(memcmp(alltypes.rep_bytes[4].bytes, "2015", 4) == 0); + + TEST(alltypes.rep_submsg_count == 5); + TEST(strcmp(alltypes.rep_submsg[4].substuff1, "2016") == 0 && alltypes.rep_submsg[0].substuff1[0] == '\0'); + TEST(alltypes.rep_submsg[4].substuff2 == 2016 && alltypes.rep_submsg[0].substuff2 == 0); + TEST(alltypes.rep_submsg[4].substuff3 == 2016 && alltypes.rep_submsg[0].substuff3 == 3); + + TEST(alltypes.rep_enum_count == 5 && alltypes.rep_enum[4] == MyEnum_Truth && alltypes.rep_enum[0] == MyEnum_Zero); + TEST(alltypes.rep_emptymsg_count == 5); + + if (mode == 0) + { + /* Expect default values */ + TEST(alltypes.has_opt_int32 == false); + TEST(alltypes.opt_int32 == 4041); + TEST(alltypes.has_opt_int64 == false); + TEST(alltypes.opt_int64 == 4042); + TEST(alltypes.has_opt_uint32 == false); + TEST(alltypes.opt_uint32 == 4043); + TEST(alltypes.has_opt_uint64 == false); + TEST(alltypes.opt_uint64 == 4044); + TEST(alltypes.has_opt_sint32 == false); + TEST(alltypes.opt_sint32 == 4045); + TEST(alltypes.has_opt_sint64 == false); + TEST(alltypes.opt_sint64 == 4046); + TEST(alltypes.has_opt_bool == false); + TEST(alltypes.opt_bool == false); + + TEST(alltypes.has_opt_fixed32 == false); + TEST(alltypes.opt_fixed32 == 4048); + TEST(alltypes.has_opt_sfixed32 == false); + TEST(alltypes.opt_sfixed32 == 4049); + TEST(alltypes.has_opt_float == false); + TEST(alltypes.opt_float == 4050.0f); + + TEST(alltypes.has_opt_fixed64 == false); + TEST(alltypes.opt_fixed64 == 4051); + TEST(alltypes.has_opt_sfixed64 == false); + TEST(alltypes.opt_sfixed64 == 4052); + TEST(alltypes.has_opt_double == false); + TEST(alltypes.opt_double == 4053.0); + + TEST(alltypes.has_opt_string == false); + TEST(strcmp(alltypes.opt_string, "4054") == 0); + TEST(alltypes.has_opt_bytes == false); + TEST(alltypes.opt_bytes.size == 4); + TEST(memcmp(alltypes.opt_bytes.bytes, "4055", 4) == 0); + TEST(alltypes.has_opt_submsg == false); + TEST(strcmp(alltypes.opt_submsg.substuff1, "1") == 0); + TEST(alltypes.opt_submsg.substuff2 == 2); + TEST(alltypes.opt_submsg.substuff3 == 3); + TEST(alltypes.has_opt_enum == false); + TEST(alltypes.opt_enum == MyEnum_Second); + TEST(alltypes.has_opt_emptymsg == false); + } + else + { + /* Expect filled-in values */ + TEST(alltypes.has_opt_int32 == true); + TEST(alltypes.opt_int32 == 3041); + TEST(alltypes.has_opt_int64 == true); + TEST(alltypes.opt_int64 == 3042); + TEST(alltypes.has_opt_uint32 == true); + TEST(alltypes.opt_uint32 == 3043); + TEST(alltypes.has_opt_uint64 == true); + TEST(alltypes.opt_uint64 == 3044); + TEST(alltypes.has_opt_sint32 == true); + TEST(alltypes.opt_sint32 == 3045); + TEST(alltypes.has_opt_sint64 == true); + TEST(alltypes.opt_sint64 == 3046); + TEST(alltypes.has_opt_bool == true); + TEST(alltypes.opt_bool == true); + + TEST(alltypes.has_opt_fixed32 == true); + TEST(alltypes.opt_fixed32 == 3048); + TEST(alltypes.has_opt_sfixed32 == true); + TEST(alltypes.opt_sfixed32 == 3049); + TEST(alltypes.has_opt_float == true); + TEST(alltypes.opt_float == 3050.0f); + + TEST(alltypes.has_opt_fixed64 == true); + TEST(alltypes.opt_fixed64 == 3051); + TEST(alltypes.has_opt_sfixed64 == true); + TEST(alltypes.opt_sfixed64 == 3052); + TEST(alltypes.has_opt_double == true); + TEST(alltypes.opt_double == 3053.0); + + TEST(alltypes.has_opt_string == true); + TEST(strcmp(alltypes.opt_string, "3054") == 0); + TEST(alltypes.has_opt_bytes == true); + TEST(alltypes.opt_bytes.size == 4); + TEST(memcmp(alltypes.opt_bytes.bytes, "3055", 4) == 0); + TEST(alltypes.has_opt_submsg == true); + TEST(strcmp(alltypes.opt_submsg.substuff1, "3056") == 0); + TEST(alltypes.opt_submsg.substuff2 == 3056); + TEST(alltypes.opt_submsg.substuff3 == 3); + TEST(alltypes.has_opt_enum == true); + TEST(alltypes.opt_enum == MyEnum_Truth); + TEST(alltypes.has_opt_emptymsg == true); + } + + TEST(alltypes.req_limits.int32_min == INT32_MIN); + TEST(alltypes.req_limits.int32_max == INT32_MAX); + TEST(alltypes.req_limits.uint32_min == 0); + TEST(alltypes.req_limits.uint32_max == UINT32_MAX); + TEST(alltypes.req_limits.int64_min == INT64_MIN); + TEST(alltypes.req_limits.int64_max == INT64_MAX); + TEST(alltypes.req_limits.uint64_min == 0); + TEST(alltypes.req_limits.uint64_max == UINT64_MAX); + TEST(alltypes.req_limits.enum_min == HugeEnum_Negative); + TEST(alltypes.req_limits.enum_max == HugeEnum_Positive); + + TEST(alltypes.end == 1099); +#endif + + return true; +} + +int main(int argc, char **argv) +{ + uint8_t buffer[1024]; + size_t count; + pb_istream_t stream; + + /* Whether to expect the optional values or the default values. */ + int mode = (argc > 1) ? atoi(argv[1]) : 0; + + /* Read the data into buffer */ + SET_BINARY_MODE(stdin); + count = fread(buffer, 1, sizeof(buffer), stdin); + + /* Construct a pb_istream_t for reading from the buffer */ + stream = pb_istream_from_buffer(buffer, count); + + /* Decode and print out the stuff */ + if (!check_alltypes(&stream, mode)) + { + printf("Parsing failed: %s\n", PB_GET_ERROR(&stream)); + return 1; + } else { + return 0; + } +} |