diff options
-rw-r--r-- | docs/index.rst | 7 | ||||
-rw-r--r-- | pb_decode.c | 5 | ||||
-rw-r--r-- | tests/Makefile | 21 | ||||
-rw-r--r-- | tests/decode_unittests.c | 117 | ||||
-rw-r--r-- | tests/encode_unittests.c | 47 |
5 files changed, 188 insertions, 9 deletions
diff --git a/docs/index.rst b/docs/index.rst index f2f8dcfd..76580364 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,6 +48,7 @@ Features and limitations #) The deprecated Protocol Buffers feature called "groups" is not supported. #) Fields in the generated structs are ordered by the tag number, instead of the natural ordering in .proto file. #) Unknown fields are not preserved when decoding and re-encoding a message. +#) Numeric arrays are always encoded as packed, even if not marked as packed in .proto. This causes incompatibility with decoders that do not support packed format. Getting started =============== @@ -82,6 +83,12 @@ After that, buffer will contain the encoded message. The number of bytes in the message is stored in *stream.bytes_written*. You can feed the message to *protoc --decode=Example example.proto* to verify its validity. +Debugging and testing +===================== +Extensive unittests are included under the *tests* folder. Just type *make* there to run the tests. + +This also generates a file called *breakpoints* which includes all lines returning *false* in nanopb. You can use this in gdb by typing *source breakpoints*, after which gdb will break on first nanopb error. + Wishlist ======== #) A specialized encoder for encoding to a memory buffer. Should serialize in reverse order to avoid having to determine submessage size beforehand. diff --git a/pb_decode.c b/pb_decode.c index 9208bdac..26924472 100644 --- a/pb_decode.c +++ b/pb_decode.c @@ -257,7 +257,7 @@ static bool decode_field(pb_istream_t *stream, int wire_type, pb_field_iterator_ while (substream.bytes_left && *size < iter->current->array_size) { void *pItem = (uint8_t*)iter->pData + iter->current->data_size * (*size); - if (!func(stream, iter->current, pItem)) + if (!func(&substream, iter->current, pItem)) return false; (*size)++; } @@ -381,7 +381,8 @@ bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struc if (!pb_field_find(&iter, tag)) { /* No match found, skip data */ - skip(stream, wire_type); + if (!skip(stream, wire_type)) + return false; continue; } diff --git a/tests/Makefile b/tests/Makefile index 115364b6..38bdb25f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -11,10 +11,15 @@ clean: %.o: %.c $(DEPS) $(CC) $(CFLAGS) -c -o $@ $< -test_decode1: test_decode1.o ../pb_decode.o person.o -test_encode1: test_encode1.o ../pb_encode.o person.o -decode_unittests: decode_unittests.o ../pb_decode.o person.o -encode_unittests: encode_unittests.o ../pb_encode.o person.o +pb_encode.o: ../pb_encode.c $(DEPS) + $(CC) $(CFLAGS) -c -o $@ $< +pb_decode.o: ../pb_decode.c $(DEPS) + $(CC) $(CFLAGS) -c -o $@ $< + +test_decode1: test_decode1.o pb_decode.o person.o +test_encode1: test_encode1.o pb_encode.o person.o +decode_unittests: decode_unittests.o pb_decode.o person.o +encode_unittests: encode_unittests.o pb_encode.o person.o person.c person.h: person.proto protoc -I. -I../generator -I/usr/include -operson.pb $< @@ -23,11 +28,13 @@ person.c person.h: person.proto breakpoints: ../*.c *.c grep -n 'return false;' $^ | cut -d: -f-2 | xargs -n 1 echo b > $@ -coverage: - gcov -o .. ../pb_encode.c - gcov -o .. ../pb_decode.c +coverage: run_unittests + gcov pb_encode.gcda + gcov pb_decode.gcda run_unittests: decode_unittests encode_unittests test_encode1 test_decode1 + rm -f *.gcda + ./decode_unittests > /dev/null ./encode_unittests > /dev/null diff --git a/tests/decode_unittests.c b/tests/decode_unittests.c index e733379d..85dd8d58 100644 --- a/tests/decode_unittests.c +++ b/tests/decode_unittests.c @@ -15,6 +15,43 @@ bool stream_callback(pb_istream_t *stream, uint8_t *buf, size_t count) return true; } +typedef struct { size_t data_count; int32_t data[10]; } IntegerArray; +const pb_field_t IntegerArray_fields[] = { + {1, PB_HTYPE_ARRAY | PB_LTYPE_VARINT, offsetof(IntegerArray, data), + pb_delta(IntegerArray, data_count, data), + pb_membersize(IntegerArray, data[0]), + pb_membersize(IntegerArray, data) / pb_membersize(IntegerArray, data[0])}, + + PB_LAST_FIELD +}; + +typedef struct { pb_callback_t data; } CallbackArray; +const pb_field_t CallbackArray_fields[] = { + {1, PB_HTYPE_CALLBACK | PB_LTYPE_VARINT, offsetof(CallbackArray, data), + 0, pb_membersize(CallbackArray, data), 0}, + + PB_LAST_FIELD +}; + +/* Verifies that the stream passed to callback matches the byte array pointed to by arg. */ +bool callback_check(pb_istream_t *stream, const pb_field_t *field, void *arg) +{ + int i; + uint8_t byte; + pb_bytes_array_t *ref = (pb_bytes_array_t*) arg; + + for (i = 0; i < ref->size; i++) + { + if (!pb_read(stream, &byte, 1)) + return false; + + if (byte != ref->bytes[i]) + return false; + } + + return true; +} + int main() { int status = 0; @@ -72,6 +109,7 @@ int main() TEST((s = S("\xAC\x02""foobar"), pb_skip_varint(&s) && s.bytes_left == 6)) TEST((s = S("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01""foobar"), pb_skip_varint(&s) && s.bytes_left == 6)) + TEST((s = S("\xFF"), !pb_skip_varint(&s))) } { @@ -79,6 +117,8 @@ int main() COMMENT("Test pb_skip_string") TEST((s = S("\x00""foobar"), pb_skip_string(&s) && s.bytes_left == 6)) TEST((s = S("\x04""testfoobar"), pb_skip_string(&s) && s.bytes_left == 6)) + TEST((s = S("\x04"), !pb_skip_string(&s))) + TEST((s = S("\xFF"), !pb_skip_string(&s))) } { @@ -165,6 +205,83 @@ int main() TEST((s = S("\x05xyzzy"), !pb_dec_string(&s, &f, &d))) } + { + pb_istream_t s; + IntegerArray dest; + + COMMENT("Testing pb_decode with repeated int32 field") + TEST((s = S(""), pb_decode(&s, IntegerArray_fields, &dest) && dest.data_count == 0)) + TEST((s = S("\x08\x01\x08\x02"), pb_decode(&s, IntegerArray_fields, &dest) + && dest.data_count == 2 && dest.data[0] == 1 && dest.data[1] == 2)) + s = S("\x08\x01\x08\x02\x08\x03\x08\x04\x08\x05\x08\x06\x08\x07\x08\x08\x08\x09\x08\x0A"); + TEST(pb_decode(&s, IntegerArray_fields, &dest) && dest.data_count == 10 && dest.data[9] == 10) + s = S("\x08\x01\x08\x02\x08\x03\x08\x04\x08\x05\x08\x06\x08\x07\x08\x08\x08\x09\x08\x0A\x08\x0B"); + TEST(!pb_decode(&s, IntegerArray_fields, &dest)) + } + + { + pb_istream_t s; + IntegerArray dest; + + COMMENT("Testing pb_decode with packed int32 field") + TEST((s = S("\x0A\x01\x01"), pb_decode(&s, IntegerArray_fields, &dest) + && dest.data_count == 1 && dest.data[0] == 1)) + TEST((s = S("\x0A\x0A\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"), pb_decode(&s, IntegerArray_fields, &dest) + && dest.data_count == 10 && dest.data[0] == 1 && dest.data[9] == 10)) + TEST((s = S("\x0A\x0B\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B"), !pb_decode(&s, IntegerArray_fields, &dest))) + + /* Test invalid wire data */ + TEST((s = S("\x0A\xFF"), !pb_decode(&s, IntegerArray_fields, &dest))) + TEST((s = S("\x0A\x01"), !pb_decode(&s, IntegerArray_fields, &dest))) + } + + { + pb_istream_t s; + IntegerArray dest; + + COMMENT("Testing pb_decode with unknown fields") + TEST((s = S("\x18\x0F\x08\x01"), pb_decode(&s, IntegerArray_fields, &dest) + && dest.data_count == 1 && dest.data[0] == 1)) + TEST((s = S("\x19\x00\x00\x00\x00\x00\x00\x00\x00\x08\x01"), pb_decode(&s, IntegerArray_fields, &dest) + && dest.data_count == 1 && dest.data[0] == 1)) + TEST((s = S("\x1A\x00\x08\x01"), pb_decode(&s, IntegerArray_fields, &dest) + && dest.data_count == 1 && dest.data[0] == 1)) + TEST((s = S("\x1B\x08\x01"), !pb_decode(&s, IntegerArray_fields, &dest))) + TEST((s = S("\x1D\x00\x00\x00\x00\x08\x01"), pb_decode(&s, IntegerArray_fields, &dest) + && dest.data_count == 1 && dest.data[0] == 1)) + } + + { + pb_istream_t s; + CallbackArray dest; + struct { size_t size; uint8_t bytes[10]; } ref; + dest.data.funcs.decode = &callback_check; + dest.data.arg = &ref; + + COMMENT("Testing pb_decode with callbacks") + /* Single varint */ + ref.size = 1; ref.bytes[0] = 0x55; + TEST((s = S("\x08\x55"), pb_decode(&s, CallbackArray_fields, &dest))) + /* Packed varint */ + ref.size = 3; ref.bytes[0] = ref.bytes[1] = ref.bytes[2] = 0x55; + TEST((s = S("\x0A\x03\x55\x55\x55"), pb_decode(&s, CallbackArray_fields, &dest))) + /* Packed varint with loop */ + ref.size = 1; ref.bytes[0] = 0x55; + TEST((s = S("\x0A\x03\x55\x55\x55"), pb_decode(&s, CallbackArray_fields, &dest))) + /* Single fixed32 */ + ref.size = 4; ref.bytes[0] = ref.bytes[1] = ref.bytes[2] = ref.bytes[3] = 0xAA; + TEST((s = S("\x0D\xAA\xAA\xAA\xAA"), pb_decode(&s, CallbackArray_fields, &dest))) + /* Single fixed64 */ + ref.size = 8; memset(ref.bytes, 0xAA, 8); + TEST((s = S("\x09\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"), pb_decode(&s, CallbackArray_fields, &dest))) + /* Unsupported field type */ + TEST((s = S("\x0B\x00"), !pb_decode(&s, CallbackArray_fields, &dest))) + + /* Just make sure that our test function works */ + ref.size = 1; ref.bytes[0] = 0x56; + TEST((s = S("\x08\x55"), !pb_decode(&s, CallbackArray_fields, &dest))) + } + if (status != 0) fprintf(stdout, "\n\nSome tests FAILED!\n"); diff --git a/tests/encode_unittests.c b/tests/encode_unittests.c index 645dd21a..ee7f7a34 100644 --- a/tests/encode_unittests.c +++ b/tests/encode_unittests.c @@ -116,6 +116,53 @@ int main() TEST(WRITES(pb_enc_svarint(&s, &field, &lmin), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01")); } + { + uint8_t buffer[30]; + pb_ostream_t s; + pb_field_t field = {1, PB_LTYPE_FIXED, 0, 0, sizeof(float)}; + float fvalue; + double dvalue; + + COMMENT("Test pb_enc_fixed using float") + fvalue = 0.0f; + TEST(WRITES(pb_enc_fixed(&s, &field, &fvalue), "\x00\x00\x00\x00")) + fvalue = 99.0f; + TEST(WRITES(pb_enc_fixed(&s, &field, &fvalue), "\x00\x00\xc6\x42")) + fvalue = -12345678.0f; + TEST(WRITES(pb_enc_fixed(&s, &field, &fvalue), "\x4e\x61\x3c\xcb")) + + COMMENT("Test pb_enc_fixed using double") + field.data_size = sizeof(double); + dvalue = 0.0; + TEST(WRITES(pb_enc_fixed(&s, &field, &dvalue), "\x00\x00\x00\x00\x00\x00\x00\x00")) + dvalue = 99.0; + TEST(WRITES(pb_enc_fixed(&s, &field, &dvalue), "\x00\x00\x00\x00\x00\xc0\x58\x40")) + dvalue = -12345678.0; + TEST(WRITES(pb_enc_fixed(&s, &field, &dvalue), "\x00\x00\x00\xc0\x29\x8c\x67\xc1")) + } + + { + uint8_t buffer[30]; + pb_ostream_t s; + struct { size_t size; uint8_t bytes[5]; } value = {5, {'x', 'y', 'z', 'z', 'y'}}; + + COMMENT("Test pb_enc_bytes") + TEST(WRITES(pb_enc_bytes(&s, NULL, &value), "\x05xyzzy")) + value.size = 0; + TEST(WRITES(pb_enc_bytes(&s, NULL, &value), "\x00")) + } + + { + uint8_t buffer[30]; + pb_ostream_t s; + char value[] = "xyzzy"; + + COMMENT("Test pb_enc_string") + TEST(WRITES(pb_enc_string(&s, NULL, &value), "\x05xyzzy")) + value[0] = '\0'; + TEST(WRITES(pb_enc_string(&s, NULL, &value), "\x00")) + } + if (status != 0) fprintf(stdout, "\n\nSome tests FAILED!\n"); |