From 388d4de833cf4e2127b2ab0489cb6b14ecc0cbb5 Mon Sep 17 00:00:00 2001 From: Petteri Aimonen Date: Thu, 26 Sep 2013 10:23:37 +0300 Subject: [PATCH 1/1] Add #defines for the maximum encoded message size. Update issue 89 Status: FixedInGit --- generator/nanopb_generator.py | 111 +++++++++++++++++++++++++----- tests/alltypes/encode_alltypes.c | 2 +- tests/basic_buffer/decode_buffer.c | 2 +- tests/basic_buffer/encode_buffer.c | 2 +- tests/encode_unittests/encode_unittests.c | 13 ++++ 5 files changed, 111 insertions(+), 19 deletions(-) diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py index 645d5fd7..130ff932 100755 --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -38,22 +38,22 @@ except: import time import os.path -# Values are tuple (c type, pb type) +# Values are tuple (c type, pb type, encoded size) FieldD = descriptor.FieldDescriptorProto datatypes = { - FieldD.TYPE_BOOL: ('bool', 'BOOL'), - FieldD.TYPE_DOUBLE: ('double', 'DOUBLE'), - FieldD.TYPE_FIXED32: ('uint32_t', 'FIXED32'), - FieldD.TYPE_FIXED64: ('uint64_t', 'FIXED64'), - FieldD.TYPE_FLOAT: ('float', 'FLOAT'), - FieldD.TYPE_INT32: ('int32_t', 'INT32'), - FieldD.TYPE_INT64: ('int64_t', 'INT64'), - FieldD.TYPE_SFIXED32: ('int32_t', 'SFIXED32'), - FieldD.TYPE_SFIXED64: ('int64_t', 'SFIXED64'), - FieldD.TYPE_SINT32: ('int32_t', 'SINT32'), - FieldD.TYPE_SINT64: ('int64_t', 'SINT64'), - FieldD.TYPE_UINT32: ('uint32_t', 'UINT32'), - FieldD.TYPE_UINT64: ('uint64_t', 'UINT64') + FieldD.TYPE_BOOL: ('bool', 'BOOL', 1), + FieldD.TYPE_DOUBLE: ('double', 'DOUBLE', 8), + FieldD.TYPE_FIXED32: ('uint32_t', 'FIXED32', 4), + FieldD.TYPE_FIXED64: ('uint64_t', 'FIXED64', 8), + FieldD.TYPE_FLOAT: ('float', 'FLOAT', 4), + FieldD.TYPE_INT32: ('int32_t', 'INT32', 5), + FieldD.TYPE_INT64: ('int64_t', 'INT64', 10), + FieldD.TYPE_SFIXED32: ('int32_t', 'SFIXED32', 4), + FieldD.TYPE_SFIXED64: ('int64_t', 'SFIXED64', 8), + FieldD.TYPE_SINT32: ('int32_t', 'SINT32', 5), + FieldD.TYPE_SINT64: ('int64_t', 'SINT64', 10), + FieldD.TYPE_UINT32: ('uint32_t', 'UINT32', 5), + FieldD.TYPE_UINT64: ('uint64_t', 'UINT64', 10) } class Names: @@ -83,6 +83,17 @@ def names_from_type_name(type_name): raise NotImplementedError("Lookup of non-absolute type names is not supported") return Names(type_name[1:].split('.')) +def varint_max_size(max_value): + '''Returns the maximum number of bytes a varint can take when encoded.''' + for i in range(1, 11): + if (max_value >> (i * 7)) == 0: + return i + raise ValueError("Value too large for varint: " + str(max_value)) + +assert varint_max_size(0) == 1 +assert varint_max_size(127) == 1 +assert varint_max_size(128) == 2 + class Enum: def __init__(self, names, desc, enum_options): '''desc is EnumDescriptorProto''' @@ -113,6 +124,7 @@ class Field: self.max_size = None self.max_count = None self.array_decl = "" + self.enc_size = None # Parse field options if field_options.HasField("max_size"): @@ -141,12 +153,13 @@ class Field: # Decide the C data type to use in the struct. if datatypes.has_key(desc.type): - self.ctype, self.pbtype = datatypes[desc.type] + self.ctype, self.pbtype, self.enc_size = datatypes[desc.type] elif desc.type == FieldD.TYPE_ENUM: self.pbtype = 'ENUM' self.ctype = names_from_type_name(desc.type_name) if self.default is not None: self.default = self.ctype + self.default + self.enc_size = 5 # protoc rejects enum values > 32 bits elif desc.type == FieldD.TYPE_STRING: self.pbtype = 'STRING' if self.max_size is None: @@ -154,15 +167,18 @@ class Field: else: self.ctype = 'char' self.array_decl += '[%d]' % self.max_size + self.enc_size = varint_max_size(self.max_size) + self.max_size elif desc.type == FieldD.TYPE_BYTES: self.pbtype = 'BYTES' if self.max_size is None: can_be_static = False else: self.ctype = self.struct_name + self.name + 't' + self.enc_size = varint_max_size(self.max_size) + self.max_size elif desc.type == FieldD.TYPE_MESSAGE: self.pbtype = 'MESSAGE' self.ctype = self.submsgname = names_from_type_name(desc.type_name) + self.enc_size = None # Needs to be filled in after the message type is available else: raise NotImplementedError(desc.type) @@ -277,6 +293,42 @@ class Field: return max(self.tag, self.max_size, self.max_count) + def encoded_size(self, allmsgs): + '''Return the maximum size that this field can take when encoded, + including the field tag. If the size cannot be determined, returns + None.''' + + if self.allocation != 'STATIC': + return None + + encsize = self.enc_size + if self.pbtype == 'MESSAGE': + for msg in allmsgs: + if msg.name == self.submsgname: + encsize = msg.encoded_size(allmsgs) + if encsize is None: + return None # Submessage size is indeterminate + encsize += varint_max_size(encsize) # submsg length is encoded also + break + else: + # Submessage cannot be found, this currently occurs when + # the submessage type is defined in a different file. + return None + + if encsize is None: + raise RuntimeError("Could not determine encoded size for %s.%s" + % (self.struct_name, self.name)) + + encsize += varint_max_size(self.tag << 3) # Tag + wire type + + if self.rules == 'REPEATED': + # Decoders must be always able to handle unpacked arrays. + # Therefore we have to reserve space for it, even though + # we emit packed arrays ourselves. + encsize *= self.max_count + + return encsize + class ExtensionRange(Field): def __init__(self, struct_name, range_start, field_options): @@ -305,6 +357,12 @@ class ExtensionRange(Field): def tags(self): return '' + + def encoded_size(self, allmsgs): + # We exclude extensions from the count, because they cannot be known + # until runtime. Other option would be to return None here, but this + # way the value remains useful if extensions are not used. + return 0 class ExtensionField(Field): def __init__(self, struct_name, desc, field_options): @@ -429,6 +487,18 @@ class Message: result += ' PB_LAST_FIELD\n};' return result + def encoded_size(self, allmsgs): + '''Return the maximum size that this message can take when encoded. + If the size cannot be determined, returns None. + ''' + size = 0 + for field in self.fields: + fsize = field.encoded_size(allmsgs) + if fsize is None: + return None + size += fsize + + return size # --------------------------------------------------------------------------- @@ -597,8 +667,17 @@ def generate_header(dependencies, headername, enums, messages, extensions, optio yield '/* Struct field encoding specification for nanopb */\n' for msg in messages: yield msg.fields_declaration() + '\n' + yield '\n' - yield '\n#ifdef __cplusplus\n' + yield '/* Maximum encoded size of messages (where known) */\n' + for msg in messages: + msize = msg.encoded_size(messages) + if msize is not None: + identifier = '%s_size' % msg.name + yield '#define %-40s %d\n' % (identifier, msize) + yield '\n' + + yield '#ifdef __cplusplus\n' yield '} /* extern "C" */\n' yield '#endif\n' diff --git a/tests/alltypes/encode_alltypes.c b/tests/alltypes/encode_alltypes.c index 88fc10f0..9a2c6f60 100644 --- a/tests/alltypes/encode_alltypes.c +++ b/tests/alltypes/encode_alltypes.c @@ -115,7 +115,7 @@ int main(int argc, char **argv) alltypes.end = 1099; { - uint8_t buffer[1024]; + uint8_t buffer[AllTypes_size]; pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); /* Now encode it and check if we succeeded. */ diff --git a/tests/basic_buffer/decode_buffer.c b/tests/basic_buffer/decode_buffer.c index d231c916..fae9e2fa 100644 --- a/tests/basic_buffer/decode_buffer.c +++ b/tests/basic_buffer/decode_buffer.c @@ -60,7 +60,7 @@ bool print_person(pb_istream_t *stream) int main() { - uint8_t buffer[512]; + uint8_t buffer[Person_size]; pb_istream_t stream; size_t count; diff --git a/tests/basic_buffer/encode_buffer.c b/tests/basic_buffer/encode_buffer.c index d3e4f6e6..c412c14e 100644 --- a/tests/basic_buffer/encode_buffer.c +++ b/tests/basic_buffer/encode_buffer.c @@ -10,7 +10,7 @@ int main() { - uint8_t buffer[512]; + uint8_t buffer[Person_size]; pb_ostream_t stream; /* Initialize the structure with constants */ diff --git a/tests/encode_unittests/encode_unittests.c b/tests/encode_unittests/encode_unittests.c index c3634ac8..32a37bf1 100644 --- a/tests/encode_unittests/encode_unittests.c +++ b/tests/encode_unittests/encode_unittests.c @@ -280,6 +280,19 @@ int main() TEST(!pb_encode(&s, CallbackContainerContainer_fields, &msg2)) } + { + uint8_t buffer[StringMessage_size]; + pb_ostream_t s; + StringMessage msg = {"0123456789"}; + + s = pb_ostream_from_buffer(buffer, sizeof(buffer)); + + COMMENT("Test that StringMessage_size is correct") + + TEST(pb_encode(&s, StringMessage_fields, &msg)); + TEST(s.bytes_written == StringMessage_size); + } + if (status != 0) fprintf(stdout, "\n\nSome tests FAILED!\n"); -- 2.16.6