Nanopb корректно кодирует и декодирует повторяющиеся поля построения в submessage

#nanopb

#nanopb

Вопрос:

Каков правильный способ кодирования / декодирования повторяющихся полей построения в вложенном сообщении Nanopb? Сгенерированный результат показывает, что операция декодирования не обнаруживает ни одного из повторяющихся полей построения. Также интригует то, что обратный вызов encode вызывается дважды, а также находится под вопросом. Чего мне не хватает?

Декодирование завершится успешно, если в качестве эксперимента этот пример будет изменен, чтобы кодирование и декодирование начинались не с TopMessage, а с SubMessage1. Кроме того, в этом случае обратный вызов encode вызывается только один раз, как и ожидалось.

Ниже приведены определения прототипов; рассматриваемое поле — subMessage11 в 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;
  }
};
  

Код кода C , который использует определения proto, является:

 #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, amp;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, amp;subMessage11) == false)
  {
    dprintf("error decoding: %s", stream->errmsg);
    return false;
  }

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

  return true;
}

bool encodeMsg(uint8_t buf[], size_tamp; 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;
  SubMessage1amp; subMessage1 = topMessage.choice.subMessage1;
  subMessage1.subMessage11.funcs.encode = subMessage11EncodeCb;

  bool status = pb_encode(amp;stream, TopMessage_fields, amp;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;
  SubMessage1amp; subMessage1 = topMessage.choice.subMessage1;
  int val;
  subMessage1.subMessage11.arg = (void *)amp;val;
  subMessage1.subMessage11.funcs.decode = amp;subMessage11DecodeCb;

  bool status = pb_decode(amp;stream, TopMessage_fields, amp;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;
  }
}
  

Полученный результат:

 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
  

Комментарии:

1. просто любопытно, где именно TopMessage_fields определено?

2. Ах, теперь я вижу это, оно будет автоматически сгенерировано в t.pb.h

Ответ №1:

Ожидаемым поведением является кодирование обратных вызовов, вызываемых несколько раз для submessages. Первый вызов предназначен для вычисления размера, который должен быть известен до того, как можно будет записать тело submessage:

Если обратный вызов используется в submessage, он будет вызываться несколько раз в течение одного вызова pb_encode . В этом случае он должен каждый раз выдавать одинаковый объем данных. Если обратный вызов находится непосредственно в главном сообщении, он вызывается только один раз.

Что касается того, почему ваше декодирование не работает, в настоящее время обратные вызовы не поддерживаются внутри oneof конструкций:

Если oneof содержит вложенное сообщение со строковым полем, обратный вызов encode вызывается дважды, а обратный вызов decode никогда не вызывается.

Комментарии:

1. Это помогает, по крайней мере, знать, что обратные вызовы в настоящее время не поддерживаются внутри конструкций oneof, и может решить хотя бы часть проблемы. Однако на стороне кодирования я вижу только два обратных вызова, когда требуется кодировать 4 или более повторяющихся элементов. Чего мне не хватает в этом случае?

2. Похоже, что цикл для 4 элементов находится внутри вашего обратного вызова, и в submessage больше нет обратных вызовов. Таким образом, обратный вызов будет обрабатывать все 4 элемента за один вызов.