How to use services correctly in libcanard

Not quite. See, we have two kinds of communication: messages and services. Messages can be published and subscribed to; services can be invoked (or called) and provided (or served). You can’t “publish” a service call or “subscribe” to a service, it doesn’t make sense. In Libcanard, there is a bit of an implementation-specific terminology mishap: it uses “subscribe” in the sense of “tell the library that the application wants to receive this kind of transfers”; the mishap is because I couldn’t find a better word to use. Maybe I should have used “listen” instead, like canardRxListen? I wonder if we should change it.

So if you want to provide a service, you tell the library to receive service request transfers, like this:

// Configure the library to listen for register access service requests.
CanardRxSubscription srv_register_access;
(void) canardRxSubscribe(&canard,
                         CanardTransferKindRequest,
                         RegisterAccessServiceID,
                         1024U,  // Larger buffers are OK.
                         CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC,
                         &srv_register_access);

Then you handle the calls like this:

// Process received frames, if any.
CanardFrame rxf;
uint8_t     buffer[64];
while (socketcanPop(sock, &rxf, sizeof(buffer), buffer, 1000) > 0)  // Error handling not implemented
{
    CanardTransfer transfer;
    if (canardRxAccept(&canard, &rxf, 0, &transfer))
    {
        if ((transfer.transfer_kind == CanardTransferKindRequest) &&
            (transfer.port_id == RegisterAccessServiceID))
        {
            handleRegisterAccess(&canard, &transfer);
        }
        free((void*) transfer.payload);
    }
}
static void handleRegisterAccess(CanardInstance* const canard, const CanardTransfer* const request_transfer)
{
    <... business logic not shown ...>
    // Send the response back. Make sure to re-use the same priority and transfer-ID.
    CanardTransfer response_transfer = *request_transfer;
    response_transfer.transfer_kind  = CanardTransferKindResponse;
    response_transfer.payload_size   = response_size;
    response_transfer.payload        = response;
    (void) canardTxPush(canard, &response_transfer);
}

When invoking a service, you inverse the logic: listen for service response transfers and emit service request transfers. When receiving the responses, match them by their transfer-ID value.

The examples above have been copy-pasted from this demo: Automatic configuration of port identifiers