Compare commits

..

25 Commits

Author SHA1 Message Date
Petteri Aimonen
17c10119dd Remove the PB_ENCODERS array. 2013-11-14 19:36:53 +02:00
Petteri Aimonen
97210c91a9 Remove the PB_DECODERS array.
Reduces code size by 2% and is overall cleaner.
2013-11-14 19:26:47 +02:00
Petteri Aimonen
cace53dfbd Change the API for pb_make_string_substream() to use less stack.
If your own application uses this function, you have to change
the old style:

    pb_istream_t substream;
    if (!pb_make_string_substream(stream, &substream))
        return false;
    .. do stuff with substream ..
    pb_close_string_substream(stream, &substream);

to the new style:

    size_t remaining;
    if (!pb_make_string_substream(stream, &remaining))
        return false;
    .. do stuff with stream ..
    pb_close_string_substream(stream, remaining);
2013-11-14 19:16:49 +02:00
Petteri Aimonen
eff9e11150 Optimize the common case of 1-byte reads for varints.
For PB_BUFFER_ONLY configuration, this gives 20% speedup without
increasing code size.
2013-11-14 17:56:42 +02:00
Petteri Aimonen
5813144246 Make tests build in a separate folder, add to gitignore 2013-11-13 22:10:42 +02:00
Petteri Aimonen
171d64734a Fix path in FindNanopb.cmake.
Update issue 94
Status: FixedInGit
2013-11-11 09:22:32 +02:00
Petteri Aimonen
321ca6c1d5 Setting version to 0.2.5-dev 2013-11-07 16:47:14 +02:00
Petteri Aimonen
935a26ab1c Publishing nanopb-0.2.4 2013-11-07 16:44:41 +02:00
Petteri Aimonen
d7af99434a Add dates to changelog 2013-11-02 22:19:26 +02:00
Petteri Aimonen
564bdc8448 Update changelog 2013-11-02 22:11:27 +02:00
Petteri Aimonen
dcdd7f98fd Generate #define tags for extension fields also.
Update issue 93
Status: FixedInGit
2013-11-02 22:05:49 +02:00
Petteri Aimonen
287207841d Remove the NANOPB_INTERNALS functions from public API.
These have been deprecated since nanopb-0.1.6 (some since 0.1.3).
Equivalent functions with better interface are available in the API.

Update issue 91
Status: FixedInGit
2013-10-29 16:32:47 +02:00
Petteri Aimonen
0074deba9a Declare static functions before use.
For compliance with MISRA C rules (issue 91).
2013-10-29 16:24:50 +02:00
Petteri Aimonen
4d69cc2f3e Cleanup of comments. 2013-10-29 16:19:07 +02:00
Petteri Aimonen
c7b4ce0293 Add a definition of the security model to the documentation. 2013-10-29 15:44:35 +02:00
Petteri Aimonen
cd3af3272d Rename some internal functions to have unique names 2013-10-29 15:32:51 +02:00
Petteri Aimonen
ed564186e1 Detect invalid sizes when encoding bytes fields. 2013-10-24 21:45:39 +03:00
Petteri Aimonen
86d6983156 Same fix for EncodedSize.__mul__ 2013-10-24 09:52:40 +03:00
Petteri Aimonen
0848255d4a Handle also longs in EncodedSize 2013-10-24 08:44:59 +03:00
Petteri Aimonen
51f0e47295 Fix the size of length prefix for messages in other files. 2013-10-23 21:21:43 +03:00
Petteri Aimonen
49bd3f35a0 Generate message size #defines also for messages defined in multiple files.
Add testcase for the same.
2013-10-23 21:01:11 +03:00
Petteri Aimonen
2bfd497eea Define pb_size_t and pb_ssize_t data types.
Use these in pb_field_t definition to clean up some #ifs, and also
to prepare for solving issue #82.
2013-10-20 21:49:55 +03:00
Petteri Aimonen
e83fbd18d3 Check array max size when encoding.
Update issue 90
Status: FixedInGit
2013-10-20 21:42:00 +03:00
Petteri Aimonen
388d4de833 Add #defines for the maximum encoded message size.
Update issue 89
Status: FixedInGit
2013-09-26 10:23:37 +03:00
Petteri Aimonen
2363af29a2 Setting version to 0.2.4-dev 2013-09-18 12:47:32 +03:00
40 changed files with 647 additions and 268 deletions

19
.gitignore vendored
View File

@@ -5,17 +5,22 @@
*.pb.c
*.pb.h
*.pb
*.pyc
*~
*.tar.gz
.sconsign.dblite
config.log
.sconf_temp
tests/build
julkaisu.txt
docs/*.html
docs/generator_flow.png
example/client
example/server
example_avr_double/decode_double
example_avr_double/encode_double
example_avr_double/test_conversions
example_unions/decode
example_unions/encode
examples/simple/simple
examples/network_server/client
examples/network_server/server
examples/using_double_on_avr/decode_double
examples/using_double_on_avr/encode_double
examples/using_double_on_avr/test_conversions
examples/using_union_messages/decode
examples/using_union_messages/encode
generator/nanopb_pb2.pyc

View File

@@ -1,4 +1,13 @@
nanopb-0.2.3
nanopb-0.2.4 (2013-11-07)
Remove the deprecated NANOPB_INTERNALS functions from public API.
Document the security model.
Check array and bytes max sizes when encoding (issue 90)
Add #defines for maximum encoded message size (issue 89)
Add #define tags for extension fields (issue 93)
Fix MISRA C violations (issue 91)
Clean up pb_field_t definition with typedefs.
nanopb-0.2.3 (2013-09-18)
Improve compatibility by removing ternary operator from initializations (issue 88)
Fix build error on Visual C++ (issue 84, patch by Markus Schwarzenberg)
Don't stop on unsupported extension fields (issue 83)
@@ -7,7 +16,7 @@ nanopb-0.2.3
Switch from Makefiles to scons for building the tests
Make the tests buildable on Windows
nanopb-0.2.2
nanopb-0.2.2 (2013-08-18)
Add support for extension fields (issue 17)
Fix unknown fields in empty message (issue 78)
Include the field tags in the generated .pb.h file.
@@ -16,7 +25,7 @@ nanopb-0.2.2
Documentation improvements (issues 12, 77 and others)
Improved tests
nanopb-0.2.1
nanopb-0.2.1 (2013-04-14)
NOTE: The default callback function signature has changed.
If you don't want to update your code, define PB_OLD_CALLBACK_STYLE.
@@ -33,7 +42,7 @@ nanopb-0.2.1
Various new generator options
Improved tests
nanopb-0.2.0
nanopb-0.2.0 (2013-03-02)
NOTE: This release requires you to regenerate all .pb.c
files. Files generated by older versions will not
compile anymore.
@@ -46,7 +55,7 @@ nanopb-0.2.0
Add option to give file extension to generator (by Michael Haberler)
Documentation updates
nanopb-0.1.9
nanopb-0.1.9 (2013-02-13)
Fixed error message bugs (issues 52, 56)
Sanitize #ifndef filename (issue 50)
Performance improvements
@@ -57,13 +66,13 @@ nanopb-0.1.9
Added generator option to make message structs packed. (issue 49)
Add more test cases.
nanopb-0.1.8
nanopb-0.1.8 (2012-12-13)
Fix bugs in the enum short names introduced in 0.1.7 (issues 42, 43)
Fix STATIC_ASSERT macro when using multiple .proto files. (issue 41)
Fix missing initialization of istream.errmsg
Make tests/Makefile work for non-gcc compilers (issue 40)
nanopb-0.1.7
nanopb-0.1.7 (2012-11-11)
Remove "skip" mode from pb_istream_t callbacks. Example implementation had a bug. (issue 37)
Add option to use shorter names for enum values (issue 38)
Improve options support in generator (issues 12, 30)
@@ -73,19 +82,19 @@ nanopb-0.1.7
Add buffer size check in example (issue 34)
Fix build warnings on MS compilers (issue 33)
nanopb-0.1.6
nanopb-0.1.6 (2012-09-02)
Reorganize the field decoder interface (issue 2)
Improve performance in submessage decoding (issue 28)
Implement error messages in the decoder side (issue 7)
Extended testcases (alltypes test is now complete).
Fix some compiler warnings (issues 25, 26, 27, 32).
nanopb-0.1.5
nanopb-0.1.5 (2012-08-04)
Fix bug in decoder with packed arrays (issue 23).
Extended testcases.
Fix some compiler warnings.
nanopb-0.1.4
nanopb-0.1.4 (2012-07-05)
Add compile-time options for easy-to-use >255 field support.
Improve the detection of missing required fields.
Added example on how to handle union messages.
@@ -93,20 +102,20 @@ nanopb-0.1.4
Fix problems that stopped the code from compiling with some compilers.
Fix some compiler warnings.
nanopb-0.1.3
nanopb-0.1.3 (2012-06-12)
Refactor the field encoder interface.
Improve generator error messages (issue 5)
Add descriptor.proto into the #include exclusion list
Fix some compiler warnings.
nanopb-0.1.2
nanopb-0.1.2 (2012-02-15)
Make the generator to generate include for other .proto files (issue 4).
Fixed generator not working on Windows (issue 3)
nanopb-0.1.1
nanopb-0.1.1 (2012-01-14)
Fixed bug in encoder with 'bytes' fields (issue 1).
Fixed a bug in the generator that caused a compiler error on sfixed32 and sfixed64 fields.
Extended testcases.
nanopb-0.1.0
nanopb-0.1.0 (2012-01-06)
First stable release.

View File

@@ -148,7 +148,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS)
"${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h"
COMMAND python
ARGS ${NANOPB_GENERATOR_EXECUTABLE} ${FIL_WE}.pb
DEPENDS ${FIL_WE}.pb
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb"
COMMENT "Running nanopb generator on ${FIL_WE}.pb"
VERBATIM )
endforeach()

View File

@@ -1,4 +1,4 @@
all: index.html concepts.html reference.html \
all: index.html concepts.html reference.html security.html \
generator_flow.png
%.png: %.svg

View File

@@ -3,8 +3,9 @@
1) `Overview`_
2) `Concepts`_
3) `API reference`_
4) `Security model`_
.. _`Overview`: index.html
.. _`Concepts`: concepts.html
.. _`API reference`: reference.html
.. _`Security model`: security.html

View File

@@ -25,7 +25,9 @@ __BIG_ENDIAN__ Set this if your platform stores integers and
systems (different layout for ints and floats)
are currently not supported.
NANOPB_INTERNALS Set this to expose the field encoder functions
that are hidden since nanopb-0.1.3.
that are hidden since nanopb-0.1.3. Starting
with nanopb-0.2.4, this flag does nothing. Use
the newer functions that have better interface.
PB_MAX_REQUIRED_FIELDS Maximum number of required fields to check for
presence. Default value is 64. Increases stack
usage 1 byte per every 8 fields. Compiler

79
docs/security.rst Normal file
View File

@@ -0,0 +1,79 @@
======================
Nanopb: Security model
======================
.. include :: menu.rst
.. contents ::
Importance of security in a Protocol Buffers library
====================================================
In the context of protocol buffers, security comes into play when decoding
untrusted data. Naturally, if the attacker can modify the contents of a
protocol buffers message, he can feed the application any values possible.
Therefore the application itself must be prepared to receive untrusted values.
Where nanopb plays a part is preventing the attacker from running arbitrary
code on the target system. Mostly this means that there must not be any
possibility to cause buffer overruns, memory corruption or invalid pointers
by the means of crafting a malicious message.
Division of trusted and untrusted data
======================================
The following data is regarded as **trusted**. It must be under the control of
the application writer. Malicious data in these structures could cause
security issues, such as execution of arbitrary code:
1. Callback and extension fields in message structures given to pb_encode()
and pb_decode(). These fields are memory pointers, and are generated
depending on the .proto file.
2. The automatically generated field definitions, i.e. *pb_field_t* lists.
3. Contents of the *pb_istream_t* and *pb_ostream_t* structures (this does not
mean the contents of the stream itself, just the stream definition).
The following data is regarded as **untrusted**. Invalid/malicious data in
these will cause "garbage in, garbage out" behaviour. It will not cause
buffer overflows, information disclosure or other security problems:
1. All data read from *pb_istream_t*.
2. All fields in message structures, except callbacks and extensions.
(Beginning with nanopb-0.2.4, in earlier versions the field sizes are partially unchecked.)
Invariants
==========
The following invariants are maintained during operation, even if the
untrusted data has been maliciously crafted:
1. Nanopb will never read more than *bytes_left* bytes from *pb_istream_t*.
2. Nanopb will never write more than *max_size* bytes to *pb_ostream_t*.
3. Nanopb will never access memory out of bounds of the message structure.
4. After pb_decode() returns successfully, the message structure will be
internally consistent:
- The *count* fields of arrays will not exceed the array size.
- The *size* field of bytes will not exceed the allocated size.
- All string fields will have null terminator.
5. After pb_encode() returns successfully, the resulting message is a valid
protocol buffers message. (Except if user-defined callbacks write incorrect
data.)
Further considerations
======================
Even if the nanopb library is free of any security issues, there are still
several possible attack vectors that the application author must consider.
The following list is not comprehensive:
1. Stack usage may depend on the contents of the message. The message
definition places an upper bound on how much stack will be used. Tests
should be run with all fields present, to record the maximum possible
stack usage.
2. Callbacks can do anything. The code for the callbacks must be carefully
checked if they are used with untrusted data.
3. If using stream input, a maximum size should be set in *pb_istream_t* to
stop a denial of service attack from using an infinite message.
4. If using network sockets as streams, a timeout should be set to stop
denial of service attacks.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
'''Generate header file for nanopb from a ProtoBuf FileDescriptorSet.'''
nanopb_version = "nanopb-0.2.3"
nanopb_version = "nanopb-0.2.5-dev"
try:
import google.protobuf.descriptor_pb2 as descriptor
@@ -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,55 @@ 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 EncodedSize:
'''Class used to represent the encoded size of a field or a message.
Consists of a combination of symbolic sizes and integer sizes.'''
def __init__(self, value = 0, symbols = []):
if isinstance(value, (str, Names)):
symbols = [str(value)]
value = 0
self.value = value
self.symbols = symbols
def __add__(self, other):
if isinstance(other, (int, long)):
return EncodedSize(self.value + other, self.symbols)
elif isinstance(other, (str, Names)):
return EncodedSize(self.value, self.symbols + [str(other)])
elif isinstance(other, EncodedSize):
return EncodedSize(self.value + other.value, self.symbols + other.symbols)
else:
raise ValueError("Cannot add size: " + repr(other))
def __mul__(self, other):
if isinstance(other, (int, long)):
return EncodedSize(self.value * other, [str(other) + '*' + s for s in self.symbols])
else:
raise ValueError("Cannot multiply size: " + repr(other))
def __str__(self):
if not self.symbols:
return str(self.value)
else:
return '(' + str(self.value) + ' + ' + ' + '.join(self.symbols) + ')'
def upperlimit(self):
if not self.symbols:
return self.value
else:
return 2**32 - 1
class Enum:
def __init__(self, names, desc, enum_options):
'''desc is EnumDescriptorProto'''
@@ -113,6 +162,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 +191,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 +205,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 +331,51 @@ 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
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
# Include submessage length prefix
encsize += varint_max_size(encsize.upperlimit())
break
else:
# Submessage cannot be found, this currently occurs when
# the submessage type is defined in a different file.
# Instead of direct numeric value, reference the size that
# has been #defined in the other file.
encsize = EncodedSize(self.submsgname + 'size')
# We will have to make a conservative assumption on the length
# prefix size, though.
encsize += 5
elif self.enc_size is None:
raise RuntimeError("Could not determine encoded size for %s.%s"
% (self.struct_name, self.name))
else:
encsize = EncodedSize(self.enc_size)
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):
@@ -306,6 +405,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 EncodedSize(0)
class ExtensionField(Field):
def __init__(self, struct_name, desc, field_options):
self.fullname = struct_name + desc.name
@@ -318,6 +423,11 @@ class ExtensionField(Field):
self.skip = False
self.rules = 'OPTEXT'
def tags(self):
'''Return the #define for the tag number of this field.'''
identifier = '%s_tag' % self.fullname
return '#define %-40s %d\n' % (identifier, self.tag)
def extension_decl(self):
'''Declaration of the extension type in the .pb.h file'''
if self.skip:
@@ -429,6 +539,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 = EncodedSize(0)
for field in self.fields:
fsize = field.encoded_size(allmsgs)
if fsize is None:
return None
size += fsize
return size
# ---------------------------------------------------------------------------
@@ -592,13 +714,24 @@ def generate_header(dependencies, headername, enums, messages, extensions, optio
for msg in sort_dependencies(messages):
for field in msg.fields:
yield field.tags()
for extension in extensions:
yield extension.tags()
yield '\n'
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 %s\n' % (identifier, msize)
yield '\n'
yield '#ifdef __cplusplus\n'
yield '} /* extern "C" */\n'
yield '#endif\n'

43
pb.h
View File

@@ -43,7 +43,7 @@
/* Version of the nanopb library. Just in case you want to check it in
* your own program. */
#define NANOPB_VERSION nanopb-0.2.3
#define NANOPB_VERSION nanopb-0.2.5-dev
/* Include all the system headers needed by nanopb. You will need the
* definitions of the following:
@@ -173,6 +173,20 @@ typedef uint8_t pb_type_t;
#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK)
#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK)
/* Data type used for storing sizes of struct fields
* and array counts.
*/
#if defined(PB_FIELD_32BIT)
typedef uint32_t pb_size_t;
typedef int32_t pb_ssize_t;
#elif defined(PB_FIELD_16BIT)
typedef uint16_t pb_size_t;
typedef int16_t pb_ssize_t;
#else
typedef uint8_t pb_size_t;
typedef int8_t pb_ssize_t;
#endif
/* This structure is used in auto-generated constants
* to specify struct fields.
* You can change field sizes if you need structures
@@ -184,29 +198,12 @@ typedef uint8_t pb_type_t;
PB_PACKED_STRUCT_START
typedef struct _pb_field_t pb_field_t;
struct _pb_field_t {
#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
uint8_t tag;
pb_size_t tag;
pb_type_t type;
uint8_t data_offset; /* Offset of field data, relative to previous field. */
int8_t size_offset; /* Offset of array size or has-boolean, relative to data */
uint8_t data_size; /* Data size in bytes for a single item */
uint8_t array_size; /* Maximum number of entries in array */
#elif defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
uint16_t tag;
pb_type_t type;
uint8_t data_offset;
int8_t size_offset;
uint16_t data_size;
uint16_t array_size;
#else
uint32_t tag;
pb_type_t type;
uint8_t data_offset;
int8_t size_offset;
uint32_t data_size;
uint32_t array_size;
#endif
pb_size_t data_offset; /* Offset of field data, relative to previous field. */
pb_ssize_t size_offset; /* Offset of array size or has-boolean, relative to data */
pb_size_t data_size; /* Data size in bytes for a single item */
pb_size_t array_size; /* Maximum number of entries in array */
/* Field definitions for submessage
* OR default value for all other non-array, non-callback types

View File

@@ -3,11 +3,13 @@
* 2011 Petteri Aimonen <jpa@kapsi.fi>
*/
/* The warn_unused_result attribute appeared first in gcc-3.4.0 */
/* Use the GCC warn_unused_result attribute to check that all return values
* are propagated correctly. On other compilers and gcc before 3.4.0 just
* ignore the annotation.
*/
#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
#define checkreturn
#else
/* Verify that we remember to check all return values for proper error propagation */
#define checkreturn __attribute__((warn_unused_result))
#endif
@@ -15,26 +17,49 @@
#include "pb.h"
#include "pb_decode.h"
typedef bool (*pb_decoder_t)(pb_istream_t *stream, const pb_field_t *field, void *dest) checkreturn;
/**************************************
* Declarations internal to this file *
**************************************/
/* --- 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_fixed32,
&pb_dec_fixed64,
/* Iterator for pb_field_t list */
typedef struct {
const pb_field_t *start; /* Start of the pb_field_t array */
const pb_field_t *pos; /* Current position of the iterator */
unsigned field_index; /* Zero-based index of the field. */
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 *pData; /* Pointer where to store current field value */
void *pSize; /* Pointer where to store the size of current array field */
} pb_field_iterator_t;
&pb_dec_bytes,
&pb_dec_string,
&pb_dec_submessage,
NULL /* extensions */
};
/**************
* pb_istream *
**************/
static bool checkreturn buf_read(pb_istream_t *stream, uint8_t *buf, size_t count);
static bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest);
static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, uint8_t *buf, size_t *size);
static void pb_field_init(pb_field_iterator_t *iter, const pb_field_t *fields, void *dest_struct);
static bool pb_field_next(pb_field_iterator_t *iter);
static bool checkreturn pb_field_find(pb_field_iterator_t *iter, uint32_t tag);
static bool checkreturn decode_ltype(pb_istream_t *stream, const pb_field_t *field, void *dest, pb_type_t type);
static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iterator_t *iter);
static bool checkreturn decode_callback_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);
static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type);
static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_field_iterator_t *iter);
static bool checkreturn find_extension_field(pb_field_iterator_t *iter);
static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct);
static bool pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest);
static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest);
static bool checkreturn pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest);
static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest);
static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest);
static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest);
static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest);
static bool checkreturn pb_skip_varint(pb_istream_t *stream);
static bool checkreturn pb_skip_string(pb_istream_t *stream);
/*******************************
* pb_istream_t implementation *
*******************************/
static bool checkreturn buf_read(pb_istream_t *stream, uint8_t *buf, size_t count)
{
@@ -84,6 +109,26 @@ bool checkreturn pb_read(pb_istream_t *stream, uint8_t *buf, size_t count)
return true;
}
/* Read a single byte from input stream. buf may not be NULL.
* This is an optimization for the varint decoding. */
static bool checkreturn pb_readbyte(pb_istream_t *stream, uint8_t *buf)
{
if (!stream->bytes_left)
PB_RETURN_ERROR(stream, "end-of-stream");
#ifndef PB_BUFFER_ONLY
if (!stream->callback(stream, buf, 1))
PB_RETURN_ERROR(stream, "io error");
#else
*buf = *(uint8_t*)stream->state;
stream->state = (uint8_t*)stream->state + 1;
#endif
stream->bytes_left--;
return true;
}
pb_istream_t pb_istream_from_buffer(uint8_t *buf, size_t bufsize)
{
pb_istream_t stream;
@@ -109,7 +154,7 @@ static bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest)
uint8_t byte;
uint32_t result;
if (!pb_read(stream, &byte, 1))
if (!pb_readbyte(stream, &byte))
return false;
if (!(byte & 0x80))
@@ -128,7 +173,7 @@ static bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest)
if (bitpos >= 32)
PB_RETURN_ERROR(stream, "varint overflow");
if (!pb_read(stream, &byte, 1))
if (!pb_readbyte(stream, &byte))
return false;
result |= (uint32_t)(byte & 0x7F) << bitpos;
@@ -151,7 +196,7 @@ bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest)
if (bitpos >= 64)
PB_RETURN_ERROR(stream, "varint overflow");
if (!pb_read(stream, &byte, 1))
if (!pb_readbyte(stream, &byte))
return false;
result |= (uint64_t)(byte & 0x7F) << bitpos;
@@ -253,41 +298,25 @@ static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire
/* Decode string length from stream and return a substream with limited length.
* Remember to close the substream using pb_close_string_substream().
*/
bool checkreturn pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream)
bool checkreturn pb_make_string_substream(pb_istream_t *stream, size_t *remaining_length)
{
uint32_t size;
if (!pb_decode_varint32(stream, &size))
return false;
*substream = *stream;
if (substream->bytes_left < size)
if (stream->bytes_left < size)
PB_RETURN_ERROR(stream, "parent stream too short");
substream->bytes_left = size;
stream->bytes_left -= size;
*remaining_length = stream->bytes_left - size;
stream->bytes_left = size;
return true;
}
void pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream)
void pb_close_string_substream(pb_istream_t *stream, size_t remaining_length)
{
stream->state = substream->state;
#ifndef PB_NO_ERRMSG
stream->errmsg = substream->errmsg;
#endif
stream->bytes_left += remaining_length;
}
/* Iterator for pb_field_t list */
typedef struct {
const pb_field_t *start; /* Start of the pb_field_t array */
const pb_field_t *pos; /* Current position of the iterator */
unsigned field_index; /* Zero-based index of the field. */
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 *pData; /* Pointer where to store current field value */
void *pSize; /* Pointer where to store the size of current array field */
} pb_field_iterator_t;
static void pb_field_init(pb_field_iterator_t *iter, const pb_field_t *fields, void *dest_struct)
{
iter->start = iter->pos = fields;
@@ -352,22 +381,37 @@ static bool checkreturn pb_field_find(pb_field_iterator_t *iter, uint32_t tag)
* Decode a single field *
*************************/
/* Invoke a decoder function based on the ltype of a field. */
static bool checkreturn decode_ltype(pb_istream_t *stream,
const pb_field_t *field, void *dest, pb_type_t type)
{
switch (PB_LTYPE(type))
{
case PB_LTYPE_VARINT: return pb_dec_varint(stream, field, dest);
case PB_LTYPE_SVARINT: return pb_dec_svarint(stream, field, dest);
case PB_LTYPE_FIXED32: return pb_dec_fixed32(stream, field, dest);
case PB_LTYPE_FIXED64: return pb_dec_fixed64(stream, field, dest);
case PB_LTYPE_BYTES: return pb_dec_bytes(stream, field, dest);
case PB_LTYPE_STRING: return pb_dec_string(stream, field, dest);
case PB_LTYPE_SUBMESSAGE: return pb_dec_submessage(stream, field, dest);
default: PB_RETURN_ERROR(stream, "invalid field type");
}
}
static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iterator_t *iter)
{
pb_type_t type;
pb_decoder_t func;
type = iter->pos->type;
func = PB_DECODERS[PB_LTYPE(type)];
switch (PB_HTYPE(type))
{
case PB_HTYPE_REQUIRED:
return func(stream, iter->pos, iter->pData);
return decode_ltype(stream, iter->pos, iter->pData, type);
case PB_HTYPE_OPTIONAL:
*(bool*)iter->pSize = true;
return func(stream, iter->pos, iter->pData);
return decode_ltype(stream, iter->pos, iter->pData, type);
case PB_HTYPE_REPEATED:
if (wire_type == PB_WT_STRING
@@ -376,24 +420,25 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
/* Packed array */
bool status = true;
size_t *size = (size_t*)iter->pSize;
pb_istream_t substream;
if (!pb_make_string_substream(stream, &substream))
size_t remaining_length;
if (!pb_make_string_substream(stream, &remaining_length))
return false;
while (substream.bytes_left && *size < iter->pos->array_size)
while (stream->bytes_left)
{
void *pItem = (uint8_t*)iter->pData + iter->pos->data_size * (*size);
if (!func(&substream, iter->pos, pItem))
if (*size >= iter->pos->array_size)
PB_RETURN_ERROR(stream, "array overflow");
if (!decode_ltype(stream, iter->pos, pItem, type))
{
status = false;
break;
}
(*size)++;
}
pb_close_string_substream(stream, &substream);
if (substream.bytes_left != 0)
PB_RETURN_ERROR(stream, "array overflow");
pb_close_string_substream(stream, remaining_length);
return status;
}
@@ -406,7 +451,7 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
PB_RETURN_ERROR(stream, "array overflow");
(*size)++;
return func(stream, iter->pos, pItem);
return decode_ltype(stream, iter->pos, pItem, type);
}
default:
@@ -429,18 +474,18 @@ static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type
if (wire_type == PB_WT_STRING)
{
pb_istream_t substream;
size_t remaining_length;
if (!pb_make_string_substream(stream, &substream))
if (!pb_make_string_substream(stream, &remaining_length))
return false;
do
{
if (!pCallback->funcs.decode(&substream, iter->pos, arg))
if (!pCallback->funcs.decode(stream, iter->pos, arg))
PB_RETURN_ERROR(stream, "callback failed");
} while (substream.bytes_left);
} while (stream->bytes_left);
pb_close_string_substream(stream, &substream);
pb_close_string_substream(stream, remaining_length);
return true;
}
else
@@ -478,7 +523,7 @@ static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_t
/* Default handler for extension fields. Expects a pb_field_t structure
* in extension->type->arg. */
static bool checkreturn default_extension_handler(pb_istream_t *stream,
static bool checkreturn default_extension_decoder(pb_istream_t *stream,
pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type)
{
const pb_field_t *field = (const pb_field_t*)extension->type->arg;
@@ -513,7 +558,7 @@ static bool checkreturn decode_extension(pb_istream_t *stream,
if (extension->type->decode)
status = extension->type->decode(stream, extension, tag, wire_type);
else
status = default_extension_handler(stream, extension, tag, wire_type);
status = default_extension_decoder(stream, extension, tag, wire_type);
if (!status)
return false;
@@ -697,14 +742,14 @@ bool checkreturn pb_decode(pb_istream_t *stream, const pb_field_t fields[], void
bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
{
pb_istream_t substream;
size_t remaining_length;
bool status;
if (!pb_make_string_substream(stream, &substream))
if (!pb_make_string_substream(stream, &remaining_length))
return false;
status = pb_decode(&substream, fields, dest_struct);
pb_close_string_substream(stream, &substream);
status = pb_decode(stream, fields, dest_struct);
pb_close_string_substream(stream, remaining_length);
return status;
}
@@ -847,10 +892,10 @@ bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, vo
bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest)
{
bool status;
pb_istream_t substream;
size_t remaining_length;
const pb_field_t* submsg_fields = (const pb_field_t*)field->ptr;
if (!pb_make_string_substream(stream, &substream))
if (!pb_make_string_substream(stream, &remaining_length))
return false;
if (field->ptr == NULL)
@@ -859,10 +904,10 @@ bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field
/* New array entries need to be initialized, while required and optional
* 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);
status = pb_decode(stream, submsg_fields, dest);
else
status = pb_decode_noinit(&substream, submsg_fields, dest);
status = pb_decode_noinit(stream, submsg_fields, dest);
pb_close_string_substream(stream, &substream);
pb_close_string_substream(stream, remaining_length);
return status;
}

View File

@@ -128,27 +128,8 @@ bool pb_decode_fixed32(pb_istream_t *stream, void *dest);
bool pb_decode_fixed64(pb_istream_t *stream, void *dest);
/* Make a limited-length substream for reading a PB_WT_STRING field. */
bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream);
void pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream);
/*******************************
* Internal / legacy functions *
*******************************/
#ifdef NANOPB_INTERNALS
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_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest);
bool pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest);
bool pb_dec_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);
bool pb_skip_varint(pb_istream_t *stream);
bool pb_skip_string(pb_istream_t *stream);
#endif
bool pb_make_string_substream(pb_istream_t *stream, size_t *remaining_length);
void pb_close_string_substream(pb_istream_t *stream, size_t remaining_length);
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -7,32 +7,36 @@
#include "pb.h"
#include "pb_encode.h"
/* The warn_unused_result attribute appeared first in gcc-3.4.0 */
/* Use the GCC warn_unused_result attribute to check that all return values
* are propagated correctly. On other compilers and gcc before 3.4.0 just
* ignore the annotation.
*/
#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
#define checkreturn
#else
/* Verify that we remember to check all return values for proper error propagation */
#define checkreturn __attribute__((warn_unused_result))
#endif
typedef bool (*pb_encoder_t)(pb_ostream_t *stream, const pb_field_t *field, const void *src) checkreturn;
/**************************************
* Declarations internal to this file *
**************************************/
static bool checkreturn buf_write(pb_ostream_t *stream, const uint8_t *buf, size_t count);
static bool checkreturn encode_ltype(pb_ostream_t *stream, const pb_field_t *field, const void *src, pb_type_t type);
static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field, const void *pData, size_t count);
static bool checkreturn encode_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData);
static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension);
static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData);
static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src);
static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src);
static bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src);
static bool checkreturn pb_enc_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *src);
static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src);
static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src);
static bool checkreturn pb_enc_submessage(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_fixed32,
&pb_enc_fixed64,
&pb_enc_bytes,
&pb_enc_string,
&pb_enc_submessage,
NULL /* extensions */
};
/* pb_ostream_t implementation */
/*******************************
* pb_ostream_t implementation *
*******************************/
static bool checkreturn buf_write(pb_ostream_t *stream, const uint8_t *buf, size_t count)
{
@@ -49,7 +53,7 @@ pb_ostream_t pb_ostream_from_buffer(uint8_t *buf, size_t bufsize)
{
pb_ostream_t stream;
#ifdef PB_BUFFER_ONLY
stream.callback = (void*)1; /* Just some marker value */
stream.callback = (void*)1; /* Just a marker value */
#else
stream.callback = &buf_write;
#endif
@@ -82,11 +86,32 @@ bool checkreturn pb_write(pb_ostream_t *stream, const uint8_t *buf, size_t count
return true;
}
/* Main encoding stuff */
/*************************
* Encode a single field *
*************************/
/* Invoke an encoder function based on the ltype of a field. */
static bool checkreturn encode_ltype(pb_ostream_t *stream,
const pb_field_t *field, const void *src, pb_type_t type)
{
switch (PB_LTYPE(type))
{
case PB_LTYPE_VARINT: return pb_enc_varint(stream, field, src);
case PB_LTYPE_SVARINT: return pb_enc_svarint(stream, field, src);
case PB_LTYPE_FIXED32: return pb_enc_fixed32(stream, field, src);
case PB_LTYPE_FIXED64: return pb_enc_fixed64(stream, field, src);
case PB_LTYPE_BYTES: return pb_enc_bytes(stream, field, src);
case PB_LTYPE_STRING: return pb_enc_string(stream, field, src);
case PB_LTYPE_SUBMESSAGE: return pb_enc_submessage(stream, field, src);
default: PB_RETURN_ERROR(stream, "invalid field type");
}
}
/* Encode a static array. Handles the size calculations and possible packing. */
static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field,
const void *pData, size_t count, pb_encoder_t func)
const void *pData, size_t count)
{
size_t i;
const void *p;
@@ -95,6 +120,9 @@ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *fie
if (count == 0)
return true;
if (count > field->array_size)
PB_RETURN_ERROR(stream, "array max size exceeded");
/* We always pack arrays if the datatype allows it. */
if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE)
{
@@ -116,7 +144,7 @@ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *fie
p = pData;
for (i = 0; i < count; i++)
{
if (!func(&sizestream, field, p))
if (!encode_ltype(&sizestream, field, p, field->type))
return false;
p = (const char*)p + field->data_size;
}
@@ -133,7 +161,7 @@ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *fie
p = pData;
for (i = 0; i < count; i++)
{
if (!func(stream, field, p))
if (!encode_ltype(stream, field, p, field->type))
return false;
p = (const char*)p + field->data_size;
}
@@ -145,7 +173,7 @@ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *fie
{
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!func(stream, field, p))
if (!encode_ltype(stream, field, p, field->type))
return false;
p = (const char*)p + field->data_size;
}
@@ -159,12 +187,9 @@ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *fie
static bool checkreturn encode_static_field(pb_ostream_t *stream,
const pb_field_t *field, const void *pData)
{
pb_encoder_t func;
const void *pSize;
bool dummy = true;
func = PB_ENCODERS[PB_LTYPE(field->type)];
if (field->size_offset)
pSize = (const char*)pData + field->size_offset;
else
@@ -175,7 +200,7 @@ static bool checkreturn encode_static_field(pb_ostream_t *stream,
case PB_HTYPE_REQUIRED:
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!func(stream, field, pData))
if (!encode_ltype(stream, field, pData, field->type))
return false;
break;
@@ -185,13 +210,13 @@ static bool checkreturn encode_static_field(pb_ostream_t *stream,
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!func(stream, field, pData))
if (!encode_ltype(stream, field, pData, field->type))
return false;
}
break;
case PB_HTYPE_REPEATED:
if (!encode_array(stream, field, pData, *(const size_t*)pSize, func))
if (!encode_array(stream, field, pData, *(const size_t*)pSize))
return false;
break;
@@ -242,7 +267,7 @@ static bool checkreturn encode_field(pb_ostream_t *stream,
/* Default handler for extension fields. Expects to have a pb_field_t
* pointer in the extension->type->arg field. */
static bool checkreturn default_extension_handler(pb_ostream_t *stream,
static bool checkreturn default_extension_encoder(pb_ostream_t *stream,
const pb_extension_t *extension)
{
const pb_field_t *field = (const pb_field_t*)extension->type->arg;
@@ -263,7 +288,7 @@ static bool checkreturn encode_extension_field(pb_ostream_t *stream,
if (extension->type->encode)
status = extension->type->encode(stream, extension);
else
status = default_extension_handler(stream, extension);
status = default_extension_encoder(stream, extension);
if (!status)
return false;
@@ -274,6 +299,10 @@ static bool checkreturn encode_extension_field(pb_ostream_t *stream,
return true;
}
/*********************
* Encode all fields *
*********************/
bool checkreturn pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct)
{
const pb_field_t *field = fields;
@@ -316,7 +345,9 @@ bool pb_encode_delimited(pb_ostream_t *stream, const pb_field_t fields[], const
return pb_encode_submessage(stream, fields, src_struct);
}
/* Helper functions */
/********************
* Helper functions *
********************/
bool checkreturn pb_encode_varint(pb_ostream_t *stream, uint64_t value)
{
uint8_t buffer[10];
@@ -518,7 +549,10 @@ bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, c
bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{
const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)src;
UNUSED(field);
if (bytes->size + offsetof(pb_bytes_array_t, bytes) > field->data_size)
PB_RETURN_ERROR(stream, "bytes size exceeded");
return pb_encode_string(stream, bytes->bytes, bytes->size);
}

View File

@@ -143,24 +143,6 @@ bool pb_encode_fixed64(pb_ostream_t *stream, const void *value);
*/
bool pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct);
/*******************************
* Internal / legacy functions *
*******************************/
#ifdef NANOPB_INTERNALS
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_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src);
bool pb_enc_fixed64(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);
#endif
/* This function is not recommended for new programs. Use pb_encode_submessage()
* instead, it has the same functionality with a less confusing interface. */
bool pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src);
#ifdef __cplusplus
} /* extern "C" */
#endif

View File

@@ -26,7 +26,7 @@ if 'CXXFLAGS' in ARGUMENTS: env.Append(CCFLAGS = ARGUMENTS['CXXFLAGS'])
add_nanopb_builders(env)
# Path to the files shared by tests, and to the nanopb core.
env.Append(CPPPATH = ["#../", "#common"])
env.Append(CPPPATH = ["#../", "$COMMON"])
# Path for finding nanopb.proto
env.Append(PROTOCPATH = '#../generator')
@@ -78,6 +78,8 @@ if 'gcc' in env['CC']:
# More strict checks on the nanopb core
env.Append(CORECFLAGS = '-Wextra -Wcast-qual -Wlogical-op -Wconversion')
env.Append(CORECFLAGS = ' -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls')
env.Append(CORECFLAGS = ' -Wstack-protector')
elif 'clang' in env['CC']:
# CLang
env.Append(CFLAGS = '-ansi -g -O0 -Wall -Werror')
@@ -108,5 +110,10 @@ elif 'cl' in env['CXX']:
env.Append(CXXFLAGS = '/Zi /W2 /WX')
# Now include the SConscript files from all subdirectories
SConscript(Glob('*/SConscript'), exports = 'env')
import os.path
env['VARIANT_DIR'] = 'build'
env['BUILD'] = '#' + env['VARIANT_DIR']
env['COMMON'] = '#' + env['VARIANT_DIR'] + '/common'
for subdir in Glob('*/SConscript'):
SConscript(subdir, exports = 'env', variant_dir = env['VARIANT_DIR'] + '/' + os.path.dirname(str(subdir)))

View File

@@ -3,9 +3,9 @@
Import("env")
env.NanopbProto("alltypes")
enc = env.Program(["encode_alltypes.c", "alltypes.pb.c", "#common/pb_encode.o"])
dec = env.Program(["decode_alltypes.c", "alltypes.pb.c", "#common/pb_decode.o"])
env.NanopbProto(["alltypes", "alltypes.options"])
enc = env.Program(["encode_alltypes.c", "alltypes.pb.c", "$COMMON/pb_encode.o"])
dec = env.Program(["decode_alltypes.c", "alltypes.pb.c", "$COMMON/pb_decode.o"])
env.RunTest(enc)
env.RunTest([dec, "encode_alltypes.output"])

View File

@@ -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. */

View File

@@ -3,8 +3,8 @@
Import("env")
enc = env.Program(["encode_legacy.c", "alltypes_legacy.c", "#common/pb_encode.o"])
dec = env.Program(["decode_legacy.c", "alltypes_legacy.c", "#common/pb_decode.o"])
enc = env.Program(["encode_legacy.c", "alltypes_legacy.c", "$COMMON/pb_encode.o"])
dec = env.Program(["decode_legacy.c", "alltypes_legacy.c", "$COMMON/pb_decode.o"])
env.RunTest(enc)
env.RunTest([dec, "encode_legacy.output"])

View File

@@ -2,11 +2,11 @@
Import("env")
enc = env.Program(["encode_buffer.c", "#common/person.pb.c", "#common/pb_encode.o"])
dec = env.Program(["decode_buffer.c", "#common/person.pb.c", "#common/pb_decode.o"])
enc = env.Program(["encode_buffer.c", "$COMMON/person.pb.c", "$COMMON/pb_encode.o"])
dec = env.Program(["decode_buffer.c", "$COMMON/person.pb.c", "$COMMON/pb_decode.o"])
env.RunTest(enc)
env.RunTest([dec, "encode_buffer.output"])
env.Decode(["encode_buffer.output", "#common/person.proto"], MESSAGE = "Person")
env.Decode(["encode_buffer.output", "$COMMON/person.proto"], MESSAGE = "Person")
env.Compare(["decode_buffer.output", "encode_buffer.decoded"])

View File

@@ -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;

View File

@@ -10,7 +10,7 @@
int main()
{
uint8_t buffer[512];
uint8_t buffer[Person_size];
pb_ostream_t stream;
/* Initialize the structure with constants */

View File

@@ -2,11 +2,11 @@
Import("env")
enc = env.Program(["encode_stream.c", "#common/person.pb.c", "#common/pb_encode.o"])
dec = env.Program(["decode_stream.c", "#common/person.pb.c", "#common/pb_decode.o"])
enc = env.Program(["encode_stream.c", "$COMMON/person.pb.c", "$COMMON/pb_encode.o"])
dec = env.Program(["decode_stream.c", "$COMMON/person.pb.c", "$COMMON/pb_decode.o"])
env.RunTest(enc)
env.RunTest([dec, "encode_stream.output"])
env.Decode(["encode_stream.output", "#common/person.proto"], MESSAGE = "Person")
env.Decode(["encode_stream.output", "$COMMON/person.proto"], MESSAGE = "Person")
env.Compare(["decode_stream.output", "encode_stream.decoded"])

View File

@@ -6,10 +6,10 @@ Import("env")
c = Copy("$TARGET", "$SOURCE")
env.Command("pb_encode.c", "#../pb_encode.c", c)
env.Command("pb_decode.c", "#../pb_decode.c", c)
env.Command("alltypes.pb.h", "#alltypes/alltypes.pb.h", c)
env.Command("alltypes.pb.c", "#alltypes/alltypes.pb.c", c)
env.Command("encode_alltypes.c", "#alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "#alltypes/decode_alltypes.c", c)
env.Command("alltypes.pb.h", "$BUILD/alltypes/alltypes.pb.h", c)
env.Command("alltypes.pb.c", "$BUILD/alltypes/alltypes.pb.c", c)
env.Command("encode_alltypes.c", "$BUILD/alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "$BUILD/alltypes/decode_alltypes.c", c)
# Define the compilation options
opts = env.Clone()

View File

@@ -3,8 +3,8 @@
Import("env")
env.NanopbProto("callbacks")
enc = env.Program(["encode_callbacks.c", "callbacks.pb.c", "#common/pb_encode.o"])
dec = env.Program(["decode_callbacks.c", "callbacks.pb.c", "#common/pb_decode.o"])
enc = env.Program(["encode_callbacks.c", "callbacks.pb.c", "$COMMON/pb_encode.o"])
dec = env.Program(["decode_callbacks.c", "callbacks.pb.c", "$COMMON/pb_decode.o"])
env.RunTest(enc)
env.RunTest([dec, "encode_callbacks.output"])

View File

@@ -12,6 +12,10 @@ message StringMessage {
required string data = 1 [(nanopb).max_size = 10];
}
message BytesMessage {
required bytes data = 1 [(nanopb).max_size = 16];
}
message CallbackArray {
// We cheat a bit and use this message for testing other types, too.
// Nanopb does not care about the actual defined data type for callback

View File

@@ -7,10 +7,10 @@ Import("env")
c = Copy("$TARGET", "$SOURCE")
env.Command("pb_encode.cxx", "#../pb_encode.c", c)
env.Command("pb_decode.cxx", "#../pb_decode.c", c)
env.Command("alltypes.pb.h", "#alltypes/alltypes.pb.h", c)
env.Command("alltypes.pb.cxx", "#alltypes/alltypes.pb.c", c)
env.Command("encode_alltypes.cxx", "#alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.cxx", "#alltypes/decode_alltypes.c", c)
env.Command("alltypes.pb.h", "$BUILD/alltypes/alltypes.pb.h", c)
env.Command("alltypes.pb.cxx", "$BUILD/alltypes/alltypes.pb.c", c)
env.Command("encode_alltypes.cxx", "$BUILD/alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.cxx", "$BUILD/alltypes/decode_alltypes.c", c)
# Now build and run the test normally.
enc = env.Program(["encode_alltypes.cxx", "alltypes.pb.cxx", "pb_encode.cxx"])

View File

@@ -1,4 +1,4 @@
Import('env')
p = env.Program(["decode_unittests.c", "#common/unittestproto.pb.c", "#common/pb_decode.o"])
p = env.Program(["decode_unittests.c", "$COMMON/unittestproto.pb.c"])
env.RunTest(p)

View File

@@ -1,8 +1,8 @@
#define NANOPB_INTERNALS
/* This includes the whole .c file to get access to static functions. */
#include "pb_decode.c"
#include <stdio.h>
#include <string.h>
#include "pb_decode.h"
#include "unittests.h"
#include "unittestproto.pb.h"

View File

@@ -1,5 +1,5 @@
# Build and run the stand-alone unit tests for the nanopb encoder part.
Import('env')
p = env.Program(["encode_unittests.c", "#common/unittestproto.pb.c", "#common/pb_encode.o"])
p = env.Program(["encode_unittests.c", "$COMMON/unittestproto.pb.c"])
env.RunTest(p)

View File

@@ -1,8 +1,8 @@
#define NANOPB_INTERNALS
/* This includes the whole .c file to get access to static functions. */
#include "pb_encode.c"
#include <stdio.h>
#include <string.h>
#include "pb_encode.h"
#include "unittests.h"
#include "unittestproto.pb.h"
@@ -172,9 +172,9 @@ int main()
struct { size_t size; uint8_t bytes[5]; } value = {5, {'x', 'y', 'z', 'z', 'y'}};
COMMENT("Test pb_enc_bytes")
TEST(WRITES(pb_enc_bytes(&s, NULL, &value), "\x05xyzzy"))
TEST(WRITES(pb_enc_bytes(&s, &BytesMessage_fields[0], &value), "\x05xyzzy"))
value.size = 0;
TEST(WRITES(pb_enc_bytes(&s, NULL, &value), "\x00"))
TEST(WRITES(pb_enc_bytes(&s, &BytesMessage_fields[0], &value), "\x00"))
}
{
@@ -223,6 +223,20 @@ int main()
TEST(!pb_encode(&s, FloatArray_fields, &msg))
}
{
uint8_t buffer[50];
pb_ostream_t s;
FloatArray msg = {1, {99.0f}};
COMMENT("Test array size limit in pb_encode")
s = pb_ostream_from_buffer(buffer, sizeof(buffer));
TEST((msg.data_count = 10) && pb_encode(&s, FloatArray_fields, &msg))
s = pb_ostream_from_buffer(buffer, sizeof(buffer));
TEST((msg.data_count = 11) && !pb_encode(&s, FloatArray_fields, &msg))
}
{
uint8_t buffer[10];
pb_ostream_t s;
@@ -244,6 +258,20 @@ int main()
"\x0A\x07\x0A\x05\x01\x02\x03\x04\x05"))
}
{
uint8_t buffer[32];
pb_ostream_t s;
BytesMessage msg = {{3, "xyz"}};
COMMENT("Test pb_encode with bytes message.")
TEST(WRITES(pb_encode(&s, BytesMessage_fields, &msg),
"\x0A\x03xyz"))
msg.data.size = 17; /* More than maximum */
TEST(!pb_encode(&s, BytesMessage_fields, &msg))
}
{
uint8_t buffer[20];
pb_ostream_t s;
@@ -280,6 +308,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");

View File

@@ -4,12 +4,12 @@ Import("env")
# We use the files from the alltypes test case
incpath = env.Clone()
incpath.Append(PROTOCPATH = '#alltypes')
incpath.Append(CPPPATH = '#alltypes')
incpath.Append(PROTOCPATH = '$BUILD/alltypes')
incpath.Append(CPPPATH = '$BUILD/alltypes')
incpath.NanopbProto("extensions")
enc = incpath.Program(["encode_extensions.c", "extensions.pb.c", "#alltypes/alltypes.pb$OBJSUFFIX", "#common/pb_encode.o"])
dec = incpath.Program(["decode_extensions.c", "extensions.pb.c", "#alltypes/alltypes.pb$OBJSUFFIX", "#common/pb_decode.o"])
incpath.NanopbProto(["extensions", "extensions.options"])
enc = incpath.Program(["encode_extensions.c", "extensions.pb.c", "$BUILD/alltypes/alltypes.pb$OBJSUFFIX", "$COMMON/pb_encode.o"])
dec = incpath.Program(["decode_extensions.c", "extensions.pb.c", "$BUILD/alltypes/alltypes.pb$OBJSUFFIX", "$COMMON/pb_decode.o"])
env.RunTest(enc)
env.RunTest([dec, "encode_extensions.output"])

View File

@@ -46,5 +46,9 @@ int main(int argc, char **argv)
fprintf(stderr, "Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1; /* Failure */
}
/* Check that the field tags are properly generated */
(void)AllTypes_extensionfield1_tag;
(void)ExtensionMessage_AllTypes_extensionfield2_tag;
}

View File

@@ -2,13 +2,13 @@
Import("env")
dec = env.GetBuildPath('#basic_buffer/${PROGPREFIX}decode_buffer${PROGSUFFIX}')
dec = env.GetBuildPath('$BUILD/basic_buffer/${PROGPREFIX}decode_buffer${PROGSUFFIX}')
env.RunTest('person_with_extra_field.output', [dec, "person_with_extra_field.pb"])
env.Compare(["person_with_extra_field.output", "person_with_extra_field.expected"])
dec = env.GetBuildPath('#basic_stream/${PROGPREFIX}decode_stream${PROGSUFFIX}')
dec = env.GetBuildPath('$BUILD/basic_stream/${PROGPREFIX}decode_stream${PROGSUFFIX}')
env.RunTest('person_with_extra_field_stream.output', [dec, "person_with_extra_field.pb"])
env.Compare(["person_with_extra_field_stream.output", "person_with_extra_field.expected"])
dec2 = env.GetBuildPath('#alltypes/${PROGPREFIX}decode_alltypes${PROGSUFFIX}')
dec2 = env.GetBuildPath('$BUILD/alltypes/${PROGPREFIX}decode_alltypes${PROGSUFFIX}')
env.RunTest('alltypes_with_extra_fields.output', [dec2, 'alltypes_with_extra_fields.pb'])

View File

@@ -7,10 +7,10 @@ Import("env")
c = Copy("$TARGET", "$SOURCE")
env.Command("pb_encode.c", "#../pb_encode.c", c)
env.Command("pb_decode.c", "#../pb_decode.c", c)
env.Command("encode_alltypes.c", "#alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "#alltypes/decode_alltypes.c", c)
env.Command("encode_alltypes.c", "$BUILD/alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "$BUILD/alltypes/decode_alltypes.c", c)
env.NanopbProto("alltypes")
env.NanopbProto(["alltypes", "alltypes.options"])
# Define the compilation options
opts = env.Clone()

View File

@@ -7,10 +7,10 @@ Import("env")
c = Copy("$TARGET", "$SOURCE")
env.Command("pb_encode.c", "#../pb_encode.c", c)
env.Command("pb_decode.c", "#../pb_decode.c", c)
env.Command("encode_alltypes.c", "#alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "#alltypes/decode_alltypes.c", c)
env.Command("encode_alltypes.c", "$BUILD/alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "$BUILD/alltypes/decode_alltypes.c", c)
env.NanopbProto("alltypes")
env.NanopbProto(["alltypes", "alltypes.options"])
# Define the compilation options
opts = env.Clone()

View File

@@ -0,0 +1,11 @@
# Test the generation of message size #defines
Import('env')
incpath = env.Clone()
incpath.Append(PROTOCPATH = '#message_sizes')
incpath.NanopbProto("messages1")
incpath.NanopbProto("messages2")
incpath.Program(['dummy.c', 'messages1.pb.c', 'messages2.pb.c'])

View File

@@ -0,0 +1,9 @@
/* Just test that the file can be compiled successfully. */
#include "messages2.pb.h"
int main()
{
return xmit_size;
}

View File

@@ -0,0 +1,27 @@
enum MessageStatus {
FAIL = 0;
OK = 1;
};
message MessageInfo {
required fixed32 msg_id = 1;
optional fixed32 interface_id = 2;
}
message MessageResponseInfo {
required fixed64 interface_id = 1;
required fixed32 seq = 2;
required fixed32 msg_id = 3;
}
message MessageHeader {
required MessageInfo info = 1;
optional MessageResponseInfo response_info = 2;
optional MessageResponse response = 3;
}
message MessageResponse {
required MessageStatus status = 1;
required fixed32 seq = 2;
}

View File

@@ -0,0 +1,8 @@
import 'nanopb.proto';
import 'messages1.proto';
message xmit {
required MessageHeader header = 1;
required bytes data = 2 [(nanopb).max_size = 128];
}

View File

@@ -3,6 +3,6 @@
Import("env")
env.NanopbProto("missing_fields")
test = env.Program(["missing_fields.c", "missing_fields.pb.c", "#common/pb_encode.o", "#common/pb_decode.o"])
test = env.Program(["missing_fields.c", "missing_fields.pb.c", "$COMMON/pb_encode.o", "$COMMON/pb_decode.o"])
env.RunTest(test)

View File

@@ -6,10 +6,10 @@ Import("env")
c = Copy("$TARGET", "$SOURCE")
env.Command("pb_encode.c", "#../pb_encode.c", c)
env.Command("pb_decode.c", "#../pb_decode.c", c)
env.Command("alltypes.pb.h", "#alltypes/alltypes.pb.h", c)
env.Command("alltypes.pb.c", "#alltypes/alltypes.pb.c", c)
env.Command("encode_alltypes.c", "#alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "#alltypes/decode_alltypes.c", c)
env.Command("alltypes.pb.h", "$BUILD/alltypes/alltypes.pb.h", c)
env.Command("alltypes.pb.c", "$BUILD/alltypes/alltypes.pb.c", c)
env.Command("encode_alltypes.c", "$BUILD/alltypes/encode_alltypes.c", c)
env.Command("decode_alltypes.c", "$BUILD/alltypes/decode_alltypes.c", c)
# Define the compilation options
opts = env.Clone()