nanopb

Nanopb correctly encoding and decoding repeated construct fields in submessage


What is the correct way to encode/decode repeated construct fields in a Nanopb submessage? The output generated shows that the decode operation does not detect any of the repeated construct fields. Also intriguing is that the encode callback gets called twice, and also in question. What am I missing?

The decode succeeds if, as an experiment, this example is modified to for encode and decode to start not at the TopMessage but at SubMessage1. Also, in this case the encode callback is called only once as expected.

Following are the proto definitions; the field in question is subMessage11 under SubMessage1.

syntax = "proto2";
import 'nanopb.proto';

message SubMessage11
{
  required uint64 int64Val = 1;
};

message SubMessage1
{
  repeated SubMessage11 subMessage11 = 1;
};

message SubMessage2
{
  required uint32 intVal = 1;
};

message TopMessage
{
  oneof choice
  {
    SubMessage1 subMessage1 = 1;
    SubMessage2 subMessage2 = 2;
  }
};

The C++ code code that uses the proto definitions is:

#include "pb_encode.h"
#include "pb_decode.h"

#include "t.pb.h"
#include "debug.hpp"

bool subMessage11EncodeCb(pb_ostream_t *stream, const pb_field_t *field,
    void * const *arg)
{
  dprintf("called, field->tag=%d field->type=%d", field->tag, field->type);

  for(int i=0; i<4; i++)
  {
    if(pb_encode_tag_for_field(stream, field) == false)
    {
      dprintf("encode failed");
      return false;
    }

    SubMessage11 subMessage11 = SubMessage11_init_zero;
    subMessage11.int64Val = 0xaabbccddeef0 + i;

    if(pb_encode_submessage(stream, SubMessage11_fields, &subMessage11) == false)
    {
      dprintf("encode failed");
      return false;
    }
  }

  return true;
}

bool subMessage11DecodeCb(pb_istream_t *stream, const pb_field_t *field,
    void **arg)
{
  dprintf("called");

  SubMessage11 subMessage11 = SubMessage11_init_zero;
  if(pb_decode(stream, SubMessage11_fields, &subMessage11) == false)
  {
    dprintf("error decoding: %s", stream->errmsg);
    return false;
  }

  dprintf("int64Val=%lx", subMessage11.int64Val);

  return true;
}

bool encodeMsg(uint8_t buf[], size_t& bufsz)
{
  dprintf("begin encoding");
  pb_ostream_t stream = pb_ostream_from_buffer(buf, bufsz);

  TopMessage topMessage = TopMessage_init_zero;

  topMessage.which_choice = TopMessage_subMessage1_tag;
  SubMessage1& subMessage1 = topMessage.choice.subMessage1;
  subMessage1.subMessage11.funcs.encode = subMessage11EncodeCb;

  bool status = pb_encode(&stream, TopMessage_fields, &topMessage);
  if(status != true)
  {
    dprintf("error encoding: %s", stream.errmsg);
    bufsz = 0;
    return status;
  }

  bufsz = stream.bytes_written;

  dprintf("done encoding");
  return status;
}

bool decodeMsg(uint8_t buf[], size_t bufsz)
{
  dprintf("begin decoding");
  pb_istream_t stream = pb_istream_from_buffer(buf, bufsz);

  TopMessage topMessage;
  topMessage.which_choice = TopMessage_subMessage1_tag;
  SubMessage1& subMessage1 = topMessage.choice.subMessage1;
  int val;
  subMessage1.subMessage11.arg = (void *)&val;
  subMessage1.subMessage11.funcs.decode = &subMessage11DecodeCb;

  bool status = pb_decode(&stream, TopMessage_fields, &topMessage);
  if(status != true)
  {
    dprintf("error decoding: %s", stream.errmsg);
    return false;
  }

  dprintf("decoded fields: ");

  dprintf("done decoding");
  return status;
}

int main(int ac, char *av[])
{
  uint8_t encBuf[1024];
  size_t encSz = sizeof(encBuf);

  if(encodeMsg(encBuf, encSz) != true)
  {
    dprintf("Encode failed");
    return 1;
  }

  hexdump(encBuf, encSz);

  if(decodeMsg(encBuf, encSz) != true)
  {
    dprintf("Decode failed");
    return 1;
  }
}

The output produced is:

c.cpp:55:encodeMsg: begin encoding
c.cpp:12:subMessage11EncodeCb: called, field->tag=1 field->type=103
c.cpp:12:subMessage11EncodeCb: called, field->tag=1 field->type=103
c.cpp:74:encodeMsg: done encoding

[0000]   0A 28 0A 08 08 F0 DD F7   E6 BC D7 2A 0A 08 08 F1   ........ ........
[0010]   DD F7 E6 BC D7 2A 0A 08   08 F2 DD F7 E6 BC D7 2A   ........ ........
[0020]   0A 08 08 F3 DD F7 E6 BC   D7 2A                     ........ ..
c.cpp:80:decodeMsg: begin decoding
c.cpp:97:decodeMsg: decoded fields:
c.cpp:99:decodeMsg: done decoding

Solution

  • Encode callbacks being called multiple times for submessages is expected behaviour. First call is for calculating the size, which must be known before the submessage body can be written out:

    If the callback is used in a submessage, it will be called multiple times during a single call to pb_encode. In this case, it must produce the same amount of data every time. If the callback is directly in the main message, it is called only once.

    As for why your decoding is not working, currently callbacks are not supported inside oneof constructs:

    If a oneof contains a sub-message that has a string field, the encode callback is called twice, and the decode callback is never called.