From 14bbe229974b516a82594ec6476488154e8957fa Mon Sep 17 00:00:00 2001 From: Petteri Aimonen Date: Mon, 25 Jul 2011 20:42:48 +0000 Subject: [PATCH] First version of decoding git-svn-id: https://svn.kapsi.fi/jpa/nanopb@942 e3a754e5-d11d-0410-8d38-ebb782a927b9 --- pb.h | 107 ++++++++++++++++++++ pb_decode.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++ pb_decode.h | 55 +++++++++++ tests/person.proto | 18 ++++ tests/test_decode1.c | 102 +++++++++++++++++++ tests/testperson.pb | 3 + 6 files changed, 559 insertions(+) create mode 100644 pb.h create mode 100644 pb_decode.c create mode 100644 pb_decode.h create mode 100644 tests/person.proto create mode 100644 tests/test_decode1.c create mode 100644 tests/testperson.pb diff --git a/pb.h b/pb.h new file mode 100644 index 00000000..2cae75c5 --- /dev/null +++ b/pb.h @@ -0,0 +1,107 @@ +#ifndef _PB_H_ +#define _PB_H_ + +#include +#include // size_t + +/* Lightweight input stream. + * If buf is NULL, read but don't store bytes. */ +typedef struct _pb_istream_t pb_istream_t; +struct _pb_istream_t +{ + bool (*callback)(pb_istream_t *stream, char *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) +{ + bool status = stream->callback(stream, buf, count); + stream->bytes_left -= count; + return status; +} + +/* Lightweight output stream. */ +typedef struct _pb_ostream_t pb_ostream_t; +struct _pb_ostream_t +{ + bool (*callback)(pb_ostream_t *stream, const char *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) +{ + bool status = stream->callback(stream, buf, count); + stream->bytes_written += count; + return status; +} + +/* List of possible decode/encode action types */ + +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, + + // Standard float types + PB_ACT_FLOAT, + PB_ACT_DOUBLE, + + // Constant-sized array + PB_ACT_BYTES, + + // Constant-sized array, with null termination + PB_ACT_STRING, + + // Callback function pointer in field value + PB_ACT_SUBMESSAGE, + + PB_LAST_ACT +} pb_action_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_LAST_FIELD {0,0,0,0} + +/* --- Types to use inside generated structures. --- */ + +// 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; + +// This structure is used for giving the callback function. +typedef struct _pb_callback_t pb_callback_t; +struct _pb_callback_t { + union { + bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); + bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void *arg); + } funcs; + + // Free arg for use by callback + void *arg; +}; + +#endif \ No newline at end of file diff --git a/pb_decode.c b/pb_decode.c new file mode 100644 index 00000000..884cf18e --- /dev/null +++ b/pb_decode.c @@ -0,0 +1,274 @@ +/* pb_decode.c -- decode a protobuf using callback functions + * + * 2011 Petteri Aimonen + */ + +#include "pb_decode.h" + +const pb_decoder_t PB_DECODERS[PB_LAST_ACT] = { + NULL, + &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_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 +}; + +// 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) +{ + char byte; + int bitpos = 0; + *dest = 0; + + while (bitpos < 32 && pb_read(stream, &byte, 1)) + { + *dest |= (byte & 0x7F) << bitpos; + bitpos += 7; + + if (!(byte & 0x80)) + return true; + } + + return false; +} + +bool pb_decode_varint64(pb_istream_t *stream, uint64_t *dest) +{ + char byte; + int bitpos = 0; + *dest = 0; + + while (bitpos < 64 && pb_read(stream, &byte, 1)) + { + *dest |= (byte & 0x7F) << bitpos; + bitpos += 7; + + if (!(byte & 0x80)) + return true; + } + + return false; +} + +bool pb_skip_varint(pb_istream_t *stream) +{ + char byte; + do + { + if (!pb_read(stream, &byte, 1)) + return false; + } while (byte & 0x80); + return true; +} + +bool pb_skip_string(pb_istream_t *stream) +{ + uint32_t length; + if (!pb_decode_varint32(stream, &length)) + return false; + + return pb_read(stream, NULL, length); +} + +bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest) +{ + while (stream->bytes_left) + { + uint32_t temp; + if (!pb_decode_varint32(stream, &temp)) + return false; + + int field_number = temp >> 3; + int wire_type = temp & 7; + + const pb_field_t *field = fields; + while (field->field_number != 0) + { + if (field->field_number != field_number) + { + field++; + continue; + } + + void *destfield = dest + field->offset; + + if (field->action == PB_ACT_HAS) + { + *(bool*)destfield = true; + field++; + continue; + } + + 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 WT_VARINT: + status = pb_skip_varint(stream); + break; + + case WT_64BIT: + status = pb_read(stream, NULL, 8); + break; + + case WT_STRING: + status = pb_skip_string(stream); + break; + + case WT_32BIT: + status = pb_read(stream, NULL, 4); + break; + } + + if (!status) + return false; + } + } + + return true; +} + +bool pb_dec_uint32(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + return pb_decode_varint32(stream, (uint32_t*)dest); +} + +bool pb_dec_sint32(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint32_t *x = (uint32_t*)dest; + bool status = pb_decode_varint32(stream, x); + *x = (*x >> 1) ^ -(int32_t)(*x & 1); + return status; +} + +bool pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + char 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); + return status; +} + +bool pb_dec_uint64(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + return pb_decode_varint64(stream, (uint64_t*)dest); +} + +bool pb_dec_sint64(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint64_t *x = (uint64_t*)dest; + bool status = pb_decode_varint64(stream, x); + *x = (*x >> 1) ^ -(int64_t)(*x & 1); + return status; +} + +bool pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + char 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); + return status; +} + +bool pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint32_t temp = 0; + bool status = pb_decode_varint32(stream, &temp); + *(bool*)dest = !!temp; + 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)); +} + +bool pb_dec_double(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + return pb_read(stream, (char*)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; + uint32_t temp; + if (!pb_decode_varint32(stream, &temp)) + return false; + x->size = temp; + + if (x->size > field->fieldsize) + return false; + + return pb_read(stream, x->bytes, x->size); +} + +bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint32_t size; + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size > field->fieldsize - 1) + return false; + + bool status = pb_read(stream, (char*)dest, size); + *((char*)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; + + if (x->funcs.decode == NULL) + return pb_skip_string(stream); + + uint32_t size; + if (!pb_decode_varint32(stream, &size)) + return false; + + if (stream->bytes_left < size) + 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; +} diff --git a/pb_decode.h b/pb_decode.h new file mode 100644 index 00000000..b71e20b1 --- /dev/null +++ b/pb_decode.h @@ -0,0 +1,55 @@ +#ifndef _PB_DECODE_H_ +#define _PB_DECODE_H_ + +#include +#include "pb.h" + +// 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); + +/* --- Helper functions --- + * You may want to use these from your callbacks. + */ + +bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); +bool pb_decode_varint64(pb_istream_t *stream, uint64_t *dest); + +bool pb_skip_varint(pb_istream_t *stream); +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). + */ + +// 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); +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); + +// 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. + */ +const pb_decoder_t PB_DECODERS[PB_LAST_ACT]; + +#endif diff --git a/tests/person.proto b/tests/person.proto new file mode 100644 index 00000000..ec5b4cca --- /dev/null +++ b/tests/person.proto @@ -0,0 +1,18 @@ +message Person { + required string name = 1; + required int32 id = 2; + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + required string number = 1; + optional PhoneType type = 2 [default = HOME]; + } + + repeated PhoneNumber phone = 4; +} diff --git a/tests/test_decode1.c b/tests/test_decode1.c new file mode 100644 index 00000000..dc0831a2 --- /dev/null +++ b/tests/test_decode1.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include "pb_decode.h" + +/* Structures for "Person" message */ + +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 { + char number[40]; + bool has_type; + Person_PhoneType type; +} Person_PhoneNumber; + +typedef struct { + char name[40]; + int32_t id; + bool has_email; + char email[40]; + + pb_callback_t phone; +} Person; + +/* Field descriptions */ +#define membersize(st, m) (sizeof ((st*)0)->m) + +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)}, + 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)} +}; + +/* 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; + + if (!pb_decode(stream, Person_fields, &x)) + return false; + + printf("Person: name '%s' id '%d' email '%s'\n", x.name, x.id, x.email); + return true; +} + +bool my_read(pb_istream_t *stream, char *buf, size_t count) +{ + char *source = (char*)stream->state; + + if (!stream->bytes_left) + return false; + + if (buf != NULL) + { + memcpy(buf, source, count); + } + + stream->state = source + count; + return true; +} + +int main() +{ + char buffer[512]; + size_t size = fread(buffer, 1, 512, stdin); + + pb_istream_t stream = {&my_read, buffer, size}; + if (!print_person(&stream)) + printf("Parsing failed.\n"); + + return 0; +} diff --git a/tests/testperson.pb b/tests/testperson.pb new file mode 100644 index 00000000..d1eb8cfb --- /dev/null +++ b/tests/testperson.pb @@ -0,0 +1,3 @@ + + Test Person7foobar@foobar.com" + 555-12345678 \ No newline at end of file -- 2.16.6