From a8d0172507d59b73b95f766aa7644147fd060f20 Mon Sep 17 00:00:00 2001 From: Petteri Aimonen Date: Thu, 4 Aug 2011 16:49:32 +0000 Subject: Encoder git-svn-id: https://svn.kapsi.fi/jpa/nanopb@951 e3a754e5-d11d-0410-8d38-ebb782a927b9 --- generator/nanopb_generator.py | 118 +++++++++++---- pb.h | 43 +++--- pb_decode.c | 57 ++++---- pb_decode.h | 36 +++-- pb_encode.c | 326 ++++++++++++++++++++++++++++++++++++++++++ pb_encode.h | 69 +++++++++ tests/Makefile | 8 +- tests/person.proto | 2 +- tests/test_decode1.c | 5 +- tests/test_encode1.c | 23 +++ 10 files changed, 583 insertions(+), 104 deletions(-) create mode 100644 pb_encode.c create mode 100644 pb_encode.h create mode 100644 tests/test_encode1.c diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py index b182d01f..6aff3155 100644 --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -121,7 +121,7 @@ class Field: if self.max_size is None: is_callback = True else: - self.ctype = 'PB_BYTES_ARRAY(%d)' % self.max_size + self.ctype = self.struct_name + self.name + 't' elif desc.type == FieldD.TYPE_MESSAGE: self.ltype = 'PB_LTYPE_SUBMESSAGE' self.ctype = names_from_type_name(desc.type_name) @@ -146,7 +146,18 @@ class Field: result += ' %s %s%s;' % (self.ctype, self.name, self.array_decl) return result - def default_decl(self): + def types(self): + '''Return definitions for any special types this field might need.''' + if self.ltype == 'PB_LTYPE_BYTES' and self.max_size is not None: + result = 'typedef struct {\n' + result += ' size_t size;\n' + result += ' uint8_t bytes[%d];\n' % self.max_size + result += '} %s;\n' % self.ctype + else: + result = None + return result + + def default_decl(self, declaration_only = False): '''Return definition for this field's default value.''' if self.default is None: return None @@ -154,7 +165,7 @@ class Field: if self.ltype == 'PB_LTYPE_STRING': ctype = 'char' if self.max_size is None: - array_decl = '[]' + return None # Not implemented else: array_decl = '[%d]' % self.max_size default = self.default.encode('string_escape') @@ -165,9 +176,9 @@ class Field: data = ['0x%02x' % ord(c) for c in data] if self.max_size is None: - ctype = 'PB_BYTES_ARRAY(%d)' % len(data) + return None # Not implemented else: - ctype = 'PB_BYTES_ARRAY(%d)' % self.max_size + ctype = self.ctype default = '{%d, {%s}}' % (len(data), ','.join(data)) array_decl = '' @@ -175,7 +186,10 @@ class Field: ctype, default = self.ctype, self.default array_decl = '' - return 'const %s %s_default%s = %s;' % (ctype, self.struct_name + self.name, array_decl, default) + if declaration_only: + return 'extern const %s %s_default%s;' % (ctype, self.struct_name + self.name, array_decl) + else: + return 'const %s %s_default%s = %s;' % (ctype, self.struct_name + self.name, array_decl, default) def pb_field_t(self, prev_field_name): '''Return the pb_field_t initializer to use in the constant array. @@ -244,20 +258,32 @@ class Message: def __str__(self): result = 'typedef struct {\n' - result += '\n'.join([str(f) for f in self.fields]) + result += '\n'.join([str(f) for f in self.ordered_fields]) result += '\n} %s;' % self.name return result - def default_decl(self): + def types(self): + result = "" + for field in self.fields: + types = field.types() + if types is not None: + result += types + '\n' + return result + + def default_decl(self, declaration_only = False): result = "" for field in self.fields: - default = field.default_decl() + default = field.default_decl(declaration_only) if default is not None: result += default + '\n' return result - def pb_field_t(self): - result = 'const pb_field_t %s_fields[] = {\n' % self.name + def fields_declaration(self): + result = 'extern const pb_field_t %s_fields[%d];' % (self.name, len(self.fields)) + return result + + def fields_definition(self): + result = 'const pb_field_t %s_fields[%d] = {\n' % (self.name, len(self.fields)) prev = None for field in self.ordered_fields: @@ -282,13 +308,8 @@ def iterate_messages(desc, names = Names()): for x in iterate_messages(submsg, sub_names): yield x -def process_file(fdesc): - '''Takes a FileDescriptorProto and generate content for its header file. - Generates strings, which should be concatenated and stored to file. - ''' - - yield '/* Automatically generated nanopb header */\n' - yield '#include \n\n' +def parse_file(fdesc): + '''Takes a FileDescriptorProto and returns tuple (enum, messages).''' enums = [] messages = [] @@ -297,10 +318,23 @@ def process_file(fdesc): enums.append(Enum(Names(), enum)) for names, message in iterate_messages(fdesc): + messages.append(Message(names, message)) for enum in message.enum_type: enums.append(Enum(names, enum)) - - messages.append(Message(names, message)) + + return enums, messages + +def generate_header(headername, enums, messages): + '''Generate content for a header file. + Generates strings, which should be concatenated and stored to file. + ''' + + yield '/* Automatically generated nanopb header */\n' + + symbol = headername.replace('.', '_').upper() + yield '#ifndef _PB_%s_\n' % symbol + yield '#define _PB_%s_\n' % symbol + yield '#include \n\n' yield '/* Enum definitions */\n' for enum in enums: @@ -309,17 +343,34 @@ def process_file(fdesc): yield '/* Struct definitions */\n' messages.sort() for msg in messages: + yield msg.types() yield str(msg) + '\n\n' yield '/* Default values for struct fields */\n' for msg in messages: - yield msg.default_decl() + yield msg.default_decl(True) yield '\n' yield '/* Struct field encoding specification for nanopb */\n' for msg in messages: - yield msg.pb_field_t() + '\n\n' + yield msg.fields_declaration() + '\n' + + yield '\n#endif\n' + +def generate_source(headername, enums, messages): + '''Generate content for a source file.''' + + yield '/* Automatically generated nanopb constant definitions */\n' + yield '#include "%s"\n\n' % headername + + for msg in messages: + yield msg.default_decl(False) + yield '\n\n' + + for msg in messages: + yield msg.fields_definition() + '\n\n' + if __name__ == '__main__': import sys import os.path @@ -328,17 +379,26 @@ if __name__ == '__main__': print "Usage: " + sys.argv[0] + " file.pb" print "where file.pb has been compiled from .proto by:" print "protoc -ofile.pb file.proto" - print "Output fill be written to file.h" + print "Output fill be written to file.h and file.c" sys.exit(1) data = open(sys.argv[1]).read() fdesc = descriptor.FileDescriptorSet.FromString(data) + enums, messages = parse_file(fdesc.file[0]) - destfile = os.path.splitext(sys.argv[1])[0] + '.h' - - print "Writing to " + destfile + noext = os.path.splitext(sys.argv[1])[0] + headername = noext + '.h' + sourcename = noext + '.c' + headerbasename = os.path.basename(headername) - destfile = open(destfile, 'w') + print "Writing to " + headername + " and " + sourcename - for part in process_file(fdesc.file[0]): - destfile.write(part) + header = open(headername, 'w') + for part in generate_header(headerbasename, enums, messages): + header.write(part) + + source = open(sourcename, 'w') + for part in generate_source(headerbasename, enums, messages): + source.write(part) + + \ No newline at end of file diff --git a/pb.h b/pb.h index 27ee11fd..46d7c2ce 100644 --- a/pb.h +++ b/pb.h @@ -12,21 +12,13 @@ #define pb_packed #endif -/* Lightweight output stream. */ -typedef struct _pb_ostream_t pb_ostream_t; -struct _pb_ostream_t -{ - 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 uint8_t *buf, size_t count) -{ - bool status = stream->callback(stream, buf, count); - stream->bytes_written += count; - return status; -}*/ +/* Wire types */ +typedef enum { + PB_WT_VARINT = 0, + PB_WT_64BIT = 1, + PB_WT_STRING = 2, + PB_WT_32BIT = 5 +} pb_wire_type_t; /* List of possible field types * Least-significant 4 bits tell the scalar type @@ -83,7 +75,8 @@ typedef enum { /* Works for all required/optional/repeated fields. * data_offset points to pb_callback_t structure. - * LTYPE is ignored. */ + * LTYPE should be 0 (it is ignored, but sometimes + * used to speculatively index an array). */ PB_HTYPE_CALLBACK = 0x30 } pb_packed pb_type_t; @@ -113,14 +106,13 @@ struct _pb_field_t { } pb_packed; /* 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]; \ -} - -typedef PB_BYTES_ARRAY() pb_bytes_array_t; + * It has the number of bytes in the beginning, and after that an array. + * Note that actual structs used will have a different length of bytes array. + */ +typedef struct { + size_t size; + uint8_t bytes[1]; +} pb_bytes_array_t; /* This structure is used for giving the callback function. * It is stored in the message structure and filled in by the method that @@ -139,11 +131,12 @@ typedef PB_BYTES_ARRAY() pb_bytes_array_t; * wire type. It can write multiple fields. */ typedef struct _pb_istream_t pb_istream_t; +typedef struct _pb_ostream_t pb_ostream_t; 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); + bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); } funcs; /* Free arg for use by callback */ diff --git a/pb_decode.c b/pb_decode.c index 63008dd5..df053314 100644 --- a/pb_decode.c +++ b/pb_decode.c @@ -7,14 +7,19 @@ #include "pb_decode.h" #include -const pb_decoder_t PB_DECODERS[PB_LTYPES_COUNT] = { - (pb_decoder_t)&pb_dec_varint, - (pb_decoder_t)&pb_dec_svarint, - (pb_decoder_t)&pb_dec_fixed, +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 LTYPE numbering. + */ +static const pb_decoder_t PB_DECODERS[PB_LTYPES_COUNT] = { + &pb_dec_varint, + &pb_dec_svarint, + &pb_dec_fixed, - (pb_decoder_t)&pb_dec_bytes, - (pb_decoder_t)&pb_dec_string, - (pb_decoder_t)&pb_dec_submessage + &pb_dec_bytes, + &pb_dec_string, + &pb_dec_submessage }; /************** @@ -108,33 +113,27 @@ bool pb_skip_string(pb_istream_t *stream) * 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) { switch (wire_type) { - 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); + case PB_WT_VARINT: return pb_skip_varint(stream); + case PB_WT_64BIT: return pb_read(stream, NULL, 8); + case PB_WT_STRING: return pb_skip_string(stream); + case PB_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) +/* Read a raw value to buffer, for the purpose of passing it to callback as + * a substream. Size is maximum size on call, and actual size on return. + */ +static bool read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, uint8_t *buf, size_t *size) { size_t max_size = *size; switch (wire_type) { - case WT_VARINT: + case PB_WT_VARINT: *size = 0; do { @@ -144,11 +143,11 @@ static bool read_raw_value(pb_istream_t *stream, int wire_type, uint8_t *buf, si } while (*buf++ & 0x80); return true; - case WT_64BIT: + case PB_WT_64BIT: *size = 8; return pb_read(stream, buf, 8); - case WT_32BIT: + case PB_WT_32BIT: *size = 4; return pb_read(stream, buf, 4); @@ -239,7 +238,7 @@ bool decode_field(pb_istream_t *stream, int wire_type, pb_field_iterator_t *iter return func(stream, iter->current, iter->pData); case PB_HTYPE_ARRAY: - if (wire_type == WT_STRING + if (wire_type == PB_WT_STRING && PB_LTYPE(iter->current->type) <= PB_LTYPE_LAST_PACKABLE) { /* Packed array */ @@ -270,7 +269,7 @@ bool decode_field(pb_istream_t *stream, int wire_type, pb_field_iterator_t *iter } case PB_HTYPE_CALLBACK: - if (wire_type == WT_STRING) + if (wire_type == PB_WT_STRING) { pb_callback_t *pCallback = (pb_callback_t*)iter->pData; pb_istream_t substream; @@ -419,7 +418,7 @@ bool pb_dec_fixed(pb_istream_t *stream, const pb_field_t *field, void *dest) #ifdef __BIG_ENDIAN__ uint8_t bytes[8] = {0}; bool status = pb_read(stream, bytes, field->data_size); - uint8_t lebytes[8] = {bytes[7], bytes[6], bytes[5], bytes[4], + uint8_t bebytes[8] = {bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]}; endian_copy(dest, lebytes, field->data_size, 8); return status; @@ -428,7 +427,7 @@ bool pb_dec_fixed(pb_istream_t *stream, const pb_field_t *field, void *dest) #endif } -bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, uint8_t *dest) +bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest) { pb_bytes_array_t *x = (pb_bytes_array_t*)dest; @@ -445,7 +444,7 @@ bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, uint8_t *dest) return pb_read(stream, x->bytes, x->size); } -bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, uint8_t *dest) +bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest) { uint32_t size; bool status; diff --git a/pb_decode.h b/pb_decode.h index 448b55db..011efdd9 100644 --- a/pb_decode.h +++ b/pb_decode.h @@ -1,18 +1,32 @@ #ifndef _PB_DECODE_H_ #define _PB_DECODE_H_ +/* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c. + * The main function is pb_decode. You will also need to create an input + * stream, which is easiest to do with pb_istream_t. + * + * You also need structures and their corresponding pb_field_t descriptions. + * These are usually generated from .proto-files with a script. + */ + #include #include "pb.h" /* Lightweight input stream. - * If buf is NULL, read but don't store bytes. - * You can to provide a callback function for reading or use + * You can provide a callback function for reading or use * pb_istream_from_buffer. * - * You can use state to store your own data (e.g. buffer pointer), + * Rules for callback: + * 1) Return false on IO errors. This will cause decoding to abort. + * + * 2) If buf is NULL, read but don't store bytes ("skip input"). + * + * 3) 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. + * + * 4) Your callback may be used with substreams, in which case bytes_left + * is different than from the main stream. Don't use bytes_left to compute + * any pointers. */ struct _pb_istream_t { @@ -25,6 +39,7 @@ pb_istream_t pb_istream_from_buffer(uint8_t *buf, size_t bufsize); bool pb_read(pb_istream_t *stream, uint8_t *buf, size_t count); /* Decode from stream to destination struct. + * Returns true on success, false on any failure. * 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_struct); @@ -49,15 +64,8 @@ bool pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest); bool pb_dec_fixed(pb_istream_t *stream, const pb_field_t *field, void *dest); -bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, uint8_t *dest); -bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, uint8_t *dest); +bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest); +bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest); 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 LTYPE numbering. - */ -const pb_decoder_t PB_DECODERS[PB_LTYPES_COUNT]; - #endif diff --git a/pb_encode.c b/pb_encode.c new file mode 100644 index 00000000..ae185c1a --- /dev/null +++ b/pb_encode.c @@ -0,0 +1,326 @@ +/* pb_encode.c -- encode a protobuf using minimal resources + * + * 2011 Petteri Aimonen + */ + +#include "pb.h" +#include "pb_encode.h" +#include + +typedef bool (*pb_encoder_t)(pb_ostream_t *stream, const pb_field_t *field, const void *src); + +/* --- Function pointers to field encoders --- + * Order in the array must match pb_action_t LTYPE numbering. + */ +static const pb_encoder_t PB_ENCODERS[PB_LTYPES_COUNT] = { + &pb_enc_varint, + &pb_enc_svarint, + &pb_enc_fixed, + + &pb_enc_bytes, + &pb_enc_string, + &pb_enc_submessage +}; + +/* pb_ostream_t implementation */ + +static bool buf_write(pb_ostream_t *stream, const uint8_t *buf, size_t count) +{ + uint8_t *dest = (uint8_t*)stream->state; + memcpy(dest, buf, count); + stream->state = dest + count; + return true; +} + +pb_ostream_t pb_ostream_from_buffer(uint8_t *buf, size_t bufsize) +{ + pb_ostream_t stream; + stream.callback = &buf_write; + stream.state = buf; + stream.max_size = bufsize; + stream.bytes_written = 0; + return stream; +} + +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; + + return stream->callback(stream, buf, count); +} + +/* Main encoding stuff */ + +static bool encode_array(pb_ostream_t *stream, const pb_field_t *field, + const void *pData, size_t count, pb_encoder_t func) +{ + int i; + const void *p; + size_t size; + + if (PB_LTYPE(field->type) < PB_LTYPE_LAST_PACKABLE) + { + if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) + return false; + + /* Determine the total size of packed array. */ + if (PB_LTYPE(field->type) == PB_LTYPE_FIXED) + { + size = field->data_size * count; + } + else + { + pb_ostream_t sizestream = {0}; + p = pData; + for (i = 0; i < count; i++) + { + if (!func(&sizestream, field, p)) + return false; + p = (const char*)p + field->data_size; + } + size = sizestream.bytes_written; + } + + pb_encode_varint(stream, size); + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing.. */ + + /* Write the data */ + p = pData; + for (i = 0; i < count; i++) + { + if (!func(stream, field, p)) + return false; + p = (const char*)p + field->data_size; + } + } + else + { + p = pData; + for (i = 0; i < count; i++) + { + if (!pb_encode_tag_for_field(stream, field)) + return false; + if (!func(stream, field, p)) + return false; + p = (const char*)p + field->data_size; + } + } + + return true; +} + +bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) +{ + const pb_field_t *field = fields; + const void *pData = src_struct; + const void *pSize; + + while (field->tag != 0) + { + pData = (const char*)pData + field->data_offset; + pSize = (const char*)pData + field->size_offset; + + pb_encoder_t func = PB_ENCODERS[PB_LTYPE(field->type)]; + + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_REQUIRED: + if (!pb_encode_tag_for_field(stream, field)) + return false; + if (!func(stream, field, pData)) + return false; + break; + + case PB_HTYPE_OPTIONAL: + if (*(bool*)pSize) + { + if (!pb_encode_tag_for_field(stream, field)) + return false; + + if (!func(stream, field, pData)) + return false; + } + break; + + case PB_HTYPE_ARRAY: + if (!encode_array(stream, field, pData, *(size_t*)pSize, func)) + return false; + break; + + case PB_HTYPE_CALLBACK: + { + pb_callback_t *callback = (pb_callback_t*)pData; + if (callback->funcs.encode != NULL) + { + if (!callback->funcs.encode(stream, field, callback->arg)) + return false; + } + break; + } + } + + field++; + } + + return true; +} + +/* Helper functions */ +bool pb_encode_varint(pb_ostream_t *stream, uint64_t value) +{ + uint8_t buffer[10]; + int i = 0; + + if (value == 0) + return pb_write(stream, (uint8_t*)&value, 1); + + while (value) + { + buffer[i] = (value & 0x7F) | 0x80; + value >>= 7; + i++; + } + buffer[i-1] &= 0x7F; /* Unset top bit on last byte */ + + return pb_write(stream, buffer, i); +} + +bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, int field_number) +{ + int tag = wiretype | (field_number << 3); + return pb_encode_varint(stream, tag); +} + +bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field) +{ + pb_wire_type_t wiretype; + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_VARINT: + case PB_LTYPE_SVARINT: + wiretype = PB_WT_VARINT; + break; + + case PB_LTYPE_FIXED: + if (field->data_size == 4) + wiretype = PB_WT_32BIT; + else if (field->data_size == 8) + wiretype = PB_WT_64BIT; + else + return false; + break; + + case PB_LTYPE_BYTES: + case PB_LTYPE_STRING: + case PB_LTYPE_SUBMESSAGE: + wiretype = PB_WT_STRING; + break; + + default: + return false; + } + + return pb_encode_tag(stream, wiretype, field->tag); +} + +bool pb_encode_string(pb_ostream_t *stream, uint8_t *buffer, size_t size) +{ + if (!pb_encode_varint(stream, size)) + return false; + + return pb_write(stream, buffer, size); +} + +/* Field encoders */ + +/* Copy srcsize bytes from src so that values are casted properly. + * On little endian machine, copy to start of dest + * On big endian machine, copy to end of dest + * destsize must always be larger than srcsize + * + * Note: This is the reverse of the endian_copy in pb_decode.c. + */ +static void endian_copy(void *dest, const void *src, size_t destsize, size_t srcsize) +{ +#ifdef __BIG_ENDIAN__ + memcpy((char*)dest + (destsize - srcsize), src, srcsize); +#else + memcpy(dest, src, srcsize); +#endif +} + +bool pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + uint64_t value = 0; + endian_copy(&value, src, sizeof(value), field->data_size); + return pb_encode_varint(stream, value); +} + +bool pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + uint64_t value = 0; + uint64_t zigzagged; + uint64_t mask; + endian_copy(&value, src, sizeof(value), field->data_size); + + mask = 0x80 << (field->data_size * 8); + zigzagged = (value & ~mask) << 1; + if (value & mask) zigzagged |= 1; + + return pb_encode_varint(stream, value); +} + +bool pb_enc_fixed(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + #ifdef __BIG_ENDIAN__ + uint8_t bytes[8] = {0}; + endian_copy(bytes, src, sizeof(bytes), field->data_size); + uint8_t lebytes[8] = {bytes[7], bytes[6], bytes[5], bytes[4], + bytes[3], bytes[2], bytes[1], bytes[0]}; + return pb_write(stream, lebytes, field->data_size); + #else + return pb_write(stream, (uint8_t*)src, field->data_size); + #endif +} + +bool pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + pb_bytes_array_t *bytes = (pb_bytes_array_t*)src; + return pb_encode_string(stream, bytes->bytes, bytes->size); +} + +bool pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + return pb_encode_string(stream, (uint8_t*)src, strlen((char*)src)); +} + +bool pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + pb_ostream_t sizestream = {0}; + size_t size; + + if (field->ptr == NULL) + return false; + + if (!pb_encode(&sizestream, (pb_field_t*)field->ptr, src)) + return false; + + size = sizestream.bytes_written; + + if (!pb_encode_varint(stream, size)) + return false; + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing */ + + return pb_encode(stream, (pb_field_t*)field->ptr, src); +} + diff --git a/pb_encode.h b/pb_encode.h new file mode 100644 index 00000000..95f3483a --- /dev/null +++ b/pb_encode.h @@ -0,0 +1,69 @@ +#ifndef _PB_ENCODE_H_ +#define _PB_ENCODE_H_ + +/* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c. + * The main function is pb_encode. You also need an output stream, structures + * and their field descriptions (just like with pb_decode). + */ + +#include +#include "pb.h" + +/* Lightweight output stream. + * You can provide callback for writing or use pb_ostream_from_buffer. + * + * Alternatively, callback can be NULL in which case the stream will just + * count the number of bytes that would have been written. In this case + * max_size is not checked. + * + * Rules for callback: + * 1) Return false on IO errors. This will cause decoding to abort. + * + * 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. + * + * 4) Your callback will be always used with the same pb_ostream_t. + * There are no substreams when encoding. + */ +struct _pb_ostream_t +{ + bool (*callback)(pb_ostream_t *stream, const uint8_t *buf, size_t count); + void *state; /* Free field for use by callback implementation */ + size_t max_size; /* Limit number of output bytes written (or use SIZE_MAX). */ + size_t bytes_written; +}; + +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); + +/* Encode struct to given output stream. + * Returns true on success, false on any failure. + * The actual struct pointed to by src_struct must match the description in fields. + * All required fields in the struct are assumed to have been filled in. + */ +bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); + +/* --- Helper functions --- + * You may want to use these from your caller or callbacks. + */ + +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); +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); + +/* --- Field encoders --- + * Each encoder writes the content for the field. + * The tag/wire type has been written already. + */ + +bool pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src); +bool pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src); +bool pb_enc_fixed(pb_ostream_t *stream, const pb_field_t *field, const void *src); + +bool pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src); +bool pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src); +bool pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src); + +#endif \ No newline at end of file diff --git a/tests/Makefile b/tests/Makefile index 35c6f977..7450e1ed 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,14 +1,14 @@ CFLAGS=-ansi -Wall -Werror -I .. -g -O0 -DEPS=../pb_decode.c ../pb_decode.h ../pb.h person.h -TESTS=test_decode1 decode_unittests +DEPS=../pb_decode.c ../pb_decode.h ../pb_encode.c ../pb_encode.h ../pb.h person.h +TESTS=test_decode1 test_encode1 decode_unittests all: $(TESTS) clean: - rm -f test_decode1 + rm -f $(TESTS) %: %.c $(DEPS) - $(CC) $(CFLAGS) -o $@ $< ../pb_decode.c + $(CC) $(CFLAGS) -o $@ $< ../pb_decode.c ../pb_encode.c run_unittests: decode_unittests ./decode_unittests diff --git a/tests/person.proto b/tests/person.proto index 01b2d4e1..5befb076 100644 --- a/tests/person.proto +++ b/tests/person.proto @@ -4,7 +4,7 @@ message Person { required string name = 1 [(nanopb).max_size = 40]; required int32 id = 2; optional string email = 3 [(nanopb).max_size = 40]; - optional bytes test = 5 [default = "abc\x00\x01\x02"]; + optional bytes test = 5 [default="\x00\x01\x02", (nanopb).max_size = 20]; enum PhoneType { MOBILE = 0; diff --git a/tests/test_decode1.c b/tests/test_decode1.c index 362c404a..cc4688dc 100644 --- a/tests/test_decode1.c +++ b/tests/test_decode1.c @@ -1,9 +1,10 @@ #include -#include -#include #include #include "person.h" +/* This test has only one source file anyway.. */ +#include "person.c" + bool print_person(pb_istream_t *stream) { int i; diff --git a/tests/test_encode1.c b/tests/test_encode1.c new file mode 100644 index 00000000..b4998f47 --- /dev/null +++ b/tests/test_encode1.c @@ -0,0 +1,23 @@ +#include +#include +#include "person.h" + +/* This test has only one source file anyway.. */ +#include "person.c" + +bool callback(pb_ostream_t *stream, const uint8_t *buf, size_t count) +{ + return fwrite(buf, 1, count, stdout) == count; +} + +int main() +{ + Person person = {"Test Person 99", 99, true, "test@person.com", + 1, {{"555-12345678", true, Person_PhoneType_MOBILE}}}; + + pb_ostream_t stream = {&callback, 0, SIZE_MAX, 0}; + + pb_encode(&stream, Person_fields, &person); + + return 0; +} -- cgit 1.2.3-korg