0

Hello nano developers,

I'd like to realize the following proto:

message container {
    enum MessageType {
        TYPE_UNKNOWN =  0;
        evt_resultStatus  =  1;
    }
    required MessageType mt = 1;
    optional bytes cmd_evt_transfer = 2;
}

message evt_resultStatus {
    required int32 operationMode = 1;
}

...

The dots denote, there are more messages with (multiple) primitive containing datatypes to come. The enum will grow likewise, just wanted to keep it short.

The container gets generated as:

typedef struct _container { 
    container_MessageType mt; 
    pb_callback_t cmd_evt_transfer; 
} container;

evt_resultStatus is:

typedef struct _evt_resultStatus { 
    int32_t operationMode; 
} evt_resultStatus;

The field cmd_evt_transfer should act as a proxy of subsequent messages like evt_resultStatus holding primitive datatypes. evt_resultStatus shall be encoded into bytes and be placed into the cmd_evt_transfer field. Then the container shall get encoded and the encoding result will be used for subsequent transfers.

The background why to do so, is to shorten the proto definition and avoid the oneof thing. Unfortunately syntax version 3 is not fully supported, so we can not make use of any fields.

The first question is: will this approach be possible?

What I've got so far is the encoding including the callback which seems to behave fine. But on the other side, decoding somehow skips the callback. I've read issues here, that this happened also when using oneof and bytes fields.

Can someone please clarify on how to proceed with this?

Sample code so far I got:

bool encode_msg_test(pb_byte_t* buffer, int32_t sval, size_t* sz, char* err) {
    evt_resultStatus rs = evt_resultStatus_init_zero;
    rs.operationMode = sval;
    pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

    /*encode container*/
    container msg = container_init_zero;
    msg.mt = container_MessageType_evt_resultStatus;
    msg.cmd_evt_transfer.arg = &rs;
    msg.cmd_evt_transfer.funcs.encode = encode_cb;
    if(! pb_encode(&stream, container_fields, &msg)) {
        const char* local_err = PB_GET_ERROR(&stream);
        sprintf(err, "pb_encode error: %s", local_err);
        return false;
    }

    *sz = stream.bytes_written;
    return true;
}

bool encode_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    evt_resultStatus* rs = (evt_resultStatus*)(*arg);

//with the below in place a stream full error rises
//    if (! pb_encode_tag_for_field(stream, field)) {
//        return false;
//    }

    if(! pb_encode(stream, evt_resultStatus_fields, rs)) {
        return false;
    }

    return true;
}

//buffer holds previously encoded data
bool decode_msg_test(pb_byte_t* buffer, int32_t* sval, size_t msg_len, char* err) {
    container msg = container_init_zero;
    evt_resultStatus res = evt_resultStatus_init_zero;

    msg.cmd_evt_transfer.arg = &res;
    msg.cmd_evt_transfer.funcs.decode = decode_cb;
    pb_istream_t stream = pb_istream_from_buffer(buffer, msg_len);
    if(! pb_decode(&stream, container_fields, &msg)) {
        const char* local_err = PB_GET_ERROR(&stream);
        sprintf(err, "pb_encode error: %s", local_err);
        return false;
    }
    *sval = res.operationMode;
    return true;
}

bool decode_cb(pb_istream_t *istream, const pb_field_t *field, void **arg) {
    evt_resultStatus * rs = (evt_resultStatus*)(*arg);

    if(! pb_decode(istream, evt_resultStatus_fields, rs)) {
        return false;
    }

    return true;
}

I feel, I don't have a proper understanding of the encoding / decoding process.

Is it correct to assume:

  • the first call of pb_encode (in encode_msg_test) takes care of the mt field
  • the second call of pb_encode (in encode_cb) handles the cmd_evt_transfer field

If I do:

bool encode_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    evt_resultStatus* rs = (evt_resultStatus*)(*arg);

    if (! pb_encode_tag_for_field(stream, field)) {
         return false;
    }

    if(! pb_encode(stream, evt_resultStatus_fields, rs)) {
        return false;
    }

    return true;
}

then I get a stream full error on the call of pb_encode.

Why is that?

woodz
  • 737
  • 6
  • 13

1 Answers1

0

Yes, the approach is reasonable. Nanopb callbacks do not care what the actual data read or written by the callback is.

As for why your decode callback is not working, you'll need to post the code you are using for decoding.

(As an aside, Any type does work in nanopb and is covered by this test case. But the type_url included in all Any messages makes them have a quite large overhead.)

jpa
  • 10,351
  • 1
  • 28
  • 45
  • Sorry to bother you on both platforms and thanks for answering. I didn't thought you have an eye here on sof. To your last statement: will it support PackFrom / UnpackTo functions? Only by doing so, it could be interesting. Also, if it is only a test case, I guess it's not suitable for production releases, right? I didn't understand the thing with `type_url` - "large overhead". I will edit my answer including test code. – woodz May 03 '22 at 18:32
  • @woodz I see you have `pb_encode_tag_for_field()` commented out - without it the message you encode will not be a valid protobuf message and that is why decoding doesn't work. There isn't a direct API like `PackFrom` API, but the test case does show how another message is encoded into the `Any` payload. Regarding production readiness - all functionality of nanopb is covered by test cases for continuous integration and there are no known bugs in `Any` message functionality. – jpa May 04 '22 at 05:45
  • agreed with the commented out thing, but do you have an idea why a stack full error comes up when I uncomment `pb_encode_tag_for_field`? – woodz May 04 '22 at 10:51
  • Maybe your stack is full? What is that error coming from, "stack full" is not a nanopb error message? – jpa May 04 '22 at 15:00
  • sorry, my mistake, it's a `stream full` error. `encode_callback_field` in pb_encode.c reports `PB_RETURN_ERROR(stream, "callback error");` and querying the error myself in `encode_msg_test` function results in a `pb_encode error: stream full` – woodz May 05 '22 at 07:55
  • So, is your stream full? How many bytes do you reserve for the message? How many bytes should the message take? – jpa May 05 '22 at 10:38