Add int_size option for generator.
This allows overriding the integer field types to e.g. uint8_t for saving RAM. Update issue 139 Status: FixedInGit
This commit is contained in:
@@ -76,6 +76,8 @@ The generator behaviour can be adjusted using these options, defined in the
|
||||
max_size Allocated size for *bytes* and *string* fields.
|
||||
max_count Allocated number of entries in arrays
|
||||
(*repeated* fields).
|
||||
int_size Override the integer type of a field.
|
||||
(To use e.g. uint8_t to save RAM.)
|
||||
type Type of the generated field. Default value
|
||||
is *FT_DEFAULT*, which selects automatically.
|
||||
You can use *FT_CALLBACK*, *FT_POINTER*,
|
||||
@@ -88,6 +90,7 @@ long_names Prefix the enum name to the enum value in
|
||||
packed_struct Make the generated structures packed.
|
||||
NOTE: This cannot be used on CPUs that break
|
||||
on unaligned accesses to variables.
|
||||
skip_message Skip the whole message from generation.
|
||||
============================ ================================================
|
||||
|
||||
These options can be defined for the .proto files before they are converted
|
||||
|
||||
@@ -44,22 +44,30 @@ except:
|
||||
import time
|
||||
import os.path
|
||||
|
||||
# Values are tuple (c type, pb type, encoded size)
|
||||
# Values are tuple (c type, pb type, encoded size, int_size_allowed)
|
||||
FieldD = descriptor.FieldDescriptorProto
|
||||
datatypes = {
|
||||
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', 10),
|
||||
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)
|
||||
FieldD.TYPE_BOOL: ('bool', 'BOOL', 1, False),
|
||||
FieldD.TYPE_DOUBLE: ('double', 'DOUBLE', 8, False),
|
||||
FieldD.TYPE_FIXED32: ('uint32_t', 'FIXED32', 4, False),
|
||||
FieldD.TYPE_FIXED64: ('uint64_t', 'FIXED64', 8, False),
|
||||
FieldD.TYPE_FLOAT: ('float', 'FLOAT', 4, False),
|
||||
FieldD.TYPE_INT32: ('int32_t', 'INT32', 10, True),
|
||||
FieldD.TYPE_INT64: ('int64_t', 'INT64', 10, True),
|
||||
FieldD.TYPE_SFIXED32: ('int32_t', 'SFIXED32', 4, False),
|
||||
FieldD.TYPE_SFIXED64: ('int64_t', 'SFIXED64', 8, False),
|
||||
FieldD.TYPE_SINT32: ('int32_t', 'SINT32', 5, True),
|
||||
FieldD.TYPE_SINT64: ('int64_t', 'SINT64', 10, True),
|
||||
FieldD.TYPE_UINT32: ('uint32_t', 'UINT32', 5, True),
|
||||
FieldD.TYPE_UINT64: ('uint64_t', 'UINT64', 10, True)
|
||||
}
|
||||
|
||||
# Integer size overrides (from .proto settings)
|
||||
intsizes = {
|
||||
nanopb_pb2.IS_8: 'int8_t',
|
||||
nanopb_pb2.IS_16: 'int16_t',
|
||||
nanopb_pb2.IS_32: 'int32_t',
|
||||
nanopb_pb2.IS_64: 'int64_t',
|
||||
}
|
||||
|
||||
class Names:
|
||||
@@ -226,7 +234,13 @@ class Field:
|
||||
|
||||
# Decide the C data type to use in the struct.
|
||||
if datatypes.has_key(desc.type):
|
||||
self.ctype, self.pbtype, self.enc_size = datatypes[desc.type]
|
||||
self.ctype, self.pbtype, self.enc_size, isa = datatypes[desc.type]
|
||||
|
||||
# Override the field size if user wants to use smaller integers
|
||||
if isa and field_options.int_size != nanopb_pb2.IS_DEFAULT:
|
||||
self.ctype = intsizes[field_options.int_size]
|
||||
if desc.type == FieldD.TYPE_UINT32 or desc.type == FieldD.TYPE_UINT64:
|
||||
self.ctype = 'u' + self.ctype;
|
||||
elif desc.type == FieldD.TYPE_ENUM:
|
||||
self.pbtype = 'ENUM'
|
||||
self.ctype = names_from_type_name(desc.type_name)
|
||||
|
||||
@@ -18,6 +18,14 @@ enum FieldType {
|
||||
FT_IGNORE = 3; // Ignore the field completely.
|
||||
}
|
||||
|
||||
enum IntSize {
|
||||
IS_DEFAULT = 0; // Default, 32/64bit based on type in .proto
|
||||
IS_8 = 1;
|
||||
IS_16 = 2;
|
||||
IS_32 = 3;
|
||||
IS_64 = 4;
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -28,6 +36,10 @@ message NanoPBOptions {
|
||||
// Allocated number of entries in arrays ('repeated' fields)
|
||||
optional int32 max_count = 2;
|
||||
|
||||
// Size of integer fields. Can save some memory if you don't need
|
||||
// full 32 bits for the value.
|
||||
optional IntSize int_size = 7 [default = IS_DEFAULT];
|
||||
|
||||
// Force type of field (callback or static allocation)
|
||||
optional FieldType type = 3 [default = FT_DEFAULT];
|
||||
|
||||
|
||||
@@ -1095,6 +1095,8 @@ static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *f
|
||||
|
||||
switch (field->data_size)
|
||||
{
|
||||
case 1: *(uint8_t*)dest = (uint8_t)value; break;
|
||||
case 2: *(uint16_t*)dest = (uint16_t)value; break;
|
||||
case 4: *(uint32_t*)dest = (uint32_t)value; break;
|
||||
case 8: *(uint64_t*)dest = value; break;
|
||||
default: PB_RETURN_ERROR(stream, "invalid data_size");
|
||||
@@ -1111,6 +1113,8 @@ static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *f
|
||||
|
||||
switch (field->data_size)
|
||||
{
|
||||
case 1: *(int8_t*)dest = (int8_t)value; break;
|
||||
case 2: *(int16_t*)dest = (int16_t)value; break;
|
||||
case 4: *(int32_t*)dest = (int32_t)value; break;
|
||||
case 8: *(int64_t*)dest = value; break;
|
||||
default: PB_RETURN_ERROR(stream, "invalid data_size");
|
||||
|
||||
@@ -567,7 +567,7 @@ static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *fi
|
||||
int64_t value = 0;
|
||||
|
||||
/* Cases 1 and 2 are for compilers that have smaller types for bool
|
||||
* or enums. */
|
||||
* or enums, and for int_size option. */
|
||||
switch (field->data_size)
|
||||
{
|
||||
case 1: value = *(const int8_t*)src; break;
|
||||
@@ -586,6 +586,8 @@ static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *f
|
||||
|
||||
switch (field->data_size)
|
||||
{
|
||||
case 1: value = *(const uint8_t*)src; break;
|
||||
case 2: value = *(const uint16_t*)src; break;
|
||||
case 4: value = *(const uint32_t*)src; break;
|
||||
case 8: value = *(const uint64_t*)src; break;
|
||||
default: PB_RETURN_ERROR(stream, "invalid data_size");
|
||||
@@ -600,6 +602,8 @@ static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *f
|
||||
|
||||
switch (field->data_size)
|
||||
{
|
||||
case 1: value = *(const int8_t*)src; break;
|
||||
case 2: value = *(const int16_t*)src; break;
|
||||
case 4: value = *(const int32_t*)src; break;
|
||||
case 8: value = *(const int64_t*)src; break;
|
||||
default: PB_RETURN_ERROR(stream, "invalid data_size");
|
||||
|
||||
12
tests/intsizes/SConscript
Normal file
12
tests/intsizes/SConscript
Normal file
@@ -0,0 +1,12 @@
|
||||
# Test that the int_size option in .proto works.
|
||||
|
||||
Import('env')
|
||||
|
||||
env.NanopbProto('intsizes')
|
||||
|
||||
p = env.Program(["intsizes_unittests.c",
|
||||
"intsizes.pb.c",
|
||||
"$COMMON/pb_encode.o",
|
||||
"$COMMON/pb_decode.o",
|
||||
"$COMMON/pb_common.o"])
|
||||
env.RunTest(p)
|
||||
39
tests/intsizes/intsizes.proto
Normal file
39
tests/intsizes/intsizes.proto
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Test the integer size overriding in nanopb options.
|
||||
* This allows to use 8- and 16-bit integer variables, which are not supported
|
||||
* directly by Google Protobuf.
|
||||
*
|
||||
* The int_size setting will override the number of bits, but keep the type
|
||||
* otherwise. E.g. uint32 + IS_8 => uint8_t
|
||||
*/
|
||||
|
||||
import 'nanopb.proto';
|
||||
|
||||
message IntSizes {
|
||||
required int32 req_int8 = 1 [(nanopb).int_size = IS_8];
|
||||
required uint32 req_uint8 = 2 [(nanopb).int_size = IS_8];
|
||||
required sint32 req_sint8 = 3 [(nanopb).int_size = IS_8];
|
||||
required int32 req_int16 = 4 [(nanopb).int_size = IS_16];
|
||||
required uint32 req_uint16 = 5 [(nanopb).int_size = IS_16];
|
||||
required sint32 req_sint16 = 6 [(nanopb).int_size = IS_16];
|
||||
required int32 req_int32 = 7 [(nanopb).int_size = IS_32];
|
||||
required uint32 req_uint32 = 8 [(nanopb).int_size = IS_32];
|
||||
required sint32 req_sint32 = 9 [(nanopb).int_size = IS_32];
|
||||
required int32 req_int64 = 10 [(nanopb).int_size = IS_64];
|
||||
required uint32 req_uint64 = 11 [(nanopb).int_size = IS_64];
|
||||
required sint32 req_sint64 = 12 [(nanopb).int_size = IS_64];
|
||||
}
|
||||
|
||||
message DefaultSizes {
|
||||
required int32 req_int8 = 1 ;
|
||||
required uint32 req_uint8 = 2 ;
|
||||
required sint32 req_sint8 = 3 ;
|
||||
required int32 req_int16 = 4 ;
|
||||
required uint32 req_uint16 = 5 ;
|
||||
required sint32 req_sint16 = 6 ;
|
||||
required int32 req_int32 = 7 ;
|
||||
required uint32 req_uint32 = 8 ;
|
||||
required sint32 req_sint32 = 9 ;
|
||||
required int64 req_int64 = 10;
|
||||
required uint64 req_uint64 = 11;
|
||||
required sint64 req_sint64 = 12;
|
||||
}
|
||||
108
tests/intsizes/intsizes_unittests.c
Normal file
108
tests/intsizes/intsizes_unittests.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include "unittests.h"
|
||||
#include "intsizes.pb.h"
|
||||
|
||||
#define S(x) pb_istream_from_buffer((uint8_t*)x, sizeof(x) - 1)
|
||||
|
||||
/* This is a macro instead of function in order to get the actual values
|
||||
* into the TEST() lines in output */
|
||||
#define TEST_ROUNDTRIP(int8, uint8, sint8, \
|
||||
int16, uint16, sint16, \
|
||||
int32, uint32, sint32, \
|
||||
int64, uint64, sint64, expected_result) \
|
||||
{ \
|
||||
uint8_t buffer1[128], buffer2[128]; \
|
||||
size_t msgsize; \
|
||||
DefaultSizes msg1 = DefaultSizes_init_zero; \
|
||||
IntSizes msg2 = IntSizes_init_zero; \
|
||||
\
|
||||
msg1.req_int8 = int8; \
|
||||
msg1.req_uint8 = uint8; \
|
||||
msg1.req_sint8 = sint8; \
|
||||
msg1.req_int16 = int16; \
|
||||
msg1.req_uint16 = uint16; \
|
||||
msg1.req_sint16 = sint16; \
|
||||
msg1.req_int32 = int32; \
|
||||
msg1.req_uint32 = uint32; \
|
||||
msg1.req_sint32 = sint32; \
|
||||
msg1.req_int64 = int64; \
|
||||
msg1.req_uint64 = uint64; \
|
||||
msg1.req_sint64 = sint64; \
|
||||
\
|
||||
{ \
|
||||
pb_ostream_t s = pb_ostream_from_buffer(buffer1, sizeof(buffer1)); \
|
||||
TEST(pb_encode(&s, DefaultSizes_fields, &msg1)); \
|
||||
msgsize = s.bytes_written; \
|
||||
} \
|
||||
\
|
||||
{ \
|
||||
pb_istream_t s = pb_istream_from_buffer(buffer1, msgsize); \
|
||||
TEST(pb_decode(&s, IntSizes_fields, &msg2) == expected_result); \
|
||||
if (expected_result) \
|
||||
{ \
|
||||
TEST(msg2.req_int8 == int8); \
|
||||
TEST(msg2.req_uint8 == uint8); \
|
||||
TEST(msg2.req_sint8 == sint8); \
|
||||
TEST(msg2.req_int16 == int16); \
|
||||
TEST(msg2.req_uint16 == uint16); \
|
||||
TEST(msg2.req_sint16 == sint16); \
|
||||
TEST(msg2.req_int32 == int32); \
|
||||
TEST(msg2.req_uint32 == uint32); \
|
||||
TEST(msg2.req_sint32 == sint32); \
|
||||
TEST(msg2.req_int64 == int64); \
|
||||
TEST(msg2.req_uint64 == uint64); \
|
||||
TEST(msg2.req_sint64 == sint64); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
if (expected_result) \
|
||||
{ \
|
||||
pb_ostream_t s = pb_ostream_from_buffer(buffer2, sizeof(buffer2)); \
|
||||
TEST(pb_encode(&s, IntSizes_fields, &msg2)); \
|
||||
TEST(s.bytes_written == msgsize); \
|
||||
TEST(memcmp(buffer1, buffer2, msgsize) == 0); \
|
||||
} \
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
{
|
||||
IntSizes msg = IntSizes_init_zero;
|
||||
|
||||
COMMENT("Test field sizes");
|
||||
TEST(sizeof(msg.req_int8) == 1);
|
||||
TEST(sizeof(msg.req_uint8) == 1);
|
||||
TEST(sizeof(msg.req_sint8) == 1);
|
||||
TEST(sizeof(msg.req_int16) == 2);
|
||||
TEST(sizeof(msg.req_uint16) == 2);
|
||||
TEST(sizeof(msg.req_sint16) == 2);
|
||||
TEST(sizeof(msg.req_int32) == 4);
|
||||
TEST(sizeof(msg.req_uint32) == 4);
|
||||
TEST(sizeof(msg.req_sint32) == 4);
|
||||
TEST(sizeof(msg.req_int64) == 8);
|
||||
TEST(sizeof(msg.req_uint64) == 8);
|
||||
TEST(sizeof(msg.req_sint64) == 8);
|
||||
}
|
||||
|
||||
COMMENT("Test roundtrip at maximum value");
|
||||
TEST_ROUNDTRIP(127, 255, 127,
|
||||
32767, 65535, 32767,
|
||||
INT32_MAX, UINT32_MAX, INT32_MAX,
|
||||
INT64_MAX, UINT64_MAX, INT64_MAX, true);
|
||||
|
||||
COMMENT("Test roundtrip at minimum value");
|
||||
TEST_ROUNDTRIP(-128, 0, -128,
|
||||
-32768, 0, -32768,
|
||||
INT32_MIN, 0, INT32_MIN,
|
||||
INT64_MIN, 0, INT64_MIN, true);
|
||||
|
||||
if (status != 0)
|
||||
fprintf(stdout, "\n\nSome tests FAILED!\n");
|
||||
|
||||
return status;
|
||||
}
|
||||
Reference in New Issue
Block a user