Add int_size option for generator.
authorPetteri Aimonen <jpa@git.mail.kapsi.fi>
Sun, 4 Jan 2015 09:36:42 +0000 (11:36 +0200)
committerPetteri Aimonen <jpa@git.mail.kapsi.fi>
Sun, 4 Jan 2015 10:02:15 +0000 (12:02 +0200)
This allows overriding the integer field types to e.g. uint8_t for
saving RAM.

Update issue 139
Status: FixedInGit

docs/reference.rst
generator/nanopb_generator.py
generator/proto/nanopb.proto
pb_decode.c
pb_encode.c
tests/intsizes/SConscript [new file with mode: 0644]
tests/intsizes/intsizes.proto [new file with mode: 0644]
tests/intsizes/intsizes_unittests.c [new file with mode: 0644]

index 8018bdb..e16500a 100644 (file)
@@ -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
index 6dcc911..85cb413 100755 (executable)
@@ -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)
index 0716be4..1bde596 100644 (file)
@@ -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];
   
index 367f073..b5ec1ef 100644 (file)
@@ -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");
index 5318361..cef9886 100644 (file)
@@ -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");
diff --git a/tests/intsizes/SConscript b/tests/intsizes/SConscript
new file mode 100644 (file)
index 0000000..a90680b
--- /dev/null
@@ -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)
diff --git a/tests/intsizes/intsizes.proto b/tests/intsizes/intsizes.proto
new file mode 100644 (file)
index 0000000..236bf18
--- /dev/null
@@ -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;
+}
diff --git a/tests/intsizes/intsizes_unittests.c b/tests/intsizes/intsizes_unittests.c
new file mode 100644 (file)
index 0000000..29cc7ab
--- /dev/null
@@ -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;
+}
\ No newline at end of file