diff options
-rw-r--r-- | pb.h | 162 | ||||
-rw-r--r-- | pb_decode.c | 387 | ||||
-rw-r--r-- | pb_decode.h | 21 | ||||
-rw-r--r-- | tests/test_decode1.c | 79 |
4 files changed, 455 insertions, 194 deletions
@@ -3,19 +3,36 @@ #include <stdint.h> #include <stddef.h> // size_t +#include <stdbool.h> + +#ifdef __GNUC__ +// This just reduces memory requirements, but is not required. +#define pb_packed __attribute__((packed)) +#else +#define pb_packed +#endif /* Lightweight input stream. - * If buf is NULL, read but don't store bytes. */ + * If buf is NULL, read but don't store bytes. + * You have to provide a callback function for reading. + * You can use state to store your own data (e.g. buffer pointer), + * and rely on pb_read to verify that no-body reads past bytes_left. + * However, substreams may change bytes_left so don't use that to + * compute any pointers. + */ typedef struct _pb_istream_t pb_istream_t; struct _pb_istream_t { - bool (*callback)(pb_istream_t *stream, char *buf, size_t count); + bool (*callback)(pb_istream_t *stream, uint8_t *buf, size_t count); void *state; // Free field for use by callback implementation size_t bytes_left; }; -static inline bool pb_read(pb_istream_t *stream, char *buf, size_t count) +static inline bool pb_read(pb_istream_t *stream, uint8_t *buf, size_t count) { + if (stream->bytes_left < count) + return false; + bool status = stream->callback(stream, buf, count); stream->bytes_left -= count; return status; @@ -25,74 +42,129 @@ static inline bool pb_read(pb_istream_t *stream, char *buf, size_t count) typedef struct _pb_ostream_t pb_ostream_t; struct _pb_ostream_t { - bool (*callback)(pb_ostream_t *stream, const char *buf, size_t count); + bool (*callback)(pb_ostream_t *stream, const uint8_t *buf, size_t count); void *state; // Free field for use by callback implementation size_t bytes_written; }; -static inline bool pb_write(pb_ostream_t *stream, const char *buf, size_t count) +static inline bool pb_write(pb_ostream_t *stream, const uint8_t *buf, size_t count) { bool status = stream->callback(stream, buf, count); stream->bytes_written += count; return status; } -/* List of possible decode/encode action types */ +/* List of possible field types + * Least-significant 4 bits tell the scalar type + * Most-significant 4 bits specify repeated/required/packed etc. + * + * INT32 and UINT32 are treated the same, as are (U)INT64 and (S)FIXED* + * These types are simply casted to correct field type when they are + * assigned to the memory pointer. + * SINT* is different, though, because it is zig-zag coded. + */ typedef enum { - // Special case. Sets boolean field to true, continues parsing the data. - PB_ACT_HAS, - // Standard integer types - PB_ACT_UINT32, - PB_ACT_SINT32, - PB_ACT_INT32, - PB_ACT_FIXED32, - PB_ACT_SFIXED32, - PB_ACT_UINT64, - PB_ACT_SINT64, - PB_ACT_INT64, - PB_ACT_FIXED64, - PB_ACT_SFIXED64, - PB_ACT_BOOL, + PB_LTYPE_UINT32 = 0x00, + PB_LTYPE_INT32 = 0x00, + PB_LTYPE_SINT32 = 0x01, + PB_LTYPE_FIXED32 = 0x02, + PB_LTYPE_SFIXED32 = 0x02, + PB_LTYPE_UINT64 = 0x03, + PB_LTYPE_INT64 = 0x03, + PB_LTYPE_SINT64 = 0x04, + PB_LTYPE_FIXED64 = 0x05, + PB_LTYPE_SFIXED64 = 0x05, + PB_LTYPE_BOOL = 0x06, + PB_LTYPE_ENUM = 0x07, // Standard float types - PB_ACT_FLOAT, - PB_ACT_DOUBLE, + PB_LTYPE_FLOAT = 0x08, + PB_LTYPE_DOUBLE = 0x09, + + // Byte array with pre-allocated buffer. + // data_size is the length of the allocated PB_BYTES_ARRAY structure. + PB_LTYPE_BYTES = 0x0A, + + // String with pre-allocated buffer. + // data_size is the maximum length. + PB_LTYPE_STRING = 0x0B, - // Constant-sized array - PB_ACT_BYTES, + // Submessage + // submsg_fields is pointer to field descriptions + PB_LTYPE_SUBMESSAGE = 0x0C, - // Constant-sized array, with null termination - PB_ACT_STRING, + ///////////// + // Modifier flags - // Callback function pointer in field value - PB_ACT_SUBMESSAGE, + // Just the basic, write data at data_offset + PB_HTYPE_REQUIRED = 0x00, - PB_LAST_ACT -} pb_action_t; + // Write true at size_offset + PB_HTYPE_OPTIONAL = 0x10, + + // Read to pre-allocated array + // Maximum number of entries is array_size, + // actual number is stored at size_offset + PB_HTYPE_ARRAY = 0x20, + + // Works for all required/optional/repeated fields. + // data_offset points to pb_callback_t structure. + // LTYPE is ignored. + PB_HTYPE_CALLBACK = 0x30 +} pb_packed pb_type_t; -// This structure is used in constants to specify struct fields. -typedef struct { - int field_number; - uint16_t offset; - pb_action_t action; - uint8_t fieldsize; -} pb_field_t; +#define PB_HTYPE(x) ((x) & 0xF0) +#define PB_LTYPE(x) ((x) & 0x0F) + +/* This structure is used in auto-generated constants + * to specify struct fields. + * You can change field sizes here if you need structures + * larger than 256 bytes or field tags larger than 256. + * The compiler should complain if your .proto has such + * structures ("initializer too large for type"). + */ +typedef struct _pb_field_t pb_field_t; +struct _pb_field_t { + uint8_t tag; + pb_type_t type; + uint8_t data_offset; // Offset of actual data or array start + uint8_t size_offset; // Offset of array size or has-boolean + uint8_t data_size; // Data size in bytes for a single item + uint8_t array_size; // Maximum number of entries in array + + // Field definitions for submessage + // OR default value for all other non-array, non-callback types + // If null, then field will zeroed. + const void *ptr; +} pb_packed; #define PB_LAST_FIELD {0,0,0,0} -/* --- Types to use inside generated structures. --- */ +// This structure is used for 'bytes' arrays. +// It has the number of bytes in the beginning, and after that an array. +#define PB_BYTES_ARRAY(buffersize) \ +struct { \ + size_t size; \ + uint8_t bytes[buffersize]; \ +} -// Byte array and size word. -// Note: because of variable length array, this type cannot be directly used. -// Autogenerated code declares the same type of fields but with explicit length. -typedef struct { - size_t size; - char bytes[]; -} pb_bytearray_t; +typedef PB_BYTES_ARRAY(1) pb_bytes_array_t; // This structure is used for giving the callback function. +// +// The decoding callback will be given a limited-length stream +// If the wire type was string, the length is the length of the string. +// If the wire type was a varint/fixed32/fixed64, the length is the length +// of the actual value. +// The function may be called multiple times (especially for repeated types, +// but also otherwise if the message happens to contain the field multiple +// times.) +// +// The encoding callback will receive the actual output stream. +// It should write all the data in one call, including the field tag and +// wire type. It can write multiple fields. typedef struct _pb_callback_t pb_callback_t; struct _pb_callback_t { union { diff --git a/pb_decode.c b/pb_decode.c index 884cf18e..acd9b204 100644 --- a/pb_decode.c +++ b/pb_decode.c @@ -1,61 +1,63 @@ -/* pb_decode.c -- decode a protobuf using callback functions +/* pb_decode.c -- decode a protobuf using minimal resources * * 2011 Petteri Aimonen <jpa@kapsi.fi> */ +#include "pb.h" #include "pb_decode.h" +#include <string.h> -const pb_decoder_t PB_DECODERS[PB_LAST_ACT] = { - NULL, +const pb_decoder_t PB_DECODERS[16] = { &pb_dec_uint32, &pb_dec_sint32, - &pb_dec_uint32, // Cast to int32 &pb_dec_fixed32, - &pb_dec_fixed32, // Cast to int32 &pb_dec_uint64, &pb_dec_sint64, - &pb_dec_uint64, // Cast to int64 &pb_dec_fixed64, - &pb_dec_fixed64, // Cast to int64 &pb_dec_bool, + &pb_dec_enum, + &pb_dec_float, &pb_dec_double, + &pb_dec_bytes, &pb_dec_string, &pb_dec_submessage }; -enum wire_type { - WT_VARINT = 0, - WT_64BIT = 1, - WT_STRING = 2, - WT_32BIT = 5 -}; +//////////////////////// +// Helper functions +//////////////////////// -// Note: pb_decode_varint32 is a bit un-orthodox: -// it will refuse to decode values that exceed uint32 range. -// The Google implementation would simply cast to 32 bits. -bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest) +static bool buf_read(pb_istream_t *stream, uint8_t *buf, size_t count) { - char byte; - int bitpos = 0; - *dest = 0; + uint8_t *source = (uint8_t*)stream->state; - while (bitpos < 32 && pb_read(stream, &byte, 1)) - { - *dest |= (byte & 0x7F) << bitpos; - bitpos += 7; - - if (!(byte & 0x80)) - return true; - } + if (buf != NULL) + memcpy(buf, source, count); - return false; + stream->state = source + count; + return true; +} + +pb_istream_t pb_istream_from_buffer(uint8_t *buf, size_t bufsize) +{ + pb_istream_t stream = {&buf_read, buf, bufsize}; + return stream; +} + +bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest) +{ + uint64_t temp; + if (!pb_decode_varint64(stream, &temp)) + return false; + *dest = temp; + return true; } bool pb_decode_varint64(pb_istream_t *stream, uint64_t *dest) { - char byte; + uint8_t byte; int bitpos = 0; *dest = 0; @@ -73,7 +75,7 @@ bool pb_decode_varint64(pb_istream_t *stream, uint64_t *dest) bool pb_skip_varint(pb_istream_t *stream) { - char byte; + uint8_t byte; do { if (!pb_read(stream, &byte, 1)) @@ -91,72 +93,235 @@ bool pb_skip_string(pb_istream_t *stream) return pb_read(stream, NULL, length); } -bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest) +/* Currently all wire type related stuff is kept hidden from + * callbacks. They shouldn't need it. It's better for performance + * to just assume the correct type and fail safely on corrupt message. + */ + +enum wire_type_t { + WT_VARINT = 0, + WT_64BIT = 1, + WT_STRING = 2, + WT_32BIT = 5 +}; + +static bool skip(pb_istream_t *stream, int wire_type) { - while (stream->bytes_left) + switch (wire_type) { - uint32_t temp; - if (!pb_decode_varint32(stream, &temp)) - return false; + case WT_VARINT: return pb_skip_varint(stream); + case WT_64BIT: return pb_read(stream, NULL, 8); + case WT_STRING: return pb_skip_string(stream); + case WT_32BIT: return pb_read(stream, NULL, 4); + default: return false; + } +} + +// Read a raw value to buffer, for the purpose of passing it to callback. +// Size is maximum size on call, and actual size on return. +static bool read_raw_value(pb_istream_t *stream, int wire_type, uint8_t *buf, size_t *size) +{ + size_t max_size = *size; + switch (wire_type) + { + case WT_VARINT: + *size = 0; + do + { + (*size)++; + if (*size > max_size) return false; + if (!pb_read(stream, buf++, 1)) return false; + } while (*buf & 0x80); + return true; + + case WT_64BIT: + *size = 8; + return pb_read(stream, buf, 8); - int field_number = temp >> 3; - int wire_type = temp & 7; + case WT_32BIT: + *size = 4; + return pb_read(stream, buf, 4); - const pb_field_t *field = fields; - while (field->field_number != 0) - { - if (field->field_number != field_number) + default: return false; + } +} + +// Decode string length from stream and return a substream with limited length +static bool make_string_substream(pb_istream_t *stream, pb_istream_t *substream) +{ + uint32_t size; + if (!pb_decode_varint32(stream, &size)) + return false; + + *substream = *stream; + if (substream->bytes_left < size) + return false; + + substream->bytes_left = size; + stream->bytes_left -= size; + return true; +} + +bool decode_field(pb_istream_t *stream, int wire_type, const pb_field_t *field, void *dest_struct) +{ + pb_decoder_t func = PB_DECODERS[PB_LTYPE(field->type)]; + void *pData = (char*)dest_struct + field->data_offset; + void *pSize = (char*)dest_struct + field->size_offset; + + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_REQUIRED: + return func(stream, field, pData); + + case PB_HTYPE_OPTIONAL: + *(bool*)pSize = true; + return func(stream, field, pData); + + case PB_HTYPE_ARRAY: + if (wire_type == WT_STRING + && PB_LTYPE(field->type) != PB_LTYPE_BYTES + && PB_LTYPE(field->type) != PB_LTYPE_STRING + && PB_LTYPE(field->type) != PB_LTYPE_SUBMESSAGE) { - field++; - continue; + // Packed array + size_t *size = (size_t*)pSize; + pb_istream_t substream; + if (!make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left && *size < field->array_size) + { + void *pItem = pData + field->data_size * (*size); + if (!func(stream, field, pItem)) + return false; + (*size)++; + } + return (substream.bytes_left == 0); } - - void *destfield = dest + field->offset; - - if (field->action == PB_ACT_HAS) + else { - *(bool*)destfield = true; - field++; - continue; + // Repeated field + size_t *size = (size_t*)pSize; + if (*size >= field->array_size) + return false; + + void *pItem = pData + field->data_size * (*size); + (*size)++; + return func(stream, field, pItem); } - - pb_decoder_t func = PB_DECODERS[field->action]; - if (!func(stream, field, destfield)) - return false; - - break; - } - if (field->field_number == 0) // No match found, skip data - { - bool status = false; - switch (wire_type) + case PB_HTYPE_CALLBACK: + if (wire_type == WT_STRING) { - case WT_VARINT: - status = pb_skip_varint(stream); - break; + pb_callback_t *pCallback = (pb_callback_t*)pData; + pb_istream_t substream; - case WT_64BIT: - status = pb_read(stream, NULL, 8); - break; + if (!make_string_substream(stream, &substream)) + return false; - case WT_STRING: - status = pb_skip_string(stream); - break; + while (substream.bytes_left) + { + if (!pCallback->funcs.decode(&substream, field, pCallback->arg)) + return false; + } + } + else + { + // Copy the single scalar value to stack. + // This is required so that we can limit the stream length, + // which in turn allows to use same callback for packed and + // not-packed fields. + uint8_t buffer[10]; + size_t size = sizeof(buffer); + if (!read_raw_value(stream, wire_type, buffer, &size)) + return false; + pb_istream_t substream = pb_istream_from_buffer(buffer, size); - case WT_32BIT: - status = pb_read(stream, NULL, 4); - break; + pb_callback_t *pCallback = (pb_callback_t*)pData; + return pCallback->funcs.decode(&substream, field, pCallback->arg); } - if (!status) - return false; + default: + return false; + } +} + +bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) +{ + // Used to check for required fields + uint32_t fields_seen = 0; + int i; + + // Initialize size/has fields and apply default values + for (i = 0; fields[i].tag != 0; i++) + { + void *pData = (char*)dest_struct + fields[i].data_offset; + void *pSize = (char*)dest_struct + fields[i].size_offset; + if (PB_HTYPE(fields[i].type) == PB_HTYPE_OPTIONAL) + { + *(bool*)pSize = false; + } + else if (PB_HTYPE(fields[i].type) == PB_HTYPE_ARRAY) + { + *(size_t*)pSize = 0; + } + + if (PB_HTYPE(fields[i].type) != PB_HTYPE_ARRAY && + PB_HTYPE(fields[i].type) != PB_HTYPE_CALLBACK) + { + if (fields[i].ptr != NULL) + { + memcpy(pData, fields[i].ptr, fields[i].data_size); + } + else + { + memset(pData, 0, fields[i].data_size); + } + } + } + + while (stream->bytes_left) + { + uint32_t temp; + if (!pb_decode_varint32(stream, &temp)) + return false; + + int tag = temp >> 3; + int wire_type = temp & 7; + + i = 0; + while (fields[i].tag != 0 && fields[i].tag != tag) + { + i++; + } + + if (fields[i].tag == 0) // No match found, skip data + { + skip(stream, wire_type); + continue; + } + + fields_seen |= 1 << (i & 31); + + if (!decode_field(stream, wire_type, &fields[i], dest_struct)) + return false; + } + + // Check that all required fields (mod 31) were present. + for (i = 0; fields[i].tag != 0; i++) + { + if (PB_HTYPE(fields[i].type) == PB_HTYPE_REQUIRED && + !(fields_seen & (1 << (i & 31)))) + { + return false; } } return true; } +/* Field decoders */ + bool pb_dec_uint32(pb_istream_t *stream, const pb_field_t *field, void *dest) { return pb_decode_varint32(stream, (uint32_t*)dest); @@ -172,11 +337,15 @@ bool pb_dec_sint32(pb_istream_t *stream, const pb_field_t *field, void *dest) bool pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest) { - char bytes[4] = {0}; + uint8_t bytes[4] = {0}; bool status = pb_read(stream, bytes, 4); - *(uint32_t*)dest = - bytes[0] | ((uint32_t)bytes[1] << 8) | - ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24); + +#ifdef __BIG_ENDIAN__ + uint8_t lebytes[4] = {bytes[3], bytes[2], bytes[1], bytes[0]}; + memcpy(dest, lebytes, 4); +#else + memcpy(dest, bytes, 4); +#endif return status; } @@ -195,13 +364,16 @@ bool pb_dec_sint64(pb_istream_t *stream, const pb_field_t *field, void *dest) bool pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest) { - char bytes[8] = {0}; + uint8_t bytes[8] = {0}; bool status = pb_read(stream, bytes, 8); - *(uint64_t*)dest = - (uint64_t)bytes[0] | ((uint64_t)bytes[1] << 8) | - ((uint64_t)bytes[2] << 16) | ((uint64_t)bytes[3] << 24) | - ((uint64_t)bytes[4] << 32) | ((uint64_t)bytes[5] << 40) | - ((uint64_t)bytes[6] << 48) | ((uint64_t)bytes[7] << 56); + +#ifdef __BIG_ENDIAN__ + uint8_t lebytes[8] = {bytes[7], bytes[6], bytes[5], bytes[4], + bytes[3], bytes[2], bytes[1], bytes[0]}; + memcpy(dest, lebytes, 4); +#else + memcpy(dest, bytes, 4); +#endif return status; } @@ -213,25 +385,37 @@ bool pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest) return status; } +bool pb_dec_enum(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + // Enum sizes can vary, copy only data_size amount of bytes. + uint32_t temp = 0; + bool status = pb_decode_varint32(stream, &temp); + memcpy(dest, &temp, field->data_size); + return status; +} + bool pb_dec_float(pb_istream_t *stream, const pb_field_t *field, void *dest) { - return pb_read(stream, (char*)dest, sizeof(float)); + return pb_read(stream, (uint8_t*)dest, sizeof(float)); } bool pb_dec_double(pb_istream_t *stream, const pb_field_t *field, void *dest) { - return pb_read(stream, (char*)dest, sizeof(double)); + return pb_read(stream, (uint8_t*)dest, sizeof(double)); } bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest) { - pb_bytearray_t *x = (pb_bytearray_t*)dest; + pb_bytes_array_t *x = (pb_bytes_array_t*)dest; + uint32_t temp; if (!pb_decode_varint32(stream, &temp)) return false; x->size = temp; - if (x->size > field->fieldsize) + // Note: data_size includes the size of the x.size field, too. + // Calculate actual size starting from offset. + if (x->size > field->data_size - offsetof(pb_bytes_array_t, bytes)) return false; return pb_read(stream, x->bytes, x->size); @@ -243,32 +427,23 @@ bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest) if (!pb_decode_varint32(stream, &size)) return false; - if (size > field->fieldsize - 1) + if (size > field->data_size - 1) return false; - bool status = pb_read(stream, (char*)dest, size); - *((char*)dest + size) = 0; + bool status = pb_read(stream, (uint8_t*)dest, size); + *((uint8_t*)dest + size) = 0; return status; } bool pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest) { - pb_callback_t *x = (pb_callback_t*)dest; + pb_istream_t substream; - if (x->funcs.decode == NULL) - return pb_skip_string(stream); - - uint32_t size; - if (!pb_decode_varint32(stream, &size)) + if (!make_string_substream(stream, &substream)) return false; - if (stream->bytes_left < size) + if (field->ptr == NULL) return false; - // Make a limited-length istream for decoding submessage - pb_istream_t shortstream = *stream; - shortstream.bytes_left = size; - bool status = x->funcs.decode(&shortstream, field, x->arg); - stream->bytes_left -= size - shortstream.bytes_left; - return status; + return pb_decode(&substream, (pb_field_t*)field->ptr, dest); } diff --git a/pb_decode.h b/pb_decode.h index b71e20b1..2f689394 100644 --- a/pb_decode.h +++ b/pb_decode.h @@ -6,12 +6,14 @@ // Decode from stream to destination struct. // The actual struct pointed to by dest must match the description in fields. -bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest); +bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* --- Helper functions --- - * You may want to use these from your callbacks. + * You may want to use these from your caller or callbacks. */ +pb_istream_t pb_istream_from_buffer(uint8_t *buf, size_t bufsize); + bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); bool pb_decode_varint64(pb_istream_t *stream, uint64_t *dest); @@ -20,10 +22,10 @@ bool pb_skip_string(pb_istream_t *stream); /* --- Field decoders --- * Each decoder takes stream and field description, and a pointer to the field - * in the destination struct (dest = struct_addr + field->offset). + * in the destination struct (dest = struct_addr + field->data_offset). + * For arrays, these functions are called repeatedly. */ -// Integer types. bool pb_dec_uint32(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_sint32(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest); @@ -31,25 +33,20 @@ bool pb_dec_uint64(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_sint64(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest); +bool pb_dec_enum(pb_istream_t *stream, const pb_field_t *field, void *dest); -// Floating point types. Info is ignored. bool pb_dec_float(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_double(pb_istream_t *stream, const pb_field_t *field, void *dest); -// Byte array. Dest is pointer to bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest); - -// Null-terminated string. bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest); - -// Use callback. Dest is pointer to pb_callback_t struct. bool pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest); typedef bool (*pb_decoder_t)(pb_istream_t *stream, const pb_field_t *field, void *dest); /* --- Function pointers to field decoders --- - * Order in the array must match pb_action_t numbering. + * Order in the array must match pb_action_t LTYPE numbering. */ -const pb_decoder_t PB_DECODERS[PB_LAST_ACT]; +const pb_decoder_t PB_DECODERS[16]; #endif diff --git a/tests/test_decode1.c b/tests/test_decode1.c index dc0831a2..fbdb7db6 100644 --- a/tests/test_decode1.c +++ b/tests/test_decode1.c @@ -9,8 +9,6 @@ typedef enum { Person_PhoneType_MOBILE = 0, Person_PhoneType_HOME = 1, Person_PhoneType_WORK = 2, - - _Person_PhoneType_size = 0xFFFFFFFF // Force 32-bit enum } Person_PhoneType; typedef struct { @@ -24,52 +22,71 @@ typedef struct { int32_t id; bool has_email; char email[40]; - - pb_callback_t phone; + size_t phone_size; + Person_PhoneNumber phone[5]; } Person; /* Field descriptions */ #define membersize(st, m) (sizeof ((st*)0)->m) +const Person_PhoneType Person_PhoneType_type_default = Person_PhoneType_HOME; + const pb_field_t Person_PhoneNumber_fields[] = { - {1, offsetof(Person_PhoneNumber, number), PB_ACT_STRING, membersize(Person_PhoneNumber, number)}, - {2, offsetof(Person_PhoneNumber, has_type), PB_ACT_HAS, membersize(Person_PhoneNumber, has_type)}, - {2, offsetof(Person_PhoneNumber, type), PB_ACT_UINT32, membersize(Person_PhoneNumber, type)}, + {1, PB_HTYPE_REQUIRED | PB_LTYPE_STRING, + offsetof(Person_PhoneNumber, number), 0, + membersize(Person_PhoneNumber, number), 0, 0}, + + {2, PB_HTYPE_OPTIONAL | PB_LTYPE_ENUM, + offsetof(Person_PhoneNumber, type), + offsetof(Person_PhoneNumber, has_type), + membersize(Person_PhoneNumber, type), 0, + &Person_PhoneType_type_default}, + PB_LAST_FIELD }; const pb_field_t Person_fields[] = { - {1, offsetof(Person, name), PB_ACT_STRING, membersize(Person, name)}, - {2, offsetof(Person, id), PB_ACT_INT32, membersize(Person, id)}, - {3, offsetof(Person, email), PB_ACT_STRING, membersize(Person, email)}, - {4, offsetof(Person, phone), PB_ACT_SUBMESSAGE, membersize(Person, phone)} + {1, PB_HTYPE_REQUIRED | PB_LTYPE_STRING, + offsetof(Person, name), 0, + membersize(Person, name), 0, 0}, + + {2, PB_HTYPE_REQUIRED | PB_LTYPE_INT32, + offsetof(Person, id), 0, + membersize(Person, id), 0, 0}, + + {3, PB_HTYPE_OPTIONAL | PB_LTYPE_STRING, + offsetof(Person, email), + offsetof(Person, has_email), + membersize(Person, email), 0, 0}, + + {4, PB_HTYPE_ARRAY | PB_LTYPE_SUBMESSAGE, + offsetof(Person, phone), + offsetof(Person, phone_size), + membersize(Person, phone[0]), + membersize(Person, phone) / membersize(Person, phone[0]), + Person_PhoneNumber_fields}, + + PB_LAST_FIELD }; -/* Default value descriptions */ -#define Person_PhoneNumber_default {"", false, Person_PhoneType_HOME}; -#define Person_default {"", 0, false, "", {{0},0}}; - /* And now, the actual test program */ -bool print_phonenumber(pb_istream_t *stream, const pb_field_t *field, void *arg) -{ - Person_PhoneNumber x = Person_PhoneNumber_default; - if (!pb_decode(stream, Person_PhoneNumber_fields, &x)) - return false; - - printf("PhoneNumber: number '%s' type '%d'\n", x.number, x.type); - return true; -} - bool print_person(pb_istream_t *stream) { - Person x = Person_default; - x.phone.funcs.decode = &print_phonenumber; + int i; + Person person; - if (!pb_decode(stream, Person_fields, &x)) + if (!pb_decode(stream, Person_fields, &person)) return false; - printf("Person: name '%s' id '%d' email '%s'\n", x.name, x.id, x.email); + printf("Person: name '%s' id '%d' email '%s'\n", person.name, person.id, person.email); + + for (i = 0; i < person.phone_size; i++) + { + Person_PhoneNumber *phone = &person.phone[i]; + printf("PhoneNumber: number '%s' type '%d'\n", phone->number, phone->type); + } + return true; } @@ -91,10 +108,10 @@ bool my_read(pb_istream_t *stream, char *buf, size_t count) int main() { - char buffer[512]; + uint8_t buffer[512]; size_t size = fread(buffer, 1, 512, stdin); - pb_istream_t stream = {&my_read, buffer, size}; + pb_istream_t stream = pb_istream_from_buffer(buffer, size); if (!print_person(&stream)) printf("Parsing failed.\n"); |