Last Updated: February 25, 2016
·
1.56K
· edenhill

protobuf-c extend workaround

protobuf-c unfortunately doesn't support the extend directive, such as:

message extendingmsg {

  required string name = 1;

   extend msg {
        optional extendingmsg msg_ext = 1000;
   };
};

But there is a workaround, namely the base.unknown_fields member of the base message. The unknown field for a message is a Length-delimited field, so it starts off with the payload length encoded in varint encoding followed by the payload itself - the extending message.

Thus, you do this:
- unpack the protobuf as the base message type ('msg' in the example above)
- the extending message ('extendingmsg' above) will (most likely) be the first&only unknown field.
- decode the varint from msg->base.unknown_fields[0], make sure to keep track of the size of the varint itself (hence the var-part of varint)
- now skip past the varint and decode the remaining payload as your extending message type ('extendingmsg' above).

Or in code:

int handle_protobuf_extendingmsg (unsigned char *buf, int len) {
    msg *m;
    extendingmsg *em;
    int vlen;
    uint64_t plen;

    /* Unpack containign msg message. */
    if (!(m = msg__unpack(NULL, len, buf)))
            return -1;

    if (m->base.n_unknown_fields < 1) {
            msg__free_unpacked(m, NULL);
            return -1;
    }

    /* The message starts past the varint-encoded length. */
    plen = rd_varint_decode_u64(
                    m->base.unknown_fields[0].data,
                    m->base.unknown_fields[0].len, &vlen);
    if (vlen <= 0) {
            /* varint decode failed */
            msg__free_unpacked(m, NULL);
            return -1;
    }

    /* Unpack the extendingmsg past the varint */
    if (!(em =
          extendingmsg__unpack(NULL,
                       m->base.unknown_fields[0].len-vlen,
                      m->base.unknown_fields[0].data+vlen))) {
            msg__free_unpacked(m, NULL);
            return -1;
    }


    /* em and e are now unpacked and ready to use                           
     * as two separeate protobuf messages. */
    printf("name: %s\n", em->name);


    extendingmsg__free_unpacked(em, NULL);
    msg__free_unpacked(m, NULL);
    return 0;
}

The above code utilizes librd's varint decoder which can be found here:
https://github.com/edenhill/librd