Compare commits

...

20 Commits

Author SHA1 Message Date
Petteri Aimonen
7a9c29f2d7 Perform field initialization to defaults only when the field is skipped.
Avoids unnecessary initialization & unnecessary scan of
the pb_field_t array.

Runtime on Cortex-M3 -5%, code size +2%.

Could need some more testing with random field orders.
Have to write a tool to randomize fields in a message.
2013-04-05 21:35:36 +03:00
Petteri Aimonen
384e686fe6 Do not generate has_ fields for callback fields.
The arg field can be used to store the field presence from inside
the callback. Furthermore, having the has_ field for encoding callbacks
would be more annoying than useful.

Update issue 70
Status: FixedInGit
2013-04-02 20:01:31 +03:00
Petteri Aimonen
214b0eae8a Change the callback function to use void**.
NOTE: This change breaks backwards-compatibility by default.
If you have old callback functions, you can define PB_OLD_CALLBACK_STYLE
to retain the old behaviour.

If you want to convert your old callbacks to new signature, you need
to do the following:

1) Change decode callback argument to   void **arg
      and encode callback argument to   void * const *arg.

2) Change any reference to arg into *arg.

The rationale for making the new behaviour the default is that it
simplifies the common case of "allocate some memory in decode callback".

Update issue 69
Status: FixedInGit
2013-04-02 19:55:21 +03:00
Petteri Aimonen
6f3740f74e Fix warning on clang.
Update issue 67
Status: FixedInGit
2013-03-13 15:34:12 +02:00
Petteri Aimonen
1396dce2ae Add support for packed structures on IAR and MSVC.
Update issue 66
Status: FixedInGit
2013-03-13 15:22:00 +02:00
Petteri Aimonen
6468f23d23 Make the generator options accept a file name in addition to format string. 2013-03-09 23:03:09 +02:00
Petteri Aimonen
03e5393072 Add PB_SYSTEM_HEADER compile time option.
This allows replacing the C99 standard include file names with
a single system-specific file. It should provide all the necessary
system functions (typedefs, memset, memcpy, strlen).

Update issue 62
Status: FixedInGit
2013-03-09 14:56:34 +02:00
Petteri Aimonen
d580b225e8 Rename pb_field_iterator_t field 'current' to 'pos'.
This avoids a name clash when compiling as Linux kernel module.

Update issue 60
Status: FixedInGit
2013-03-09 14:52:38 +02:00
Petteri Aimonen
0352647118 Implement error message support for the encoder side.
Update issue 7
Status: FixedInGit
2013-03-09 14:49:15 +02:00
Petteri Aimonen
9b6641ac64 alltypes.proto no longer needs to include nanopb.proto 2013-03-09 14:45:41 +02:00
Petteri Aimonen
5f3bf35e01 Switch alltypes.proto to use the new .options file mechanism. 2013-03-09 14:23:44 +02:00
Petteri Aimonen
6f8dbc73eb Add simple support for separate options file.
Update issue 12
Still needs documentation.
2013-03-09 14:21:21 +02:00
Petteri Aimonen
e1b8a555f3 Fix additional bug with empty message types.
pb_field_next() would access past the fields array.
2013-03-09 13:12:09 +02:00
Petteri Aimonen
60109c0be1 Add option to run the tests with mudflap to detect pointer errors. 2013-03-09 13:09:14 +02:00
Petteri Aimonen
96d4016838 Improve the fuzztest.
Enable -fstack-protector-all to detect any stack smashing bugs. Also
use test_decode3 for maximal vulnerable surface.
2013-03-09 12:51:47 +02:00
Petteri Aimonen
a9c88f5570 Fix error in backwards compatibility testcase 2013-03-09 12:43:35 +02:00
Petteri Aimonen
d2e3c1ad93 Fix bug with decoding empty message types. Add test for the same.
Note: the bug only applies to empty message types. Empty messages
of non-empty message types are not affected.

Update issue 65
Status: FixedInGit
2013-03-09 12:35:07 +02:00
Petteri Aimonen
5522e02133 Add a dummy field if struct would otherwise be empty.
Update issue 64
Status: FixedInGit
2013-03-06 18:02:57 +02:00
Petteri Aimonen
64bf72d73d Add generator option to configure #include directives.
This suits complex projects, where there are multiple interdependent .proto files
in various directories. Patch by Michael Haberler.
2013-03-04 19:27:42 +02:00
Petteri Aimonen
104710b26c Setting version to 0.2.1-dev 2013-03-02 16:35:17 +02:00
23 changed files with 447 additions and 212 deletions

View File

@@ -181,7 +181,9 @@ Field callbacks
=============== ===============
When a field has dynamic length, nanopb cannot statically allocate storage for it. Instead, it allows you to handle the field in whatever way you want, using a callback function. When a field has dynamic length, nanopb cannot statically allocate storage for it. Instead, it allows you to handle the field in whatever way you want, using a callback function.
The `pb_callback_t`_ structure contains a function pointer and a *void* pointer you can use for passing data to the callback. If the function pointer is NULL, the field will be skipped. The actual behavior of the callback function is different in encoding and decoding modes. The `pb_callback_t`_ structure contains a function pointer and a *void* pointer called *arg* you can use for passing data to the callback. If the function pointer is NULL, the field will be skipped. A pointer to the *arg* is passed to the function, so that it can modify it and retrieve the value.
The actual behavior of the callback function is different in encoding and decoding modes. In encoding mode, the callback is called once and should write out everything, including field tags. In decoding mode, the callback is called repeatedly for every data item.
.. _`pb_callback_t`: reference.html#pb-callback-t .. _`pb_callback_t`: reference.html#pb-callback-t
@@ -189,7 +191,7 @@ Encoding callbacks
------------------ ------------------
:: ::
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg);
When encoding, the callback should write out complete fields, including the wire type and field number tag. It can write as many or as few fields as it likes. For example, if you want to write out an array as *repeated* field, you should do it all in a single call. When encoding, the callback should write out complete fields, including the wire type and field number tag. It can write as many or as few fields as it likes. For example, if you want to write out an array as *repeated* field, you should do it all in a single call.
@@ -203,7 +205,7 @@ If the callback is used in a submessage, it will be called multiple times during
This callback writes out a dynamically sized string:: This callback writes out a dynamically sized string::
bool write_string(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool write_string(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
char *str = get_string_from_somewhere(); char *str = get_string_from_somewhere();
if (!pb_encode_tag_for_field(stream, field)) if (!pb_encode_tag_for_field(stream, field))
@@ -216,7 +218,7 @@ Decoding callbacks
------------------ ------------------
:: ::
bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg);
When decoding, the callback receives a length-limited substring that reads the contents of a single field. The field tag has already been read. For *string* and *bytes*, the length value has already been parsed, and is available at *stream->bytes_left*. When decoding, the callback receives a length-limited substring that reads the contents of a single field. The field tag has already been read. For *string* and *bytes*, the length value has already been parsed, and is available at *stream->bytes_left*.
@@ -226,7 +228,7 @@ The callback will be called multiple times for repeated fields. For packed field
This callback reads multiple integers and prints them:: This callback reads multiple integers and prints them::
bool read_ints(pb_istream_t *stream, const pb_field_t *field, void *arg) bool read_ints(pb_istream_t *stream, const pb_field_t *field, void **arg)
{ {
while (stream->bytes_left) while (stream->bytes_left)
{ {

View File

@@ -24,6 +24,8 @@ PB_NO_ERRMSG Disables the support for error messages; only err
Decreases the code size by a few hundred bytes. Decreases the code size by a few hundred bytes.
PB_BUFFER_ONLY Disables the support for custom streams. Only supports encoding to memory buffers. PB_BUFFER_ONLY Disables the support for custom streams. Only supports encoding to memory buffers.
Speeds up execution and decreases code size slightly. Speeds up execution and decreases code size slightly.
PB_OLD_CALLBACK_STYLE Use the old function signature (void\* instead of void\*\*) for callback fields. This was the
default until nanopb-0.2.1.
============================ ================================================================================================ ============================ ================================================================================================
The PB_MAX_REQUIRED_FIELDS, PB_FIELD_16BIT and PB_FIELD_32BIT settings allow raising some datatype limits to suit larger messages. The PB_MAX_REQUIRED_FIELDS, PB_FIELD_16BIT and PB_FIELD_32BIT settings allow raising some datatype limits to suit larger messages.
@@ -122,14 +124,16 @@ Part of a message structure, for fields with type PB_HTYPE_CALLBACK::
typedef struct _pb_callback_t pb_callback_t; typedef struct _pb_callback_t pb_callback_t;
struct _pb_callback_t { struct _pb_callback_t {
union { union {
bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg);
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg);
} funcs; } funcs;
void *arg; void *arg;
}; };
The *arg* is passed to the callback when calling. It can be used to store any information that the callback might need. A pointer to the *arg* is passed to the callback when calling. It can be used to store any information that the callback might need.
Previously the function received just the value of *arg* instead of a pointer to it. This old behaviour can be enabled by defining *PB_OLD_CALLBACK_STYLE*.
When calling `pb_encode`_, *funcs.encode* is used, and similarly when calling `pb_decode`_, *funcs.decode* is used. The function pointers are stored in the same memory location but are of incompatible types. You can set the function pointer to NULL to skip the field. When calling `pb_encode`_, *funcs.encode* is used, and similarly when calling `pb_decode`_, *funcs.decode* is used. The function pointers are stored in the same memory location but are of incompatible types. You can set the function pointer to NULL to skip the field.

View File

@@ -23,7 +23,7 @@
#include "fileproto.pb.h" #include "fileproto.pb.h"
#include "common.h" #include "common.h"
bool printfile_callback(pb_istream_t *stream, const pb_field_t *field, void *arg) bool printfile_callback(pb_istream_t *stream, const pb_field_t *field, void **arg)
{ {
FileInfo fileinfo; FileInfo fileinfo;

View File

@@ -23,9 +23,9 @@
#include "fileproto.pb.h" #include "fileproto.pb.h"
#include "common.h" #include "common.h"
bool listdir_callback(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool listdir_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
DIR *dir = (DIR*) arg; DIR *dir = (DIR*) *arg;
struct dirent *file; struct dirent *file;
FileInfo fileinfo; FileInfo fileinfo;

View File

@@ -16,6 +16,9 @@ enum FieldType {
FT_IGNORE = 3; // Ignore the field completely. FT_IGNORE = 3; // Ignore the field completely.
} }
// This is the inner options message, which basically defines options for
// a field. When it is used in message or file scope, it applies to all
// fields.
message NanoPBOptions { message NanoPBOptions {
// Allocated size for 'bytes' and 'string' fields. // Allocated size for 'bytes' and 'string' fields.
optional int32 max_size = 1; optional int32 max_size = 1;
@@ -33,6 +36,9 @@ message NanoPBOptions {
optional bool packed_struct = 5 [default = false]; optional bool packed_struct = 5 [default = false];
} }
// Extensions to protoc 'Descriptor' type in order to define options
// inside a .proto file.
//
// Protocol Buffers extension number registry // Protocol Buffers extension number registry
// -------------------------------- // --------------------------------
// Project: Nanopb // Project: Nanopb

View File

@@ -1,8 +1,9 @@
'''Generate header file for nanopb from a ProtoBuf FileDescriptorSet.''' '''Generate header file for nanopb from a ProtoBuf FileDescriptorSet.'''
nanopb_version = "nanopb-0.2.0" nanopb_version = "nanopb-0.2.1-dev"
try: try:
import google.protobuf.descriptor_pb2 as descriptor import google.protobuf.descriptor_pb2 as descriptor
import google.protobuf.text_format as text_format
except: except:
print print
print "*************************************************************" print "*************************************************************"
@@ -54,9 +55,7 @@ datatypes = {
} }
class Names: class Names:
'''Keeps a set of nested names and formats them to C identifier. '''Keeps a set of nested names and formats them to C identifier.'''
You can subclass this with your own implementation.
'''
def __init__(self, parts = ()): def __init__(self, parts = ()):
if isinstance(parts, Names): if isinstance(parts, Names):
parts = parts.parts parts = parts.parts
@@ -187,7 +186,7 @@ class Field:
return cmp(self.tag, other.tag) return cmp(self.tag, other.tag)
def __str__(self): def __str__(self):
if self.rules == 'OPTIONAL': if self.rules == 'OPTIONAL' and self.allocation == 'STATIC':
result = ' bool has_' + self.name + ';\n' result = ' bool has_' + self.name + ';\n'
elif self.rules == 'REPEATED' and self.allocation == 'STATIC': elif self.rules == 'REPEATED' and self.allocation == 'STATIC':
result = ' size_t ' + self.name + '_count;\n' result = ' size_t ' + self.name + '_count;\n'
@@ -286,7 +285,7 @@ class Message:
self.fields = [] self.fields = []
for f in desc.field: for f in desc.field:
field_options = get_nanopb_suboptions(f, message_options) field_options = get_nanopb_suboptions(f, message_options, self.name + f.name)
if field_options.type != nanopb_pb2.FT_IGNORE: if field_options.type != nanopb_pb2.FT_IGNORE:
self.fields.append(Field(self.name, f, field_options)) self.fields.append(Field(self.name, f, field_options))
@@ -300,6 +299,12 @@ class Message:
def __str__(self): def __str__(self):
result = 'typedef struct _%s {\n' % self.name result = 'typedef struct _%s {\n' % self.name
if not self.ordered_fields:
# Empty structs are not allowed in C standard.
# Therefore add a dummy field if an empty message occurs.
result += ' uint8_t dummy_field;'
result += '\n'.join([str(f) for f in self.ordered_fields]) result += '\n'.join([str(f) for f in self.ordered_fields])
result += '\n}' result += '\n}'
@@ -307,6 +312,11 @@ class Message:
result += ' pb_packed' result += ' pb_packed'
result += ' %s;' % self.name result += ' %s;' % self.name
if self.packed:
result = 'PB_PACKED_STRUCT_START\n' + result
result += '\nPB_PACKED_STRUCT_END'
return result return result
def types(self): def types(self):
@@ -377,14 +387,14 @@ def parse_file(fdesc, file_options):
base_name = Names() base_name = Names()
for enum in fdesc.enum_type: for enum in fdesc.enum_type:
enum_options = get_nanopb_suboptions(enum, file_options) enum_options = get_nanopb_suboptions(enum, file_options, base_name + enum.name)
enums.append(Enum(base_name, enum, enum_options)) enums.append(Enum(base_name, enum, enum_options))
for names, message in iterate_messages(fdesc, base_name): for names, message in iterate_messages(fdesc, base_name):
message_options = get_nanopb_suboptions(message, file_options) message_options = get_nanopb_suboptions(message, file_options, names)
messages.append(Message(names, message, message_options)) messages.append(Message(names, message, message_options))
for enum in message.enum_type: for enum in message.enum_type:
enum_options = get_nanopb_suboptions(enum, message_options) enum_options = get_nanopb_suboptions(enum, message_options, names + enum.name)
enums.append(Enum(names, enum, enum_options)) enums.append(Enum(names, enum, enum_options))
# Fix field default values where enum short names are used. # Fix field default values where enum short names are used.
@@ -450,11 +460,17 @@ def generate_header(dependencies, headername, enums, messages, options):
symbol = make_identifier(headername) symbol = make_identifier(headername)
yield '#ifndef _PB_%s_\n' % symbol yield '#ifndef _PB_%s_\n' % symbol
yield '#define _PB_%s_\n' % symbol yield '#define _PB_%s_\n' % symbol
yield '#include <pb.h>\n\n' try:
yield options.libformat % ('pb.h')
except TypeError:
# no %s specified - use whatever was passed in as options.libformat
yield options.libformat
yield '\n'
for dependency in dependencies: for dependency in dependencies:
noext = os.path.splitext(dependency)[0] noext = os.path.splitext(dependency)[0]
yield '#include "%s.%s.h"\n' % (noext,options.extension) yield options.genformat % (noext + '.' + options.extension + '.h')
yield '\n'
yield '#ifdef __cplusplus\n' yield '#ifdef __cplusplus\n'
yield 'extern "C" {\n' yield 'extern "C" {\n'
@@ -490,7 +506,8 @@ def generate_source(headername, enums, messages):
yield '/* Automatically generated nanopb constant definitions */\n' yield '/* Automatically generated nanopb constant definitions */\n'
yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime()) yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime())
yield '#include "%s"\n\n' % headername yield options.genformat % (headername)
yield '\n'
for msg in messages: for msg in messages:
yield msg.default_decl(False) yield msg.default_decl(False)
@@ -566,36 +583,46 @@ def generate_source(headername, enums, messages):
yield '\n' yield '\n'
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Command line interface # Options parsing for the .proto files
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
import sys from fnmatch import fnmatch
import os.path
from optparse import OptionParser
import google.protobuf.text_format as text_format
optparser = OptionParser( def read_options_file(infile):
usage = "Usage: nanopb_generator.py [options] file.pb ...", '''Parse a separate options file to list:
epilog = "Compile file.pb from file.proto by: 'protoc -ofile.pb file.proto'. " + [(namemask, options), ...]
"Output will be written to file.pb.h and file.pb.c.") '''
optparser.add_option("-x", dest="exclude", metavar="FILE", action="append", default=[], results = []
help="Exclude file from generated #include list.") for line in infile:
optparser.add_option("-e", "--extension", dest="extension", metavar="EXTENSION", default="pb", line = line.strip()
help="use extension instead of 'pb' for generated files.") if not line or line.startswith('//') or line.startswith('#'):
optparser.add_option("-q", "--quiet", dest="quiet", action="store_true", default=False, continue
help="Don't print anything except errors.")
optparser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False,
help="Print more information.")
optparser.add_option("-s", dest="settings", metavar="OPTION:VALUE", action="append", default=[],
help="Set generator option (max_size, max_count etc.).")
def get_nanopb_suboptions(subdesc, options): parts = line.split(None, 1)
opts = nanopb_pb2.NanoPBOptions()
text_format.Merge(parts[1], opts)
results.append((parts[0], opts))
return results
class Globals:
'''Ugly global variables, should find a good way to pass these.'''
verbose_options = False
separate_options = []
def get_nanopb_suboptions(subdesc, options, name):
'''Get copy of options, and merge information from subdesc.''' '''Get copy of options, and merge information from subdesc.'''
new_options = nanopb_pb2.NanoPBOptions() new_options = nanopb_pb2.NanoPBOptions()
new_options.CopyFrom(options) new_options.CopyFrom(options)
# Handle options defined in a separate file
dotname = '.'.join(name.parts)
for namemask, options in Globals.separate_options:
if fnmatch(dotname, namemask):
new_options.MergeFrom(options)
# Handle options defined in .proto
if isinstance(subdesc.options, descriptor.FieldOptions): if isinstance(subdesc.options, descriptor.FieldOptions):
ext_type = nanopb_pb2.nanopb ext_type = nanopb_pb2.nanopb
elif isinstance(subdesc.options, descriptor.FileOptions): elif isinstance(subdesc.options, descriptor.FileOptions):
@@ -611,8 +638,44 @@ def get_nanopb_suboptions(subdesc, options):
ext = subdesc.options.Extensions[ext_type] ext = subdesc.options.Extensions[ext_type]
new_options.MergeFrom(ext) new_options.MergeFrom(ext)
if Globals.verbose_options:
print "Options for " + dotname + ":"
print text_format.MessageToString(new_options)
return new_options return new_options
# ---------------------------------------------------------------------------
# Command line interface
# ---------------------------------------------------------------------------
import sys
import os.path
from optparse import OptionParser
optparser = OptionParser(
usage = "Usage: nanopb_generator.py [options] file.pb ...",
epilog = "Compile file.pb from file.proto by: 'protoc -ofile.pb file.proto'. " +
"Output will be written to file.pb.h and file.pb.c.")
optparser.add_option("-x", dest="exclude", metavar="FILE", action="append", default=[],
help="Exclude file from generated #include list.")
optparser.add_option("-e", "--extension", dest="extension", metavar="EXTENSION", default="pb",
help="Set extension to use instead of 'pb' for generated files. [default: %default]")
optparser.add_option("-f", "--options-file", dest="options_file", metavar="FILE", default="%s.options",
help="Set name of a separate generator options file.")
optparser.add_option("-Q", "--generated-include-format", dest="genformat",
metavar="FORMAT", default='#include "%s"\n',
help="Set format string to use for including other .pb.h files. [default: %default]")
optparser.add_option("-L", "--library-include-format", dest="libformat",
metavar="FORMAT", default='#include <%s>\n',
help="Set format string to use for including the nanopb pb.h header. [default: %default]")
optparser.add_option("-q", "--quiet", dest="quiet", action="store_true", default=False,
help="Don't print anything except errors.")
optparser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False,
help="Print more information.")
optparser.add_option("-s", dest="settings", metavar="OPTION:VALUE", action="append", default=[],
help="Set generator option (max_size, max_count etc.).")
def process(filenames, options): def process(filenames, options):
'''Process the files given on the command line.''' '''Process the files given on the command line.'''
@@ -623,6 +686,8 @@ def process(filenames, options):
if options.quiet: if options.quiet:
options.verbose = False options.verbose = False
Globals.verbose_options = options.verbose
toplevel_options = nanopb_pb2.NanoPBOptions() toplevel_options = nanopb_pb2.NanoPBOptions()
for s in options.settings: for s in options.settings:
text_format.Merge(s, toplevel_options) text_format.Merge(s, toplevel_options)
@@ -631,12 +696,23 @@ def process(filenames, options):
data = open(filename, 'rb').read() data = open(filename, 'rb').read()
fdesc = descriptor.FileDescriptorSet.FromString(data) fdesc = descriptor.FileDescriptorSet.FromString(data)
file_options = get_nanopb_suboptions(fdesc.file[0], toplevel_options) # Check if any separate options are specified
try:
optfilename = options.options_file % os.path.splitext(filename)[0]
except TypeError:
# No %s specified, use the filename as-is
optfilename = options.options_file
if options.verbose: if options.verbose:
print "Options for " + filename + ":" print 'Reading options from ' + optfilename
print text_format.MessageToString(file_options)
if os.path.isfile(optfilename):
Globals.separate_options = read_options_file(open(optfilename, "rU"))
else:
Globals.separate_options = []
# Parse the file
file_options = get_nanopb_suboptions(fdesc.file[0], toplevel_options, Names([filename]))
enums, messages = parse_file(fdesc.file[0], file_options) enums, messages = parse_file(fdesc.file[0], file_options)
noext = os.path.splitext(filename)[0] noext = os.path.splitext(filename)[0]

46
pb.h
View File

@@ -6,17 +6,40 @@
* see pb_encode.h or pb_decode.h * see pb_encode.h or pb_decode.h
*/ */
#define NANOPB_VERSION nanopb-0.2.0 #define NANOPB_VERSION nanopb-0.2.1-dev
#ifdef PB_SYSTEM_HEADER
#include PB_SYSTEM_HEADER
#else
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h>
#endif
#ifdef __GNUC__ /* Macro for defining packed structures (compiler dependent).
/* This just reduces memory requirements, but is not required. */ * This just reduces memory requirements, but is not required.
#define pb_packed __attribute__((packed)) */
#if defined(__GNUC__) || defined(__clang__)
/* For GCC and clang */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed __attribute__((packed))
#elif defined(__ICCARM__)
/* For IAR ARM compiler */
# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)")
# define PB_PACKED_STRUCT_END _Pragma("pack(pop)")
# define pb_packed
#elif defined(_MSC_VER)
/* For Microsoft Visual C++ */
# define PB_PACKED_STRUCT_START __pragma(pack(push, 1))
# define PB_PACKED_STRUCT_END __pragma(pack(pop))
# define pb_packed
#else #else
#define pb_packed /* Unknown compiler */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed
#endif #endif
/* Handly macro for suppressing unreferenced-parameter compiler warnings. */ /* Handly macro for suppressing unreferenced-parameter compiler warnings. */
@@ -113,6 +136,7 @@ typedef uint8_t pb_type_t;
* structures. Fix that by defining PB_FIELD_16BIT or * structures. Fix that by defining PB_FIELD_16BIT or
* PB_FIELD_32BIT. * PB_FIELD_32BIT.
*/ */
PB_PACKED_STRUCT_START
typedef struct _pb_field_t pb_field_t; typedef struct _pb_field_t pb_field_t;
struct _pb_field_t { struct _pb_field_t {
@@ -144,6 +168,7 @@ struct _pb_field_t {
* If null, then field will zeroed. */ * If null, then field will zeroed. */
const void *ptr; const void *ptr;
} pb_packed; } pb_packed;
PB_PACKED_STRUCT_END
/* This structure is used for 'bytes' arrays. /* This structure is used for 'bytes' arrays.
* It has the number of bytes in the beginning, and after that an array. * It has the number of bytes in the beginning, and after that an array.
@@ -178,10 +203,19 @@ typedef struct _pb_istream_t pb_istream_t;
typedef struct _pb_ostream_t pb_ostream_t; typedef struct _pb_ostream_t pb_ostream_t;
typedef struct _pb_callback_t pb_callback_t; typedef struct _pb_callback_t pb_callback_t;
struct _pb_callback_t { struct _pb_callback_t {
#ifdef PB_OLD_CALLBACK_STYLE
/* Deprecated since nanopb-0.2.1 */
union { union {
bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg);
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg);
} funcs; } funcs;
#else
/* New function signature, which allows modifying arg contents in callback. */
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 * const *arg);
} funcs;
#endif
/* Free arg for use by callback */ /* Free arg for use by callback */
void *arg; void *arg;
@@ -199,7 +233,7 @@ typedef enum {
#define pb_membersize(st, m) (sizeof ((st*)0)->m) #define pb_membersize(st, m) (sizeof ((st*)0)->m)
#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0])) #define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0]))
#define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2)) #define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2))
#define pb_delta_end(st, m1, m2) (offsetof(st, m1) == offsetof(st, m2) \ #define pb_delta_end(st, m1, m2) (int)(offsetof(st, m1) == offsetof(st, m2) \
? offsetof(st, m1) \ ? offsetof(st, m1) \
: offsetof(st, m1) - offsetof(st, m2) - pb_membersize(st, m2)) : offsetof(st, m1) - offsetof(st, m2) - pb_membersize(st, m2))
#define PB_LAST_FIELD {0,(pb_type_t) 0,0,0,0,0,0} #define PB_LAST_FIELD {0,(pb_type_t) 0,0,0,0,0,0}

View File

@@ -14,7 +14,6 @@
#define NANOPB_INTERNALS #define NANOPB_INTERNALS
#include "pb.h" #include "pb.h"
#include "pb_decode.h" #include "pb_decode.h"
#include <string.h>
typedef bool (*pb_decoder_t)(pb_istream_t *stream, const pb_field_t *field, void *dest) checkreturn; typedef bool (*pb_decoder_t)(pb_istream_t *stream, const pb_field_t *field, void *dest) checkreturn;
@@ -280,7 +279,7 @@ void pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream)
/* Iterator for pb_field_t list */ /* Iterator for pb_field_t list */
typedef struct { typedef struct {
const pb_field_t *start; /* Start of the pb_field_t array */ const pb_field_t *start; /* Start of the pb_field_t array */
const pb_field_t *current; /* Current position of the iterator */ const pb_field_t *pos; /* Current position of the iterator */
unsigned field_index; /* Zero-based index of the field. */ unsigned field_index; /* Zero-based index of the field. */
unsigned required_field_index; /* Zero-based index that counts only the required fields */ unsigned required_field_index; /* Zero-based index that counts only the required fields */
void *dest_struct; /* Pointer to the destination structure to decode to */ void *dest_struct; /* Pointer to the destination structure to decode to */
@@ -290,33 +289,36 @@ typedef struct {
static void pb_field_init(pb_field_iterator_t *iter, const pb_field_t *fields, void *dest_struct) static void pb_field_init(pb_field_iterator_t *iter, const pb_field_t *fields, void *dest_struct)
{ {
iter->start = iter->current = fields; iter->start = iter->pos = fields;
iter->field_index = 0; iter->field_index = 0;
iter->required_field_index = 0; iter->required_field_index = 0;
iter->pData = (char*)dest_struct + iter->current->data_offset; iter->pData = (char*)dest_struct + iter->pos->data_offset;
iter->pSize = (char*)iter->pData + iter->current->size_offset; iter->pSize = (char*)iter->pData + iter->pos->size_offset;
iter->dest_struct = dest_struct; iter->dest_struct = dest_struct;
} }
static bool pb_field_next(pb_field_iterator_t *iter) static bool pb_field_next(pb_field_iterator_t *iter)
{ {
bool notwrapped = true; bool notwrapped = true;
size_t prev_size = iter->current->data_size; size_t prev_size = iter->pos->data_size;
if (PB_ATYPE(iter->current->type) == PB_ATYPE_STATIC && if (PB_ATYPE(iter->pos->type) == PB_ATYPE_STATIC &&
PB_HTYPE(iter->current->type) == PB_HTYPE_REPEATED) PB_HTYPE(iter->pos->type) == PB_HTYPE_REPEATED)
{ {
prev_size *= iter->current->array_size; prev_size *= iter->pos->array_size;
} }
if (PB_HTYPE(iter->current->type) == PB_HTYPE_REQUIRED) if (PB_HTYPE(iter->pos->type) == PB_HTYPE_REQUIRED)
iter->required_field_index++; iter->required_field_index++;
iter->current++; if (iter->pos->tag == 0)
return false; /* Only happens with empty message types */
iter->pos++;
iter->field_index++; iter->field_index++;
if (iter->current->tag == 0) if (iter->pos->tag == 0)
{ {
iter->current = iter->start; iter->pos = iter->start;
iter->field_index = 0; iter->field_index = 0;
iter->required_field_index = 0; iter->required_field_index = 0;
iter->pData = iter->dest_struct; iter->pData = iter->dest_struct;
@@ -324,24 +326,11 @@ static bool pb_field_next(pb_field_iterator_t *iter)
notwrapped = false; notwrapped = false;
} }
iter->pData = (char*)iter->pData + prev_size + iter->current->data_offset; iter->pData = (char*)iter->pData + prev_size + iter->pos->data_offset;
iter->pSize = (char*)iter->pData + iter->current->size_offset; iter->pSize = (char*)iter->pData + iter->pos->size_offset;
return notwrapped; return notwrapped;
} }
static bool checkreturn pb_field_find(pb_field_iterator_t *iter, uint32_t tag)
{
unsigned start = iter->field_index;
do {
if (iter->current->tag == tag)
return true;
pb_field_next(iter);
} while (iter->field_index != start);
return false;
}
/************************* /*************************
* Decode a single field * * Decode a single field *
*************************/ *************************/
@@ -351,17 +340,17 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
pb_type_t type; pb_type_t type;
pb_decoder_t func; pb_decoder_t func;
type = iter->current->type; type = iter->pos->type;
func = PB_DECODERS[PB_LTYPE(type)]; func = PB_DECODERS[PB_LTYPE(type)];
switch (PB_HTYPE(type)) switch (PB_HTYPE(type))
{ {
case PB_HTYPE_REQUIRED: case PB_HTYPE_REQUIRED:
return func(stream, iter->current, iter->pData); return func(stream, iter->pos, iter->pData);
case PB_HTYPE_OPTIONAL: case PB_HTYPE_OPTIONAL:
*(bool*)iter->pSize = true; *(bool*)iter->pSize = true;
return func(stream, iter->current, iter->pData); return func(stream, iter->pos, iter->pData);
case PB_HTYPE_REPEATED: case PB_HTYPE_REPEATED:
if (wire_type == PB_WT_STRING if (wire_type == PB_WT_STRING
@@ -374,10 +363,10 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
if (!pb_make_string_substream(stream, &substream)) if (!pb_make_string_substream(stream, &substream))
return false; return false;
while (substream.bytes_left && *size < iter->current->array_size) while (substream.bytes_left && *size < iter->pos->array_size)
{ {
void *pItem = (uint8_t*)iter->pData + iter->current->data_size * (*size); void *pItem = (uint8_t*)iter->pData + iter->pos->data_size * (*size);
if (!func(&substream, iter->current, pItem)) if (!func(&substream, iter->pos, pItem))
{ {
status = false; status = false;
break; break;
@@ -395,12 +384,12 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
{ {
/* Repeated field */ /* Repeated field */
size_t *size = (size_t*)iter->pSize; size_t *size = (size_t*)iter->pSize;
void *pItem = (uint8_t*)iter->pData + iter->current->data_size * (*size); void *pItem = (uint8_t*)iter->pData + iter->pos->data_size * (*size);
if (*size >= iter->current->array_size) if (*size >= iter->pos->array_size)
PB_RETURN_ERROR(stream, "array overflow"); PB_RETURN_ERROR(stream, "array overflow");
(*size)++; (*size)++;
return func(stream, iter->current, pItem); return func(stream, iter->pos, pItem);
} }
default: default:
@@ -412,6 +401,12 @@ static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type
{ {
pb_callback_t *pCallback = (pb_callback_t*)iter->pData; pb_callback_t *pCallback = (pb_callback_t*)iter->pData;
#ifdef PB_OLD_CALLBACK_STYLE
void *arg = pCallback->arg;
#else
void **arg = &(pCallback->arg);
#endif
if (pCallback->funcs.decode == NULL) if (pCallback->funcs.decode == NULL)
return pb_skip_field(stream, wire_type); return pb_skip_field(stream, wire_type);
@@ -424,7 +419,7 @@ static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type
while (substream.bytes_left) while (substream.bytes_left)
{ {
if (!pCallback->funcs.decode(&substream, iter->current, pCallback->arg)) if (!pCallback->funcs.decode(&substream, iter->pos, arg))
PB_RETURN_ERROR(stream, "callback failed"); PB_RETURN_ERROR(stream, "callback failed");
} }
@@ -445,13 +440,13 @@ static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type
return false; return false;
substream = pb_istream_from_buffer(buffer, size); substream = pb_istream_from_buffer(buffer, size);
return pCallback->funcs.decode(&substream, iter->current, pCallback->arg); return pCallback->funcs.decode(&substream, iter->pos, arg);
} }
} }
static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iterator_t *iter) static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iterator_t *iter)
{ {
switch (PB_ATYPE(iter->current->type)) switch (PB_ATYPE(iter->pos->type))
{ {
case PB_ATYPE_STATIC: case PB_ATYPE_STATIC:
return decode_static_field(stream, wire_type, iter); return decode_static_field(stream, wire_type, iter);
@@ -464,71 +459,105 @@ static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_t
} }
} }
/* Initialize message fields to default values, recursively */ /* Set field count to zero (or clear has_ field). */
static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct) static void pb_clear_field_count(const pb_field_iterator_t *iter)
{ {
pb_field_iterator_t iter; pb_type_t type;
pb_field_init(&iter, fields, dest_struct); type = iter->pos->type;
/* Initialize size/has fields and apply default values */ if (iter->pos->tag == 0)
do return; /* Empty message type */
if (PB_ATYPE(type) == PB_ATYPE_STATIC)
{ {
pb_type_t type; if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL)
type = iter.current->type;
if (iter.current->tag == 0)
continue;
if (PB_ATYPE(type) == PB_ATYPE_STATIC)
{ {
/* Initialize the size field for optional/repeated fields to 0. */ *(bool*)iter->pSize = false;
if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL)
{
*(bool*)iter.pSize = false;
}
else if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
{
*(size_t*)iter.pSize = 0;
continue; /* Array is empty, no need to initialize contents */
}
/* Initialize field contents to default value */
if (PB_LTYPE(iter.current->type) == PB_LTYPE_SUBMESSAGE)
{
pb_message_set_to_defaults((const pb_field_t *) iter.current->ptr, iter.pData);
}
else if (iter.current->ptr != NULL)
{
memcpy(iter.pData, iter.current->ptr, iter.current->data_size);
}
else
{
memset(iter.pData, 0, iter.current->data_size);
}
} }
else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) else if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
{ {
continue; /* Don't overwrite callback */ *(size_t*)iter->pSize = 0;
} }
} while (pb_field_next(&iter)); }
}
/* Initialize message field to default value. Recurses on submessages. */
static void pb_set_field_to_default(const pb_field_iterator_t *iter)
{
pb_type_t type;
type = iter->pos->type;
if (iter->pos->tag == 0)
return; /* Empty message type */
/* We only need to initialize static fields.
* Furthermore, arrays do not need to be initialized as their length
* will be zero by default.
*/
if (PB_ATYPE(type) == PB_ATYPE_STATIC &&
PB_HTYPE(type) != PB_HTYPE_REPEATED)
{
if (PB_LTYPE(iter->pos->type) == PB_LTYPE_SUBMESSAGE)
{
/* Submessage: initialize the fields recursively */
pb_field_iterator_t subiter;
pb_field_init(&subiter, (const pb_field_t *)iter->pos->ptr, iter->pData);
do {
pb_clear_field_count(&subiter);
pb_set_field_to_default(&subiter);
} while (pb_field_next(&subiter));
}
else if (iter->pos->ptr != NULL)
{
/* Normal field: copy the default value */
memcpy(iter->pData, iter->pos->ptr, iter->pos->data_size);
}
else
{
/* Normal field without default value: initialize to zero */
memset(iter->pData, 0, iter->pos->data_size);
}
}
} }
/********************* /*********************
* Decode all fields * * Decode all fields *
*********************/ *********************/
bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) /* Helper function to initialize fields while advancing iterator */
static void advance_iterator(pb_field_iterator_t *iter, bool *initialize, bool *current_seen)
{
/* Initialize the fields we didn't decode. */
if (*initialize && !*current_seen)
pb_set_field_to_default(iter);
/* Stop initializing after the first pass through the array */
if (!pb_field_next(iter))
*initialize = false;
/* Clear the field count before decoding */
if (*initialize)
pb_clear_field_count(iter);
/* Reset the flag to indicate that the new field has not been written to yet. */
*current_seen = false;
}
static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct, bool initialize)
{ {
uint8_t fields_seen[(PB_MAX_REQUIRED_FIELDS + 7) / 8] = {0}; /* Used to check for required fields */ uint8_t fields_seen[(PB_MAX_REQUIRED_FIELDS + 7) / 8] = {0}; /* Used to check for required fields */
bool current_seen = false;
pb_field_iterator_t iter; pb_field_iterator_t iter;
pb_field_init(&iter, fields, dest_struct); pb_field_init(&iter, fields, dest_struct);
pb_clear_field_count(&iter);
while (stream->bytes_left) while (stream->bytes_left)
{ {
uint32_t tag; uint32_t tag;
pb_wire_type_t wire_type; pb_wire_type_t wire_type;
bool eof; bool eof;
unsigned start;
bool skip = false;
if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) if (!pb_decode_tag(stream, &wire_type, &tag, &eof))
{ {
@@ -538,20 +567,45 @@ bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[
return false; return false;
} }
if (!pb_field_find(&iter, tag)) /* Go through the fields until we either find a match or
* wrap around to start. On the first pass, also initialize
* any missing fields.
*
* The logic here is to avoid unnecessary initialization
* in the common case, where all fields occur in the proper
* order.
*/
start = iter.field_index;
while (iter.pos->tag != tag)
{
advance_iterator(&iter, &initialize, &current_seen);
if (iter.field_index == start)
{
skip = true;
break;
}
}
/* Skip data if field was not found */
if (skip)
{ {
/* No match found, skip data */
if (!pb_skip_field(stream, wire_type)) if (!pb_skip_field(stream, wire_type))
return false; return false;
continue; continue;
} }
if (PB_HTYPE(iter.current->type) == PB_HTYPE_REQUIRED current_seen = true;
/* Keep track if all required fields are present */
if (PB_HTYPE(iter.pos->type) == PB_HTYPE_REQUIRED
&& iter.required_field_index < PB_MAX_REQUIRED_FIELDS) && iter.required_field_index < PB_MAX_REQUIRED_FIELDS)
{ {
fields_seen[iter.required_field_index >> 3] |= (uint8_t)(1 << (iter.required_field_index & 7)); fields_seen[iter.required_field_index >> 3] |= (uint8_t)(1 << (iter.required_field_index & 7));
} }
/* Finally, decode the field data */
if (!decode_field(stream, wire_type, &iter)) if (!decode_field(stream, wire_type, &iter))
return false; return false;
} }
@@ -561,17 +615,21 @@ bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[
/* First figure out the number of required fields by /* First figure out the number of required fields by
* seeking to the end of the field array. Usually we * seeking to the end of the field array. Usually we
* are already close to end after decoding. * are already close to end after decoding.
*
* Note: this simultaneously initializes any fields
* that haven't been already initialized.
*/ */
unsigned req_field_count; unsigned req_field_count;
pb_type_t last_type; pb_type_t last_type;
unsigned i; unsigned i;
do { do {
req_field_count = iter.required_field_index; req_field_count = iter.required_field_index;
last_type = iter.current->type; last_type = iter.pos->type;
} while (pb_field_next(&iter)); advance_iterator(&iter, &initialize, &current_seen);
} while (iter.field_index != 0);
/* Fixup if last field was also required. */ /* Fixup if last field was also required. */
if (PB_HTYPE(last_type) == PB_HTYPE_REQUIRED) if (PB_HTYPE(last_type) == PB_HTYPE_REQUIRED && iter.pos->tag)
req_field_count++; req_field_count++;
/* Check the whole bytes */ /* Check the whole bytes */
@@ -589,10 +647,14 @@ bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[
return true; return true;
} }
bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
{
return pb_decode_inner(stream, fields, dest_struct, false);
}
bool checkreturn pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) bool checkreturn pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
{ {
pb_message_set_to_defaults(fields, dest_struct); return pb_decode_inner(stream, fields, dest_struct, true);
return pb_decode_noinit(stream, fields, dest_struct);
} }
/* Field decoders */ /* Field decoders */
@@ -741,12 +803,7 @@ bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field
if (field->ptr == NULL) if (field->ptr == NULL)
PB_RETURN_ERROR(stream, "invalid field descriptor"); PB_RETURN_ERROR(stream, "invalid field descriptor");
/* New array entries need to be initialized, while required and optional status = pb_decode(&substream, submsg_fields, dest);
* submessages have already been initialized in the top-level pb_decode. */
if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED)
status = pb_decode(&substream, submsg_fields, dest);
else
status = pb_decode_noinit(&substream, submsg_fields, dest);
pb_close_string_substream(stream, &substream); pb_close_string_substream(stream, &substream);
return status; return status;

View File

@@ -62,6 +62,9 @@ bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struc
/* Same as pb_decode, except does not initialize the destination structure /* Same as pb_decode, except does not initialize the destination structure
* to default values. This is slightly faster if you need no default values * to default values. This is slightly faster if you need no default values
* and just do memset(struct, 0, sizeof(struct)) yourself. * and just do memset(struct, 0, sizeof(struct)) yourself.
*
* It can also be used to merge fields from a new message into a previously
* initialized structure.
*/ */
bool pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); bool pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct);

View File

@@ -6,7 +6,6 @@
#define NANOPB_INTERNALS #define NANOPB_INTERNALS
#include "pb.h" #include "pb.h"
#include "pb_encode.h" #include "pb_encode.h"
#include <string.h>
/* The warn_unused_result attribute appeared first in gcc-3.4.0 */ /* The warn_unused_result attribute appeared first in gcc-3.4.0 */
#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) #if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
@@ -56,6 +55,9 @@ pb_ostream_t pb_ostream_from_buffer(uint8_t *buf, size_t bufsize)
stream.state = buf; stream.state = buf;
stream.max_size = bufsize; stream.max_size = bufsize;
stream.bytes_written = 0; stream.bytes_written = 0;
#ifndef PB_NO_ERRMSG
stream.errmsg = NULL;
#endif
return stream; return stream;
} }
@@ -64,14 +66,14 @@ bool checkreturn pb_write(pb_ostream_t *stream, const uint8_t *buf, size_t count
if (stream->callback != NULL) if (stream->callback != NULL)
{ {
if (stream->bytes_written + count > stream->max_size) if (stream->bytes_written + count > stream->max_size)
return false; PB_RETURN_ERROR(stream, "stream full");
#ifdef PB_BUFFER_ONLY #ifdef PB_BUFFER_ONLY
if (!buf_write(stream, buf, count)) if (!buf_write(stream, buf, count))
return false; PB_RETURN_ERROR(stream, "io error");
#else #else
if (!stream->callback(stream, buf, count)) if (!stream->callback(stream, buf, count))
return false; PB_RETURN_ERROR(stream, "io error");
#endif #endif
} }
@@ -111,7 +113,7 @@ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *fie
} }
else else
{ {
pb_ostream_t sizestream = {0,0,0,0}; pb_ostream_t sizestream = PB_OSTREAM_SIZING;
p = pData; p = pData;
for (i = 0; i < count; i++) for (i = 0; i < count; i++)
{ {
@@ -187,7 +189,7 @@ bool checkreturn encode_static_field(pb_ostream_t *stream, const pb_field_t *fie
break; break;
default: default:
return false; PB_RETURN_ERROR(stream, "invalid field type");
} }
return true; return true;
@@ -196,10 +198,17 @@ bool checkreturn encode_static_field(pb_ostream_t *stream, const pb_field_t *fie
bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData) bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData)
{ {
const pb_callback_t *callback = (const pb_callback_t*)pData; const pb_callback_t *callback = (const pb_callback_t*)pData;
#ifdef PB_OLD_CALLBACK_STYLE
const void *arg = callback->arg;
#else
void * const *arg = &(callback->arg);
#endif
if (callback->funcs.encode != NULL) if (callback->funcs.encode != NULL)
{ {
if (!callback->funcs.encode(stream, field, callback->arg)) if (!callback->funcs.encode(stream, field, arg))
return false; PB_RETURN_ERROR(stream, "callback error");
} }
return true; return true;
} }
@@ -235,7 +244,7 @@ bool checkreturn pb_encode(pb_ostream_t *stream, const pb_field_t fields[], cons
break; break;
default: default:
return false; PB_RETURN_ERROR(stream, "invalid field type");
} }
field++; field++;
@@ -340,7 +349,7 @@ bool checkreturn pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t
break; break;
default: default:
return false; PB_RETURN_ERROR(stream, "invalid field type");
} }
return pb_encode_tag(stream, wiretype, field->tag); return pb_encode_tag(stream, wiretype, field->tag);
@@ -357,7 +366,7 @@ bool checkreturn pb_encode_string(pb_ostream_t *stream, const uint8_t *buffer, s
bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct)
{ {
/* First calculate the message size using a non-writing substream. */ /* First calculate the message size using a non-writing substream. */
pb_ostream_t substream = {0,0,0,0}; pb_ostream_t substream = PB_OSTREAM_SIZING;
size_t size; size_t size;
bool status; bool status;
@@ -373,7 +382,7 @@ bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fie
return pb_write(stream, NULL, size); /* Just sizing */ return pb_write(stream, NULL, size); /* Just sizing */
if (stream->bytes_written + size > stream->max_size) if (stream->bytes_written + size > stream->max_size)
return false; PB_RETURN_ERROR(stream, "stream full");
/* Use a substream to verify that a callback doesn't write more than /* Use a substream to verify that a callback doesn't write more than
* what it did the first time. */ * what it did the first time. */
@@ -381,14 +390,20 @@ bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fie
substream.state = stream->state; substream.state = stream->state;
substream.max_size = size; substream.max_size = size;
substream.bytes_written = 0; substream.bytes_written = 0;
#ifndef PB_NO_ERRMSG
substream.errmsg = NULL;
#endif
status = pb_encode(&substream, fields, src_struct); status = pb_encode(&substream, fields, src_struct);
stream->bytes_written += substream.bytes_written; stream->bytes_written += substream.bytes_written;
stream->state = substream.state; stream->state = substream.state;
#ifndef PB_NO_ERRMSG
stream->errmsg = substream.errmsg;
#endif
if (substream.bytes_written != size) if (substream.bytes_written != size)
return false; PB_RETURN_ERROR(stream, "submsg size changed");
return status; return status;
} }
@@ -405,7 +420,7 @@ bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, co
case 2: value = *(const uint16_t*)src; break; case 2: value = *(const uint16_t*)src; break;
case 4: value = *(const uint32_t*)src; break; case 4: value = *(const uint32_t*)src; break;
case 8: value = *(const uint64_t*)src; break; case 8: value = *(const uint64_t*)src; break;
default: return false; default: PB_RETURN_ERROR(stream, "invalid data_size");
} }
return pb_encode_varint(stream, value); return pb_encode_varint(stream, value);
@@ -419,7 +434,7 @@ bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, c
{ {
case 4: value = *(const int32_t*)src; break; case 4: value = *(const int32_t*)src; break;
case 8: value = *(const int64_t*)src; break; case 8: value = *(const int64_t*)src; break;
default: return false; default: PB_RETURN_ERROR(stream, "invalid data_size");
} }
return pb_encode_svarint(stream, value); return pb_encode_svarint(stream, value);
@@ -453,7 +468,7 @@ bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, co
bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src) bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{ {
if (field->ptr == NULL) if (field->ptr == NULL)
return false; PB_RETURN_ERROR(stream, "invalid field descriptor");
return pb_encode_submessage(stream, (const pb_field_t*)field->ptr, src); return pb_encode_submessage(stream, (const pb_field_t*)field->ptr, src);
} }

View File

@@ -46,11 +46,22 @@ struct _pb_ostream_t
void *state; /* Free field for use by callback implementation */ 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 max_size; /* Limit number of output bytes written (or use SIZE_MAX). */
size_t bytes_written; size_t bytes_written;
#ifndef PB_NO_ERRMSG
const char *errmsg;
#endif
}; };
pb_ostream_t pb_ostream_from_buffer(uint8_t *buf, size_t bufsize); 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); bool pb_write(pb_ostream_t *stream, const uint8_t *buf, size_t count);
/* Stream type for use in computing message sizes */
#ifndef PB_NO_ERRMSG
#define PB_OSTREAM_SIZING {0,0,0,0,0}
#else
#define PB_OSTREAM_SIZING {0,0,0,0}
#endif
/* Encode struct to given output stream. /* Encode struct to given output stream.
* Returns true on success, false on any failure. * Returns true on success, false on any failure.
* The actual struct pointed to by src_struct must match the description in fields. * The actual struct pointed to by src_struct must match the description in fields.

View File

@@ -15,13 +15,21 @@ CC_VERSION=$(shell $(CC) -v 2>&1)
CFLAGS_CORE= CFLAGS_CORE=
ifneq (,$(findstring gcc,$(CC_VERSION))) ifneq (,$(findstring gcc,$(CC_VERSION)))
CFLAGS_CORE=-pedantic -Wextra -Wcast-qual -Wlogical-op -Wconversion CFLAGS_CORE=-pedantic -Wextra -Wcast-qual -Wlogical-op -Wconversion
CFLAGS+=--coverage CFLAGS+=--coverage -fstack-protector-all
LDFLAGS+=--coverage LDFLAGS+=--coverage
endif endif
ifneq (,$(findstring clang,$(CC_VERSION))) ifneq (,$(findstring clang,$(CC_VERSION)))
CFLAGS_CORE=-pedantic -Wextra -Wcast-qual -Wconversion CFLAGS_CORE=-pedantic -Wextra -Wcast-qual -Wconversion
endif endif
# Also use mudflap if it is available
# To enable, run with make -B USE_MUDFLAP=y
USE_MUDFLAP ?= n
ifeq ($(USE_MUDFLAP),y)
CFLAGS += -fmudflap
LDFLAGS += -lmudflap -fmudflap
endif
all: breakpoints $(TESTS) run_unittests all: breakpoints $(TESTS) run_unittests
clean: clean:
@@ -127,5 +135,5 @@ test_options: options.pb.h options.expected options.pb.o
fi \ fi \
done done
run_fuzztest: test_decode2 run_fuzztest: test_decode3
bash -c 'I=1; while true; do cat /dev/urandom | ./test_decode2 > /dev/null; I=$$(($$I+1)); echo -en "\r$$I"; done' bash -c 'ulimit -c unlimited; I=1; while true; do cat /dev/urandom | ./test_decode3 > /dev/null; I=$$(($$I+1)); echo -en "\r$$I"; done'

3
tests/alltypes.options Normal file
View File

@@ -0,0 +1,3 @@
* max_size:16
* max_count:5

View File

@@ -1,11 +1,13 @@
import "nanopb.proto";
message SubMessage { message SubMessage {
required string substuff1 = 1 [(nanopb).max_size = 16, default = "1"]; required string substuff1 = 1 [default = "1"];
required int32 substuff2 = 2 [default = 2]; required int32 substuff2 = 2 [default = 2];
optional fixed32 substuff3 = 3 [default = 3]; optional fixed32 substuff3 = 3 [default = 3];
} }
message EmptyMessage {
}
enum MyEnum { enum MyEnum {
Zero = 0; Zero = 0;
First = 1; First = 1;
@@ -30,32 +32,34 @@ message AllTypes {
required sfixed64 req_sfixed64= 12; required sfixed64 req_sfixed64= 12;
required double req_double = 13; required double req_double = 13;
required string req_string = 14 [(nanopb).max_size = 16]; required string req_string = 14;
required bytes req_bytes = 15 [(nanopb).max_size = 16]; required bytes req_bytes = 15;
required SubMessage req_submsg = 16; required SubMessage req_submsg = 16;
required MyEnum req_enum = 17; required MyEnum req_enum = 17;
required EmptyMessage req_emptymsg = 18;
repeated int32 rep_int32 = 21 [(nanopb).max_count = 5]; repeated int32 rep_int32 = 21;
repeated int64 rep_int64 = 22 [(nanopb).max_count = 5]; repeated int64 rep_int64 = 22;
repeated uint32 rep_uint32 = 23 [(nanopb).max_count = 5]; repeated uint32 rep_uint32 = 23;
repeated uint64 rep_uint64 = 24 [(nanopb).max_count = 5]; repeated uint64 rep_uint64 = 24;
repeated sint32 rep_sint32 = 25 [(nanopb).max_count = 5]; repeated sint32 rep_sint32 = 25;
repeated sint64 rep_sint64 = 26 [(nanopb).max_count = 5]; repeated sint64 rep_sint64 = 26;
repeated bool rep_bool = 27 [(nanopb).max_count = 5]; repeated bool rep_bool = 27;
repeated fixed32 rep_fixed32 = 28 [(nanopb).max_count = 5]; repeated fixed32 rep_fixed32 = 28;
repeated sfixed32 rep_sfixed32= 29 [(nanopb).max_count = 5]; repeated sfixed32 rep_sfixed32= 29;
repeated float rep_float = 30 [(nanopb).max_count = 5]; repeated float rep_float = 30;
repeated fixed64 rep_fixed64 = 31 [(nanopb).max_count = 5]; repeated fixed64 rep_fixed64 = 31;
repeated sfixed64 rep_sfixed64= 32 [(nanopb).max_count = 5]; repeated sfixed64 rep_sfixed64= 32;
repeated double rep_double = 33 [(nanopb).max_count = 5]; repeated double rep_double = 33;
repeated string rep_string = 34 [(nanopb).max_size = 16, (nanopb).max_count = 5]; repeated string rep_string = 34;
repeated bytes rep_bytes = 35 [(nanopb).max_size = 16, (nanopb).max_count = 5]; repeated bytes rep_bytes = 35;
repeated SubMessage rep_submsg = 36 [(nanopb).max_count = 5]; repeated SubMessage rep_submsg = 36;
repeated MyEnum rep_enum = 37 [(nanopb).max_count = 5]; repeated MyEnum rep_enum = 37;
repeated EmptyMessage rep_emptymsg = 38;
optional int32 opt_int32 = 41 [default = 4041]; optional int32 opt_int32 = 41 [default = 4041];
optional int64 opt_int64 = 42 [default = 4042]; optional int64 opt_int64 = 42 [default = 4042];
@@ -73,10 +77,11 @@ message AllTypes {
optional sfixed64 opt_sfixed64= 52 [default = 4052]; optional sfixed64 opt_sfixed64= 52 [default = 4052];
optional double opt_double = 53 [default = 4053]; optional double opt_double = 53 [default = 4053];
optional string opt_string = 54 [(nanopb).max_size = 16, default = "4054"]; optional string opt_string = 54 [default = "4054"];
optional bytes opt_bytes = 55 [(nanopb).max_size = 16, default = "4055"]; optional bytes opt_bytes = 55 [default = "4055"];
optional SubMessage opt_submsg = 56; optional SubMessage opt_submsg = 56;
optional MyEnum opt_enum = 57 [default = Second]; optional MyEnum opt_enum = 57 [default = Second];
optional EmptyMessage opt_emptymsg = 58;
// Just to make sure that the size of the fields has been calculated // Just to make sure that the size of the fields has been calculated
// properly, i.e. otherwise a bug in last field might not be detected. // properly, i.e. otherwise a bug in last field might not be detected.

View File

@@ -5,7 +5,7 @@
* incompatible changes made to the generator in future versions. * incompatible changes made to the generator in future versions.
*/ */
#include "alltypes.pb.h" #include "bc_alltypes.pb.h"
const char SubMessage_substuff1_default[16] = "1"; const char SubMessage_substuff1_default[16] = "1";
const int32_t SubMessage_substuff2_default = 2; const int32_t SubMessage_substuff2_default = 2;

View File

@@ -19,11 +19,11 @@ bool stream_callback(pb_istream_t *stream, uint8_t *buf, size_t count)
} }
/* Verifies that the stream passed to callback matches the byte array pointed to by arg. */ /* Verifies that the stream passed to callback matches the byte array pointed to by arg. */
bool callback_check(pb_istream_t *stream, const pb_field_t *field, void *arg) bool callback_check(pb_istream_t *stream, const pb_field_t *field, void **arg)
{ {
int i; int i;
uint8_t byte; uint8_t byte;
pb_bytes_array_t *ref = (pb_bytes_array_t*) arg; pb_bytes_array_t *ref = (pb_bytes_array_t*) *arg;
for (i = 0; i < ref->size; i++) for (i = 0; i < ref->size; i++)
{ {

View File

@@ -17,7 +17,7 @@ bool streamcallback(pb_ostream_t *stream, const uint8_t *buf, size_t count)
return true; return true;
} }
bool fieldcallback(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool fieldcallback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
int value = 0x55; int value = 0x55;
if (!pb_encode_tag_for_field(stream, field)) if (!pb_encode_tag_for_field(stream, field))
@@ -25,7 +25,7 @@ bool fieldcallback(pb_ostream_t *stream, const pb_field_t *field, const void *ar
return pb_encode_varint(stream, value); return pb_encode_varint(stream, value);
} }
bool crazyfieldcallback(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool crazyfieldcallback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
/* This callback writes different amount of data the second time. */ /* This callback writes different amount of data the second time. */
uint32_t *state = (uint32_t*)arg; uint32_t *state = (uint32_t*)arg;

View File

@@ -76,6 +76,7 @@ bool check_alltypes(pb_istream_t *stream, int mode)
TEST(alltypes.rep_submsg[4].substuff3 == 2016 && alltypes.rep_submsg[0].substuff3 == 3); TEST(alltypes.rep_submsg[4].substuff3 == 2016 && alltypes.rep_submsg[0].substuff3 == 3);
TEST(alltypes.rep_enum_count == 5 && alltypes.rep_enum[4] == MyEnum_Truth && alltypes.rep_enum[0] == MyEnum_Zero); TEST(alltypes.rep_enum_count == 5 && alltypes.rep_enum[4] == MyEnum_Truth && alltypes.rep_enum[0] == MyEnum_Zero);
TEST(alltypes.rep_emptymsg_count == 5);
if (mode == 0) if (mode == 0)
{ {
@@ -120,6 +121,7 @@ bool check_alltypes(pb_istream_t *stream, int mode)
TEST(alltypes.opt_submsg.substuff3 == 3); TEST(alltypes.opt_submsg.substuff3 == 3);
TEST(alltypes.has_opt_enum == false); TEST(alltypes.has_opt_enum == false);
TEST(alltypes.opt_enum == MyEnum_Second); TEST(alltypes.opt_enum == MyEnum_Second);
TEST(alltypes.has_opt_emptymsg == false);
} }
else else
{ {
@@ -164,6 +166,7 @@ bool check_alltypes(pb_istream_t *stream, int mode)
TEST(alltypes.opt_submsg.substuff3 == 3); TEST(alltypes.opt_submsg.substuff3 == 3);
TEST(alltypes.has_opt_enum == true); TEST(alltypes.has_opt_enum == true);
TEST(alltypes.opt_enum == MyEnum_Truth); TEST(alltypes.opt_enum == MyEnum_Truth);
TEST(alltypes.has_opt_emptymsg == true);
} }
TEST(alltypes.end == 1099); TEST(alltypes.end == 1099);

View File

@@ -6,7 +6,7 @@
#include <pb_decode.h> #include <pb_decode.h>
#include "callbacks.pb.h" #include "callbacks.pb.h"
bool print_string(pb_istream_t *stream, const pb_field_t *field, void *arg) bool print_string(pb_istream_t *stream, const pb_field_t *field, void **arg)
{ {
uint8_t buffer[1024] = {0}; uint8_t buffer[1024] = {0};
@@ -20,37 +20,37 @@ bool print_string(pb_istream_t *stream, const pb_field_t *field, void *arg)
/* Print the string, in format comparable with protoc --decode. /* Print the string, in format comparable with protoc --decode.
* Format comes from the arg defined in main(). * Format comes from the arg defined in main().
*/ */
printf((char*)arg, buffer); printf((char*)*arg, buffer);
return true; return true;
} }
bool print_int32(pb_istream_t *stream, const pb_field_t *field, void *arg) bool print_int32(pb_istream_t *stream, const pb_field_t *field, void **arg)
{ {
uint64_t value; uint64_t value;
if (!pb_decode_varint(stream, &value)) if (!pb_decode_varint(stream, &value))
return false; return false;
printf((char*)arg, (long)value); printf((char*)*arg, (long)value);
return true; return true;
} }
bool print_fixed32(pb_istream_t *stream, const pb_field_t *field, void *arg) bool print_fixed32(pb_istream_t *stream, const pb_field_t *field, void **arg)
{ {
uint32_t value; uint32_t value;
if (!pb_decode_fixed32(stream, &value)) if (!pb_decode_fixed32(stream, &value))
return false; return false;
printf((char*)arg, (long)value); printf((char*)*arg, (long)value);
return true; return true;
} }
bool print_fixed64(pb_istream_t *stream, const pb_field_t *field, void *arg) bool print_fixed64(pb_istream_t *stream, const pb_field_t *field, void **arg)
{ {
uint64_t value; uint64_t value;
if (!pb_decode_fixed64(stream, &value)) if (!pb_decode_fixed64(stream, &value))
return false; return false;
printf((char*)arg, (long long)value); printf((char*)*arg, (long long)value);
return true; return true;
} }

View File

@@ -27,6 +27,7 @@ int main()
} }
else else
{ {
fprintf(stderr, "Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1; /* Failure */ return 1; /* Failure */
} }
} }

View File

@@ -26,7 +26,12 @@ int main()
/* Now encode it and check if we succeeded. */ /* Now encode it and check if we succeeded. */
if (pb_encode(&stream, Person_fields, &person)) if (pb_encode(&stream, Person_fields, &person))
{
return 0; /* Success */ return 0; /* Success */
}
else else
{
fprintf(stderr, "Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1; /* Failure */ return 1; /* Failure */
}
} }

View File

@@ -64,6 +64,7 @@ int main(int argc, char **argv)
alltypes.rep_submsg[4].substuff3 = 2016; alltypes.rep_submsg[4].substuff3 = 2016;
alltypes.rep_enum_count = 5; alltypes.rep_enum[4] = MyEnum_Truth; alltypes.rep_enum_count = 5; alltypes.rep_enum[4] = MyEnum_Truth;
alltypes.rep_emptymsg_count = 5;
if (mode != 0) if (mode != 0)
{ {
@@ -107,6 +108,7 @@ int main(int argc, char **argv)
alltypes.opt_submsg.substuff2 = 3056; alltypes.opt_submsg.substuff2 = 3056;
alltypes.has_opt_enum = true; alltypes.has_opt_enum = true;
alltypes.opt_enum = MyEnum_Truth; alltypes.opt_enum = MyEnum_Truth;
alltypes.has_opt_emptymsg = true;
} }
alltypes.end = 1099; alltypes.end = 1099;
@@ -122,7 +124,7 @@ int main(int argc, char **argv)
} }
else else
{ {
fprintf(stderr, "Encoding failed!\n"); fprintf(stderr, "Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1; /* Failure */ return 1; /* Failure */
} }
} }

View File

@@ -5,7 +5,7 @@
#include <pb_encode.h> #include <pb_encode.h>
#include "callbacks.pb.h" #include "callbacks.pb.h"
bool encode_string(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool encode_string(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
char *str = "Hello world!"; char *str = "Hello world!";
@@ -15,7 +15,7 @@ bool encode_string(pb_ostream_t *stream, const pb_field_t *field, const void *ar
return pb_encode_string(stream, (uint8_t*)str, strlen(str)); return pb_encode_string(stream, (uint8_t*)str, strlen(str));
} }
bool encode_int32(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool encode_int32(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
if (!pb_encode_tag_for_field(stream, field)) if (!pb_encode_tag_for_field(stream, field))
return false; return false;
@@ -23,7 +23,7 @@ bool encode_int32(pb_ostream_t *stream, const pb_field_t *field, const void *arg
return pb_encode_varint(stream, 42); return pb_encode_varint(stream, 42);
} }
bool encode_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool encode_fixed32(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
if (!pb_encode_tag_for_field(stream, field)) if (!pb_encode_tag_for_field(stream, field))
return false; return false;
@@ -32,7 +32,7 @@ bool encode_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *a
return pb_encode_fixed32(stream, &value); return pb_encode_fixed32(stream, &value);
} }
bool encode_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *arg) bool encode_fixed64(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{ {
if (!pb_encode_tag_for_field(stream, field)) if (!pb_encode_tag_for_field(stream, field))
return false; return false;