ccapnproto

How to handle capn_data in c?


I use c-capnproto to serialize my sensor data. Now I need to add a HMAC to it. It needs to be a capn_data blob, but this is not handled in the example code.

When I try to add the data, it does not work. This is my capnp file:

@0xe2d0214c41cb77af;

using C = import "c.capnp";
$C.fieldgetset;

struct Heartbeat @0x97002102216a8d67 {
    temperature @0 :UInt32;
}
 
struct AuthenticatedHeartbeat @0xe2fa13efd2e3a9dc {
    heartbeat @0 :Data;
    hmac @1 :Data;
}

After compiling it, I use it:

static uint8_t beat[1024];
static uint8_t hmac[32] = {0xc2, 0xf1, 0x53, 0x12, 0x4b, 0x0c, 0xa4, 0xd1,
                           0x22, 0xf1, 0x64, 0x22, 0x1b, 0xbc, 0xa5, 0xd2,
                           0xe2, 0xf2, 0x75, 0x32, 0x2b, 0x3c, 0xa6, 0xd3,
                           0x72, 0xf1, 0x86, 0x42, 0x6b, 0x7c, 0xa7, 0xd4};
static uint8_t auth[1024];
int main(void) {
    int packed_size;
    {
        struct capn root;
        capn_init_malloc(&root);
        capn_ptr cr = capn_root(&root);
        struct capn_segment *cs = cr.seg;
        Heartbeat_ptr hb = new_Heartbeat(cs);
        Heartbeat_set_temperature(hb, 0x25);
        capn_setp(capn_root(&root), 0, hb.p);
        packed_size = capn_write_mem(&root, beat, sizeof(beat), 1/*packed*/);
        printf("BEAT:    max_unpacked_size=%d, packed_size=%d\n", capn_size(&root), packed_size);
        capn_free(&root);
    }
    print_buffer(beat, packed_size);
    //HMAC_calc(beat, packed_size, hmac);
    {
        struct capn root;
        capn_init_malloc(&root);
        capn_ptr cr = capn_root(&root);
        struct capn_segment *cs = cr.seg;
        AuthenticatedHeartbeat_ptr ahb = new_AuthenticatedHeartbeat(cs);
        AuthenticatedHeartbeat_set_heartbeat(ahb, buf_to_data(beat, packed_size));
        AuthenticatedHeartbeat_set_hmac(ahb, buf_to_data(hmac, 32));
        capn_setp(capn_root(&root), 0, ahb.p);
        packed_size = capn_write_mem(&root, auth, sizeof(auth), 1/*packed*/);
        printf("AUTH:    max_unpacked_size=%d, packed_size=%d\n", capn_size(&root), packed_size);
        capn_free(&root);
    }
    print_buffer(auth, packed_size);
}

I wrote a helper function like the chars_to_text function provided in the example

static capn_data buf_to_data(char *buf, const int len) {
  return (capn_data) {{
    .type = CAPN_NULL,
    .has_ptr_tag = 0,
    .is_list_member = 0,
    .is_composite_list = 0,
    .datasz = 0,
    .ptrs = 0,
    .len = len,
    .data = buf,
    .seg = NULL,
  }};
}

but I guess I'm missing some parts. The resulting data

BEAT:    max_unpacked_size=24, packed_size=6
[  0] - 10021001 - ....
[  1] - 01250000 - .%
AUTH:    max_unpacked_size=32, packed_size=6
[  0] - 10034002 - ..@.
[  1] - 00010000 - ..

does at least not contain the temperature anymore (0x25) in the autheticated data. Also the HMAC is not present, the length way too short.

Could You please hint me at how to handle the capn_data blob, how to fill it and how to pack it into the root.


Solution

  • Finally found a hint in this closed issue, that was not visible or overlooked in my research at the beginning...

    I used this code to serialize capn_data (this replaces the lower scope in the original question):

        {
            struct capn root;
            capn_init_malloc(&root);
            capn_ptr cr = capn_root(&root);
            struct capn_segment *cs = cr.seg;
            
            capn_list8 list8 = capn_new_list8(cs, packed_size);
            capn_setv8(list8, 0, beat, packed_size);
            capn_list8 list9 = capn_new_list8(cs, 32);
            capn_setv8(list9, 0, hmac, 32);
            
            struct AuthenticatedHeartbeat s_ahb;
            s_ahb.heartbeat.p = list8.p;
            s_ahb.hmac.p = list9.p;
            
            AuthenticatedHeartbeat_ptr pahb = new_AuthenticatedHeartbeat(cs);
            write_AuthenticatedHeartbeat(&s_ahb, pahb);
            capn_setp(capn_root(&root), 0, pahb.p);
            packed_size = capn_write_mem(&root, auth, sizeof(auth), 1/*packed*/);
            printf("AUTH:    max_unpacked_size=%d, packed_size=%d\n", capn_size(&root), packed_size);
            capn_free(&root);
        }
    

    So first a "list" is created that is later used as capn_data as these two are essentially the same as @eqvinox stated.

    The buf_to_data() function is no longer needed.

    As a reference this is sample code for de-serializing the data:

        {
            struct capn root;
            capn_init_mem(&root, auth, packed_size, 1/*packed*/);
            AuthenticatedHeartbeat_ptr ahbp;
            ahbp.p = capn_getp(capn_root(&root), 0, 1);
            capn_data dhb = AuthenticatedHeartbeat_get_heartbeat(ahbp);
            capn_data dhmac = AuthenticatedHeartbeat_get_hmac(ahbp);
            printf("dhb   ptr=%p, len=%d\n", dhb.p.data, dhb.p.len);
            printf("dhmac ptr=%p, len=%d\n", dhmac.p.data, dhmac.p.len);
            unpacked_size = dhb.p.len;
            hmac2_size = dhmac.p.len;
            memcpy(beat2, dhb.p.data, unpacked_size);
            memcpy(hmac2, dhmac.p.data, hmac2_size);
            capn_free(&root);
        }
        {
            struct capn root;
            capn_init_mem(&root, beat2, unpacked_size, 1/*packed*/);
            Heartbeat_ptr hbp;
            hbp.p = capn_getp(capn_root(&root), 0, 1);
            uint32_t temperature = Heartbeat_get_temperature(hbp);
            printf("DESER:   temperature=%x\n", temperature);
            capn_free(&root);
        }