diff options
-rw-r--r-- | pb_encode.c | 33 | ||||
-rw-r--r-- | pb_encode.h | 6 | ||||
-rw-r--r-- | tests/Makefile | 9 | ||||
-rw-r--r-- | tests/decode_unittests.c | 26 | ||||
-rw-r--r-- | tests/encode_unittests.c | 123 | ||||
-rw-r--r-- | tests/unittests.h | 14 |
6 files changed, 179 insertions, 32 deletions
diff --git a/pb_encode.c b/pb_encode.c index ae185c1..63ca288 100644 --- a/pb_encode.c +++ b/pb_encode.c @@ -44,15 +44,17 @@ pb_ostream_t pb_ostream_from_buffer(uint8_t *buf, size_t bufsize) bool pb_write(pb_ostream_t *stream, const uint8_t *buf, size_t count) { - stream->bytes_written += count; - - if (stream->callback == NULL) - return true; - - if (stream->bytes_written > stream->max_size) - return false; + if (stream->callback != NULL) + { + if (stream->bytes_written + count > stream->max_size) + return false; + + if (!stream->callback(stream, buf, count)) + return false; + } - return stream->callback(stream, buf, count); + stream->bytes_written += count; + return true; } /* Main encoding stuff */ @@ -231,7 +233,7 @@ bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field) return pb_encode_tag(stream, wiretype, field->tag); } -bool pb_encode_string(pb_ostream_t *stream, uint8_t *buffer, size_t size) +bool pb_encode_string(pb_ostream_t *stream, const uint8_t *buffer, size_t size) { if (!pb_encode_varint(stream, size)) return false; @@ -268,14 +270,17 @@ bool pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *s { uint64_t value = 0; uint64_t zigzagged; - uint64_t mask; + uint64_t signbitmask, xormask; endian_copy(&value, src, sizeof(value), field->data_size); - mask = 0x80 << (field->data_size * 8); - zigzagged = (value & ~mask) << 1; - if (value & mask) zigzagged |= 1; + signbitmask = (uint64_t)0x80 << (field->data_size * 8 - 8); + xormask = ((uint64_t)-1) >> (64 - field->data_size * 8); + if (value & signbitmask) + zigzagged = ((value ^ xormask) << 1) | 1; + else + zigzagged = value << 1; - return pb_encode_varint(stream, value); + return pb_encode_varint(stream, zigzagged); } bool pb_enc_fixed(pb_ostream_t *stream, const pb_field_t *field, const void *src) diff --git a/pb_encode.h b/pb_encode.h index 95f3483..17ba5b9 100644 --- a/pb_encode.h +++ b/pb_encode.h @@ -21,7 +21,7 @@ * * 2) You can use state to store your own data (e.g. buffer pointer). * - * 3) pb_write will update bytes_written before your callback runs. + * 3) pb_write will update bytes_written after your callback runs. * * 4) Your callback will be always used with the same pb_ostream_t. * There are no substreams when encoding. @@ -50,8 +50,10 @@ bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_ bool pb_encode_varint(pb_ostream_t *stream, uint64_t value); bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, int field_number); +/* Encode tag based on LTYPE and field number defined in the field structure. */ bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field); -bool pb_encode_string(pb_ostream_t *stream, uint8_t *buffer, size_t size); +/* Write length as varint and then the contents of buffer. */ +bool pb_encode_string(pb_ostream_t *stream, const uint8_t *buffer, size_t size); /* --- Field encoders --- * Each encoder writes the content for the field. diff --git a/tests/Makefile b/tests/Makefile index 7450e1e..84c035e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,8 +1,8 @@ CFLAGS=-ansi -Wall -Werror -I .. -g -O0 -DEPS=../pb_decode.c ../pb_decode.h ../pb_encode.c ../pb_encode.h ../pb.h person.h -TESTS=test_decode1 test_encode1 decode_unittests +DEPS=../pb_decode.c ../pb_decode.h ../pb_encode.c ../pb_encode.h ../pb.h person.h unittests.h +TESTS=test_decode1 test_encode1 decode_unittests encode_unittests -all: $(TESTS) +all: $(TESTS) run_unittests clean: rm -f $(TESTS) @@ -10,8 +10,9 @@ clean: %: %.c $(DEPS) $(CC) $(CFLAGS) -o $@ $< ../pb_decode.c ../pb_encode.c -run_unittests: decode_unittests +run_unittests: decode_unittests encode_unittests ./decode_unittests + ./encode_unittests run_fuzztest: test_decode1 bash -c 'I=1; while cat /dev/urandom | ./test_decode1 > /dev/null; do I=$$(($$I+1)); echo -en "\r$$I"; done'
\ No newline at end of file diff --git a/tests/decode_unittests.c b/tests/decode_unittests.c index 7d3b13e..ff4c6b4 100644 --- a/tests/decode_unittests.c +++ b/tests/decode_unittests.c @@ -1,17 +1,7 @@ #include <stdio.h> #include <string.h> #include "pb_decode.h" - -#define COMMENT(x) printf("\n----" x "----\n"); -#define STR(x) #x -#define STR2(x) STR(x) -#define TEST(x) \ - if (!(x)) { \ - fprintf(stderr, __FILE__ ":" STR2(__LINE__) " FAILED:" #x "\n"); \ - status = 1; \ - } else { \ - printf("OK: " #x "\n"); \ - } +#include "unittests.h" #define S(x) pb_istream_from_buffer((uint8_t*)x, sizeof(x)) @@ -114,7 +104,7 @@ int main() /* Verify that no more than data_size is written. */ d = 0; f.data_size = 1; - TEST(pb_dec_varint(&s, &f, &d) && d == 0xFF) + TEST(pb_dec_varint(&s, &f, &d) && (d == 0xFF || d == 0xFF000000)) } { @@ -129,6 +119,18 @@ int main() TEST((s = S("\xff\xff\xff\xff\x0f"), pb_dec_svarint(&s, &f, &d) && d == INT32_MIN)) } + { + pb_istream_t s; + pb_field_t f = {1, PB_LTYPE_SVARINT, 0, 0, 8, 0, 0}; + uint64_t d; + + COMMENT("Test pb_dec_svarint using uint64_t") + TEST((s = S("\x01"), pb_dec_svarint(&s, &f, &d) && d == -1)) + TEST((s = S("\x02"), pb_dec_svarint(&s, &f, &d) && d == 1)) + TEST((s = S("\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"), pb_dec_svarint(&s, &f, &d) && d == INT64_MAX)) + TEST((s = S("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"), pb_dec_svarint(&s, &f, &d) && d == INT64_MIN)) + } + if (status != 0) fprintf(stdout, "\n\nSome tests FAILED!\n"); diff --git a/tests/encode_unittests.c b/tests/encode_unittests.c new file mode 100644 index 0000000..645dd21 --- /dev/null +++ b/tests/encode_unittests.c @@ -0,0 +1,123 @@ +#include <stdio.h> +#include <string.h> +#include "pb_encode.h" +#include "unittests.h" + +bool streamcallback(pb_ostream_t *stream, const uint8_t *buf, size_t count) +{ + /* Allow only 'x' to be written */ + while (count--) + { + if (*buf++ != 'x') + return false; + } + return true; +} + +/* Check that expression x writes data y. + * Y is a string, which may contain null bytes. Null terminator is ignored. + */ +#define WRITES(x, y) \ +memset(buffer, 0xAA, sizeof(buffer)), \ +s = pb_ostream_from_buffer(buffer, sizeof(buffer)), \ +(x) && \ +memcmp(buffer, y, sizeof(y) - 1) == 0 && \ +buffer[sizeof(y) - 1] == 0xAA + +int main() +{ + int status = 0; + + { + uint8_t buffer1[] = "foobartest1234"; + uint8_t buffer2[sizeof(buffer1)]; + pb_ostream_t stream = pb_ostream_from_buffer(buffer2, sizeof(buffer1)); + + COMMENT("Test pb_write and pb_ostream_t"); + TEST(pb_write(&stream, buffer1, sizeof(buffer1))); + TEST(memcmp(buffer1, buffer2, sizeof(buffer1)) == 0); + TEST(!pb_write(&stream, buffer1, 1)); + TEST(stream.bytes_written == sizeof(buffer1)); + } + + { + uint8_t buffer1[] = "xxxxxxx"; + pb_ostream_t stream = {&streamcallback, 0, SIZE_MAX, 0}; + + COMMENT("Test pb_write with custom callback"); + TEST(pb_write(&stream, buffer1, 5)); + buffer1[0] = 'a'; + TEST(!pb_write(&stream, buffer1, 5)); + } + + { + uint8_t buffer[30]; + pb_ostream_t s; + + COMMENT("Test pb_encode_varint") + TEST(WRITES(pb_encode_varint(&s, 0), "\0")); + TEST(WRITES(pb_encode_varint(&s, 1), "\1")); + TEST(WRITES(pb_encode_varint(&s, 0x7F), "\x7F")); + TEST(WRITES(pb_encode_varint(&s, 0x80), "\x80\x01")); + TEST(WRITES(pb_encode_varint(&s, UINT32_MAX), "\xFF\xFF\xFF\xFF\x0F")); + TEST(WRITES(pb_encode_varint(&s, UINT64_MAX), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01")); + } + + { + uint8_t buffer[30]; + pb_ostream_t s; + + COMMENT("Test pb_encode_tag") + TEST(WRITES(pb_encode_tag(&s, PB_WT_STRING, 5), "\x2A")); + TEST(WRITES(pb_encode_tag(&s, PB_WT_VARINT, 99), "\x98\x06")); + } + + { + uint8_t buffer[30]; + pb_ostream_t s; + pb_field_t field = {10, PB_LTYPE_SVARINT}; + + COMMENT("Test pb_encode_tag_for_field") + TEST(WRITES(pb_encode_tag_for_field(&s, &field), "\x50")); + } + + { + uint8_t buffer[30]; + pb_ostream_t s; + + COMMENT("Test pb_encode_string") + TEST(WRITES(pb_encode_string(&s, (const uint8_t*)"abcd", 4), "\x04""abcd")); + TEST(WRITES(pb_encode_string(&s, (const uint8_t*)"abcd\x00", 5), "\x05""abcd\x00")); + TEST(WRITES(pb_encode_string(&s, (const uint8_t*)"", 0), "\x00")); + } + + { + uint8_t buffer[30]; + pb_ostream_t s; + uint8_t value = 1; + int8_t svalue = -1; + int32_t max = INT32_MAX; + int32_t min = INT32_MIN; + int64_t lmax = INT64_MAX; + int64_t lmin = INT64_MIN; + pb_field_t field = {1, PB_LTYPE_VARINT, 0, 0, sizeof(value)}; + + COMMENT("Test pb_enc_varint and pb_enc_svarint") + TEST(WRITES(pb_enc_varint(&s, &field, &value), "\x01")); + TEST(WRITES(pb_enc_svarint(&s, &field, &svalue), "\x01")); + TEST(WRITES(pb_enc_svarint(&s, &field, &value), "\x02")); + + field.data_size = sizeof(max); + TEST(WRITES(pb_enc_svarint(&s, &field, &max), "\xfe\xff\xff\xff\x0f")); + TEST(WRITES(pb_enc_svarint(&s, &field, &min), "\xff\xff\xff\xff\x0f")); + + field.data_size = sizeof(lmax); + TEST(WRITES(pb_enc_svarint(&s, &field, &lmax), "\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01")); + TEST(WRITES(pb_enc_svarint(&s, &field, &lmin), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01")); + } + + if (status != 0) + fprintf(stdout, "\n\nSome tests FAILED!\n"); + + return status; +} diff --git a/tests/unittests.h b/tests/unittests.h new file mode 100644 index 0000000..c2b470a --- /dev/null +++ b/tests/unittests.h @@ -0,0 +1,14 @@ +#include <stdio.h> + +#define COMMENT(x) printf("\n----" x "----\n"); +#define STR(x) #x +#define STR2(x) STR(x) +#define TEST(x) \ + if (!(x)) { \ + fprintf(stderr, "\033[31;1mFAILED:\033[22;39m " __FILE__ ":" STR2(__LINE__) " " #x "\n"); \ + status = 1; \ + } else { \ + printf("\033[32;1mOK:\033[22;39m " #x "\n"); \ + } + + |