Example
authorPetteri Aimonen <jpa@npb.mail.kapsi.fi>
Wed, 17 Aug 2011 19:03:06 +0000 (19:03 +0000)
committerPetteri Aimonen <jpa@npb.mail.kapsi.fi>
Wed, 17 Aug 2011 19:03:06 +0000 (19:03 +0000)
git-svn-id: https://svn.kapsi.fi/jpa/nanopb@957 e3a754e5-d11d-0410-8d38-ebb782a927b9

14 files changed:
docs/concepts.rst
docs/index.rst
docs/reference.rst
example/Makefile [new file with mode: 0644]
example/fileproto.proto [new file with mode: 0644]
example/server.c [new file with mode: 0644]
generator/nanopb_generator.py
pb.h
pb_decode.c
pb_encode.c
tests/Makefile
tests/person.proto
tests/test_decode1.c
tests/test_encode1.c

index 58620a8..fac9061 100644 (file)
@@ -164,7 +164,7 @@ 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.
 
-The `pb_callback_t`_ structure contains a function pointer and a *void* pointer you can use for passing data to the callback. 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 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.
 
 .. _`pb_callback_t`: reference.html#pb-callback-t
 
@@ -176,7 +176,7 @@ Encoding callbacks
 
 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.
 
-The callback may be called multiple times during a single call to `pb_encode`_. It must produce the same amount of data every time.
+If the callback is used in a submessage, it will be called multiple times during a single call to `pb_encode`_. It must produce the same amount of data every time. If the callback is directly in the main message, it is called only once.
 
 .. _`pb_encode`: reference.html#pb-encode
 
index 93f04c6..ea89123 100644 (file)
@@ -42,7 +42,7 @@ Features and limitations
 
 **Limitations**
 
-#) User must provide callbacks when decoding arrays or strings without maximum size.
+#) User must provide callbacks when decoding arrays or strings without maximum size. Malloc support could be added as a separate module.
 #) Some speed has been sacrificed for code size. For example varint calculations are always done in 64 bits.
 #) Encoding is focused on writing to streams. For memory buffers only it could be made more efficient.
 #) The deprecated Protocol Buffers feature called "groups" is not supported.
index b8f9454..2812958 100644 (file)
@@ -94,7 +94,7 @@ Part of a message structure, for fields with type PB_HTYPE_CALLBACK::
 
 The *arg* is passed to the callback when calling. It can be used to store any information that the callback might need.
 
-When calling `pb_encode`_, *funcs.encode* must be set, and similarly when calling `pb_decode`_, *funcs.decode* must be set. The function pointers are stored in the same memory location but are of incompatible types.
+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.
 
 pb_wire_type_t
 --------------
diff --git a/example/Makefile b/example/Makefile
new file mode 100644 (file)
index 0000000..0f08f2d
--- /dev/null
@@ -0,0 +1,11 @@
+CFLAGS=-ansi -Wall -Werror -I .. -g -O0
+DEPS=../pb_decode.c ../pb_decode.h ../pb_encode.c ../pb_encode.h ../pb.h
+
+all: server
+
+%: %.c $(DEPS) fileproto.h
+       $(CC) $(CFLAGS) -o $@ $< ../pb_decode.c ../pb_encode.c fileproto.c
+
+fileproto.h: fileproto.proto ../generator/nanopb_generator.py
+       protoc -I. -I../generator -I/usr/include -ofileproto.pb $<
+       python ../generator/nanopb_generator.py fileproto.pb
diff --git a/example/fileproto.proto b/example/fileproto.proto
new file mode 100644 (file)
index 0000000..e2786b1
--- /dev/null
@@ -0,0 +1,26 @@
+import "nanopb.proto";
+
+// This defines protocol for a simple server that lists files.
+//
+// If you come from high-level programming background, the hardcoded
+// maximum lengths may disgust you. However, if your microcontroller only
+// has a few kB of ram to begin with, setting reasonable limits for
+// filenames is ok.
+//
+// On the other hand, using the callback interface, it is not necessary
+// to set a limit on the number of files in the response.
+
+message ListFilesRequest {
+    optional string path = 1 [default = "/", (nanopb).max_size = 128];
+}
+
+message FileInfo {
+    required uint64 inode = 1;
+    required string name = 2 [(nanopb).max_size = 128];
+}
+
+message ListFilesResponse {
+    optional bool path_error = 1 [default = false];
+    repeated FileInfo file = 2;
+}
+
diff --git a/example/server.c b/example/server.c
new file mode 100644 (file)
index 0000000..a671f4c
--- /dev/null
@@ -0,0 +1,149 @@
+/* This is a simple TCP server that listens on port 1234 and provides lists
+ * of files to clients, using a protocol defined in file_server.proto.
+ *
+ * It directly deserializes and serializes messages from network, minimizing
+ * memory use.
+ * 
+ * For flexibility, this example is implemented using posix api.
+ * In a real embedded system you would typically use some other kind of
+ * a communication and filesystem layer.
+ */
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pb_encode.h>
+#include <pb_decode.h>
+
+#include "fileproto.h"
+
+bool write_callback(pb_ostream_t *stream, const uint8_t *buf, size_t count)
+{
+    int fd = *(int*)stream->state;
+    return send(fd, buf, count, 0) == count;
+}
+
+bool read_callback(pb_istream_t *stream, uint8_t *buf, size_t count)
+{
+    int fd = *(int*)stream->state;
+    
+    if (buf == NULL)
+    {
+        /* Well, this is a really inefficient way to skip input. */
+        /* It is only used when there are unknown fields. */
+        char dummy;
+        while (count-- && recv(fd, &dummy, 1, 0) == 1);
+        return count == 0;
+    }
+    
+    return recv(fd, buf, count, MSG_WAITALL) == count;
+}
+
+bool listdir_callback(pb_ostream_t *stream, const pb_field_t *field, const void *arg)
+{
+    DIR *dir = (DIR*) arg;
+    struct dirent *file;
+    FileInfo fileinfo;
+    
+    while ((file = readdir(dir)) != NULL)
+    {
+        fileinfo.inode = file->d_ino;
+        strncpy(fileinfo.name, file->d_name, sizeof(fileinfo.name));
+        fileinfo.name[sizeof(fileinfo.name) - 1] = '\0';
+        
+        if (!pb_encode_tag_for_field(stream, field))
+            return false;
+        
+        if (!pb_enc_submessage(stream, field, &fileinfo))
+            return false;
+    }
+    
+    return true;
+}
+
+void handle_connection(int connfd)
+{
+    ListFilesRequest request;
+    ListFilesResponse response;
+    pb_istream_t input = {&read_callback, &connfd, SIZE_MAX};
+    pb_ostream_t output = {&write_callback, &connfd, SIZE_MAX, 0};
+    DIR *directory;
+    
+    if (!pb_decode(&input, ListFilesRequest_fields, &request))
+    {
+        printf("Decoding failed.\n");
+        return;
+    }
+    
+    directory = opendir(request.path);
+    
+    printf("Listing directory: %s\n", request.path);
+    
+    if (directory == NULL)
+    {
+        perror("opendir");
+        
+        response.has_path_error = true;
+        response.path_error = true;
+        response.file.funcs.encode = NULL;
+    }
+    else
+    {
+        response.has_path_error = false;
+        response.file.funcs.encode = &listdir_callback;
+        response.file.arg = directory;
+    }
+    
+    if (!pb_encode(&output, ListFilesResponse_fields, &response))
+    {
+        printf("Encoding failed.\n");
+    }
+}
+
+int main(int argc, char **argv)
+{
+    int listenfd, connfd;
+    struct sockaddr_in servaddr;
+    
+    listenfd = socket(AF_INET, SOCK_STREAM, 0);
+    
+    memset(&servaddr, 0, sizeof(servaddr));
+    servaddr.sin_family = AF_INET;
+    servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+    servaddr.sin_port = htons(1234);
+    if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) != 0)
+    {
+        perror("bind");
+        return 1;
+    }
+    
+    if (listen(listenfd, 5) != 0)
+    {
+        perror("listen");
+        return 1;
+    }
+    
+    for(;;)
+    {
+        connfd = accept(listenfd, NULL, NULL);
+        
+        if (connfd < 0)
+        {
+            perror("accept");
+            return 1;
+        }
+        
+        printf("Got connection.\n");
+        
+        handle_connection(connfd);
+        
+        printf("Closing connection.\n");
+        
+        close(connfd);
+    }
+}
index e62d04f..a6d38c0 100644 (file)
@@ -124,7 +124,7 @@ class Field:
                 self.ctype = self.struct_name + self.name + 't'
         elif desc.type == FieldD.TYPE_MESSAGE:
             self.ltype = 'PB_LTYPE_SUBMESSAGE'
-            self.ctype = names_from_type_name(desc.type_name)
+            self.ctype = self.submsgname = names_from_type_name(desc.type_name)
         else:
             raise NotImplementedError(desc.type)
         
@@ -167,8 +167,8 @@ class Field:
             if self.max_size is None:
                 return None # Not implemented
             else:
-                array_decl = '[%d]' % self.max_size
-            default = self.default.encode('string_escape')
+                array_decl = '[%d]' % (self.max_size + 1)
+            default = str(self.default).encode('string_escape')
             default = default.replace('"', '\\"')
             default = '"' + default + '"'
         elif self.ltype == 'PB_LTYPE_BYTES':
@@ -223,7 +223,7 @@ class Field:
             result += ' 0,'
         
         if self.ltype == 'PB_LTYPE_SUBMESSAGE':
-            result += '\n    &%s_fields}' % self.ctype
+            result += '\n    &%s_fields}' % self.submsgname
         elif self.default is None or self.htype == 'PB_HTYPE_CALLBACK':
             result += ' 0}'
         else:
diff --git a/pb.h b/pb.h
index e6037b8..2c6a252 100644 (file)
--- a/pb.h
+++ b/pb.h
@@ -126,6 +126,8 @@ typedef struct {
  * The encoding callback will receive the actual output stream.
  * It should write all the data in one call, including the field tag and
  * wire type. It can write multiple fields.
+ *
+ * The callback can be null if you want to skip a field.
  */
 typedef struct _pb_istream_t pb_istream_t;
 typedef struct _pb_ostream_t pb_ostream_t;
index 379d134..2cde54c 100644 (file)
@@ -185,14 +185,19 @@ static void pb_field_init(pb_field_iterator_t *iter, const pb_field_t *fields, v
 {
     iter->start = iter->current = fields;
     iter->field_index = 0;
-    iter->pData = dest_struct;
+    iter->pData = dest_struct + iter->current->data_offset;
+    iter->pSize = (char*)iter->pData + iter->current->size_offset;
     iter->dest_struct = dest_struct;
 }
 
 static bool pb_field_next(pb_field_iterator_t *iter)
 {
     bool notwrapped = true;
-    size_t prev_size = iter->current->data_size * iter->current->array_size;
+    size_t prev_size = iter->current->data_size;
+    
+    if (PB_HTYPE(iter->current->type) == PB_HTYPE_ARRAY)
+        prev_size *= iter->current->array_size;
+    
     iter->current++;
     iter->field_index++;
     if (iter->current->tag == 0)
@@ -271,9 +276,14 @@ static bool decode_field(pb_istream_t *stream, int wire_type, pb_field_iterator_
             }
         
         case PB_HTYPE_CALLBACK:
+        {
+            pb_callback_t *pCallback = (pb_callback_t*)iter->pData;
+            
+            if (pCallback->funcs.decode == NULL)
+                return skip(stream, wire_type);
+            
             if (wire_type == PB_WT_STRING)
             {
-                pb_callback_t *pCallback = (pb_callback_t*)iter->pData;
                 pb_istream_t substream;
                 
                 if (!make_string_substream(stream, &substream))
@@ -293,7 +303,6 @@ static bool decode_field(pb_istream_t *stream, int wire_type, pb_field_iterator_
                  * which in turn allows to use same callback for packed and
                  * not-packed fields. */
                 pb_istream_t substream;
-                pb_callback_t *pCallback = (pb_callback_t*)iter->pData;
                 uint8_t buffer[10];
                 size_t size = sizeof(buffer);
                 
@@ -303,7 +312,8 @@ static bool decode_field(pb_istream_t *stream, int wire_type, pb_field_iterator_
                 
                 return pCallback->funcs.decode(&substream, iter->current, pCallback->arg);
             }
-            
+        }
+        
         default:
             return false;
     }
@@ -344,6 +354,10 @@ bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struc
         {
             *(size_t*)iter.pSize = 0;
         }
+        else if (PB_HTYPE(iter.current->type) == PB_HTYPE_REQUIRED)
+        {
+            memset(iter.pData, 0, iter.current->data_size);
+        }
     } while (pb_field_next(&iter));
     
     while (stream->bytes_left)
@@ -351,10 +365,15 @@ bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struc
         uint32_t temp;
         int tag, wire_type;
         if (!pb_decode_varint32(stream, &temp))
-            return stream->bytes_left == 0; /* Was it EOF? */
+        {
+            if (stream->bytes_left == 0)
+                break; /* It was EOF */
+            else
+                return false; /* It was error */
+        }
         
         if (temp == 0)
-            return true; /* Special feature: allow 0-terminated messages. */
+            break; /* Special feature: allow 0-terminated messages. */
         
         tag = temp >> 3;
         wire_type = temp & 7;
index 188d768..2e74034 100644 (file)
@@ -130,7 +130,10 @@ bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_
     {
         pData = (const char*)pData + prev_size + field->data_offset;
         pSize = (const char*)pData + field->size_offset;
-        prev_size = field->data_size * field->array_size;
+        
+        prev_size = field->data_size;
+        if (PB_HTYPE(field->type) == PB_HTYPE_ARRAY)
+            prev_size *= field->array_size;
         
         pb_encoder_t func = PB_ENCODERS[PB_LTYPE(field->type)];
         
@@ -339,7 +342,7 @@ bool pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void
     substream.max_size = size;
     substream.bytes_written = 0;
     
-    status = pb_encode(stream, (pb_field_t*)field->ptr, src);
+    status = pb_encode(&substream, (pb_field_t*)field->ptr, src);
     
     stream->bytes_written += substream.bytes_written;
     
index 4993297..243dbd1 100644 (file)
@@ -2,21 +2,27 @@ CFLAGS=-ansi -Wall -Werror -I .. -g -O0
 DEPS=../pb_decode.c ../pb_decode.h ../pb_encode.c ../pb_encode.h ../pb.h person.h unittests.h
 TESTS=test_decode1 test_encode1 decode_unittests encode_unittests
 
-all: $(TESTS) run_unittests
+all: $(TESTS) run_unittests breakpoints
 
 clean:
        rm -f $(TESTS)
 
 %: %.c $(DEPS)
-       $(CC) $(CFLAGS) -o $@ $< ../pb_decode.c ../pb_encode.c
+       $(CC) $(CFLAGS) -o $@ $< ../pb_decode.c ../pb_encode.c person.c
 
-%.h: %.proto
+person.h: person.proto
        protoc -I. -I../generator -I/usr/include -operson.pb $<
        python ../generator/nanopb_generator.py person.pb
 
-run_unittests: decode_unittests encode_unittests
-       ./decode_unittests
-       ./encode_unittests
+breakpoints: ../*.c *.c
+       grep -n 'return false;' $^ | cut -d: -f-2 | xargs -n 1 echo b > $@
+
+run_unittests: decode_unittests encode_unittests test_encode1 test_decode1
+       ./decode_unittests > /dev/null
+       ./encode_unittests > /dev/null
+       
+       [ "`./test_encode1 | ./test_decode1`" = \
+       "`./test_encode1 | protoc --decode=Person -I. -I../generator -I/usr/include person.proto`" ]
 
 run_fuzztest: test_decode1
        bash -c 'I=1; while cat /dev/urandom | ./test_decode1 > /dev/null; do I=$$(($$I+1)); echo -en "\r$$I"; done'
\ No newline at end of file
index 5befb07..dafcf93 100644 (file)
@@ -4,8 +4,7 @@ message Person {
   required string name = 1 [(nanopb).max_size = 40];
   required int32 id = 2;
   optional string email = 3 [(nanopb).max_size = 40];
-  optional bytes test = 5 [default="\x00\x01\x02", (nanopb).max_size = 20];
-
+  
   enum PhoneType {
     MOBILE = 0;
     HOME = 1;
index a2c7f42..b7698ef 100644 (file)
@@ -2,9 +2,6 @@
 #include <pb_decode.h>
 #include "person.h"
 
-/* This test has only one source file anyway.. */
-#include "person.c"
-
 bool print_person(pb_istream_t *stream)
 {
     int i;
@@ -13,12 +10,33 @@ bool print_person(pb_istream_t *stream)
     if (!pb_decode(stream, Person_fields, &person))
         return false;
     
-    printf("Person: name '%s' id '%d' email '%s'\n", person.name, person.id, person.email);
+    printf("name: \"%s\"\n", person.name);
+    printf("id: %d\n", person.id);
+    
+    if (person.has_email)
+        printf("email: \"%s\"\n", person.email);
     
     for (i = 0; i < person.phone_count; i++)
     {
         Person_PhoneNumber *phone = &person.phone[i];
-        printf("PhoneNumber: number '%s' type '%d'\n", phone->number, phone->type);
+        printf("phone {\n");
+        printf("  number: \"%s\"\n", phone->number);
+        
+        switch (phone->type)
+        {
+            case Person_PhoneType_WORK:
+                printf("  type: WORK\n");
+                break;
+            
+            case Person_PhoneType_HOME:
+                printf("  type: HOME\n");
+                break;
+            
+            case Person_PhoneType_MOBILE:
+                printf("  type: MOBILE\n");
+                break;
+        }
+        printf("}\n");
     }
     
     return true;
index 99be7cb..ac13df3 100644 (file)
@@ -2,9 +2,6 @@
 #include <pb_encode.h>
 #include "person.h"
 
-/* This test has only one source file anyway.. */
-#include "person.c"
-
 bool streamcallback(pb_ostream_t *stream, const uint8_t *buf, size_t count)
 {
     FILE *file = (FILE*) stream->state;