Skip to content

File syn_xrce_dds.c

File List > proto > syn_xrce_dds.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_XRCE_DDS) || SYN_USE_XRCE_DDS

#include "../port/syn_port_system.h"
#include "syn_xrce_dds.h"

#include <string.h>

#define SYN_XRCE_DEFAULT_HEARTBEAT_MS 5000U
#define SYN_XRCE_FLAG_LITTLE_ENDIAN 0x01U
#define SYN_XRCE_REPRESENTATION_BY_REF 0x01U
#define SYN_XRCE_REPRESENTATION_AS_STRING 0x02U
#define SYN_XRCE_DATA_FORMAT_DATA 0x00U

/* ── Internal Helper: CDR Alignment ──────────────────────────────────────── */

static size_t cdr_pad_writer(SYN_CDR_Writer *w, size_t align)
{
    size_t remainder = w->pos % align;
    if (remainder != 0U) {
        size_t pad = align - remainder;
        if (w->pos + pad > w->size) {
            w->error = true;
            return w->pos;
        }
        (void)memset(&w->data[w->pos], 0, pad);
        w->pos += pad;
    }
    return w->pos;
}

static size_t cdr_pad_reader(SYN_CDR_Reader *r, size_t align)
{
    size_t remainder = r->pos % align;
    if (remainder != 0U) {
        size_t pad = align - remainder;
        if (r->pos + pad > r->size) {
            r->error = true;
            return r->pos;
        }
        r->pos += pad;
    }
    return r->pos;
}

/* ── CDR Writer Implementation ───────────────────────────────────────────── */

void syn_cdr_writer_init(SYN_CDR_Writer *w, uint8_t *buf, size_t size)
{
    if (w == NULL) {
        return;
    }
    w->data = buf;
    w->size = size;
    w->pos = 0U;
    w->error = (buf == NULL || size == 0U);
}

bool syn_cdr_write_u8(SYN_CDR_Writer *w, uint8_t v)
{
    if (w == NULL || w->error || w->pos + 1U > w->size) {
        if (w != NULL) {
            w->error = true;
        }
        return false;
    }
    w->data[w->pos++] = v;
    return true;
}

bool syn_cdr_write_u16(SYN_CDR_Writer *w, uint16_t v)
{
    if (w == NULL || w->error) {
        return false;
    }
    (void)cdr_pad_writer(w, 2U);
    if (w->error || w->pos + 2U > w->size) {
        w->error = true;
        return false;
    }
    w->data[w->pos++] = (uint8_t)(v & 0xFFU);
    w->data[w->pos++] = (uint8_t)((v >> 8U) & 0xFFU);
    return true;
}

bool syn_cdr_write_u32(SYN_CDR_Writer *w, uint32_t v)
{
    if (w == NULL || w->error) {
        return false;
    }
    (void)cdr_pad_writer(w, 4U);
    if (w->error || w->pos + 4U > w->size) {
        w->error = true;
        return false;
    }
    w->data[w->pos++] = (uint8_t)(v & 0xFFU);
    w->data[w->pos++] = (uint8_t)((v >> 8U) & 0xFFU);
    w->data[w->pos++] = (uint8_t)((v >> 16U) & 0xFFU);
    w->data[w->pos++] = (uint8_t)((v >> 24U) & 0xFFU);
    return true;
}

bool syn_cdr_write_u64(SYN_CDR_Writer *w, uint64_t v)
{
    if (w == NULL || w->error) {
        return false;
    }
    (void)cdr_pad_writer(w, 8U);
    if (w->error || w->pos + 8U > w->size) {
        w->error = true;
        return false;
    }
    for (size_t i = 0U; i < 8U; i++) {
        w->data[w->pos++] = (uint8_t)((v >> (i * 8U)) & 0xFFU);
    }
    return true;
}

bool syn_cdr_write_i8(SYN_CDR_Writer *w, int8_t v)
{
    return syn_cdr_write_u8(w, (uint8_t)v);
}

bool syn_cdr_write_i16(SYN_CDR_Writer *w, int16_t v)
{
    return syn_cdr_write_u16(w, (uint16_t)v);
}

bool syn_cdr_write_i32(SYN_CDR_Writer *w, int32_t v)
{
    return syn_cdr_write_u32(w, (uint32_t)v);
}

bool syn_cdr_write_i64(SYN_CDR_Writer *w, int64_t v)
{
    return syn_cdr_write_u64(w, (uint64_t)v);
}

bool syn_cdr_write_float(SYN_CDR_Writer *w, float v)
{
    uint32_t u;
    (void)memcpy(&u, &v, sizeof(u));
    return syn_cdr_write_u32(w, u);
}

bool syn_cdr_write_double(SYN_CDR_Writer *w, double v)
{
    uint64_t u;
    (void)memcpy(&u, &v, sizeof(u));
    return syn_cdr_write_u64(w, u);
}

bool syn_cdr_write_bool(SYN_CDR_Writer *w, bool v)
{
    return syn_cdr_write_u8(w, v ? 1U : 0U);
}

bool syn_cdr_write_string(SYN_CDR_Writer *w, const char *str)
{
    if (w == NULL || str == NULL) {
        if (w != NULL) {
            w->error = true;
        }
        return false;
    }
    size_t str_len = strlen(str) + 1U; /* Length including null terminator */
    if (!syn_cdr_write_u32(w, (uint32_t)str_len)) {
        return false;
    }
    if (w->pos + str_len > w->size) {
        w->error = true;
        return false;
    }
    (void)memcpy(&w->data[w->pos], str, str_len);
    w->pos += str_len;
    return true;
}

bool syn_cdr_write_bytes(SYN_CDR_Writer *w, const uint8_t *bytes, size_t len)
{
    if (w == NULL || w->error || (bytes == NULL && len > 0U)) {
        if (w != NULL) {
            w->error = true;
        }
        return false;
    }
    if (w->pos + len > w->size) {
        w->error = true;
        return false;
    }
    if (len > 0U) {
        (void)memcpy(&w->data[w->pos], bytes, len);
        w->pos += len;
    }
    return true;
}

/* ── CDR Reader Implementation ───────────────────────────────────────────── */

void syn_cdr_reader_init(SYN_CDR_Reader *r, const uint8_t *buf, size_t size)
{
    if (r == NULL) {
        return;
    }
    r->data = buf;
    r->size = size;
    r->pos = 0U;
    r->error = (buf == NULL || size == 0U);
}

bool syn_cdr_read_u8(SYN_CDR_Reader *r, uint8_t *out)
{
    if (r == NULL || r->error || out == NULL || r->pos + 1U > r->size) {
        if (r != NULL) {
            r->error = true;
        }
        return false;
    }
    *out = r->data[r->pos++];
    return true;
}

bool syn_cdr_read_u16(SYN_CDR_Reader *r, uint16_t *out)
{
    if (r == NULL || r->error || out == NULL) {
        return false;
    }
    (void)cdr_pad_reader(r, 2U);
    if (r->error || r->pos + 2U > r->size) {
        r->error = true;
        return false;
    }
    *out = (uint16_t)r->data[r->pos] | ((uint16_t)r->data[r->pos + 1U] << 8U);
    r->pos += 2U;
    return true;
}

bool syn_cdr_read_u32(SYN_CDR_Reader *r, uint32_t *out)
{
    if (r == NULL || r->error || out == NULL) {
        return false;
    }
    (void)cdr_pad_reader(r, 4U);
    if (r->error || r->pos + 4U > r->size) {
        r->error = true;
        return false;
    }
    *out = (uint32_t)r->data[r->pos] | ((uint32_t)r->data[r->pos + 1U] << 8U) |
           ((uint32_t)r->data[r->pos + 2U] << 16U) | ((uint32_t)r->data[r->pos + 3U] << 24U);
    r->pos += 4U;
    return true;
}

bool syn_cdr_read_u64(SYN_CDR_Reader *r, uint64_t *out)
{
    if (r == NULL || r->error || out == NULL) {
        return false;
    }
    (void)cdr_pad_reader(r, 8U);
    if (r->error || r->pos + 8U > r->size) {
        r->error = true;
        return false;
    }
    uint64_t v = 0U;
    for (size_t i = 0U; i < 8U; i++) {
        v |= ((uint64_t)r->data[r->pos + i] << (i * 8U));
    }
    *out = v;
    r->pos += 8U;
    return true;
}

bool syn_cdr_read_i8(SYN_CDR_Reader *r, int8_t *out)
{
    return syn_cdr_read_u8(r, (uint8_t *)out);
}

bool syn_cdr_read_i16(SYN_CDR_Reader *r, int16_t *out)
{
    return syn_cdr_read_u16(r, (uint16_t *)out);
}

bool syn_cdr_read_i32(SYN_CDR_Reader *r, int32_t *out)
{
    return syn_cdr_read_u32(r, (uint32_t *)out);
}

bool syn_cdr_read_i64(SYN_CDR_Reader *r, int64_t *out)
{
    return syn_cdr_read_u64(r, (uint64_t *)out);
}

bool syn_cdr_read_float(SYN_CDR_Reader *r, float *out)
{
    uint32_t u;
    if (!syn_cdr_read_u32(r, &u) || out == NULL) {
        return false;
    }
    (void)memcpy(out, &u, sizeof(u));
    return true;
}

bool syn_cdr_read_double(SYN_CDR_Reader *r, double *out)
{
    uint64_t u;
    if (!syn_cdr_read_u64(r, &u) || out == NULL) {
        return false;
    }
    (void)memcpy(out, &u, sizeof(u));
    return true;
}

bool syn_cdr_read_bool(SYN_CDR_Reader *r, bool *out)
{
    uint8_t u;
    if (!syn_cdr_read_u8(r, &u) || out == NULL) {
        return false;
    }
    *out = (u != 0U);
    return true;
}

bool syn_cdr_read_string(SYN_CDR_Reader *r, char *out_buf, size_t max_len)
{
    if (r == NULL || r->error || out_buf == NULL || max_len == 0U) {
        if (r != NULL) {
            r->error = true;
        }
        return false;
    }
    uint32_t str_len = 0U;
    if (!syn_cdr_read_u32(r, &str_len)) {
        return false;
    }
    if (str_len == 0U || r->pos + str_len > r->size) {
        r->error = true;
        return false;
    }
    size_t copy_len = (str_len < max_len) ? str_len : max_len - 1U;
    (void)memcpy(out_buf, &r->data[r->pos], copy_len);
    out_buf[copy_len] = '\0';
    r->pos += str_len;
    return true;
}

bool syn_cdr_read_bytes(SYN_CDR_Reader *r, uint8_t *out_buf, size_t len)
{
    if (r == NULL || r->error || (out_buf == NULL && len > 0U)) {
        if (r != NULL) {
            r->error = true;
        }
        return false;
    }
    if (r->pos + len > r->size) {
        r->error = true;
        return false;
    }
    if (len > 0U) {
        (void)memcpy(out_buf, &r->data[r->pos], len);
        r->pos += len;
    }
    return true;
}

/* ── Internal Framing Helpers ────────────────────────────────────────────── */

static void xrce_write_header(SYN_CDR_Writer *w, uint8_t session_id, uint8_t stream_id,
                              uint16_t seq_num, uint32_t client_key)
{
    (void)syn_cdr_write_u8(w, session_id);
    (void)syn_cdr_write_u8(w, stream_id);
    (void)syn_cdr_write_u16(w, seq_num);
    if (session_id == 0x00U || session_id >= 0x80U) {
        (void)syn_cdr_write_u32(w, client_key);
    }
}

static size_t xrce_start_submessage(SYN_CDR_Writer *w, uint8_t submsg_id, uint8_t flags)
{
    (void)syn_cdr_write_u8(w, submsg_id);
    (void)syn_cdr_write_u8(w, flags | SYN_XRCE_FLAG_LITTLE_ENDIAN);
    size_t len_pos = w->pos;
    (void)syn_cdr_write_u16(w, 0U); /* Placeholder for submessage payload length */
    return len_pos;
}

static void xrce_finish_submessage(SYN_CDR_Writer *w, size_t len_pos)
{
    if (w->error || len_pos + 2U > w->pos) {
        return;
    }
    uint16_t submsg_len = (uint16_t)(w->pos - (len_pos + 2U));
    w->data[len_pos] = (uint8_t)(submsg_len & 0xFFU);
    w->data[len_pos + 1U] = (uint8_t)((submsg_len >> 8U) & 0xFFU);
}

/* ── Public XRCE Client API ──────────────────────────────────────────────── */

SYN_Status syn_xrce_client_init(SYN_XRCE_Client *client, const SYN_XRCE_Config *cfg)
{
    if (client == NULL || cfg == NULL) {
        return SYN_INVALID_PARAM;
    }
    if (cfg->transport == NULL || cfg->rx_buf == NULL || cfg->tx_buf == NULL) {
        return SYN_INVALID_PARAM;
    }
    if (cfg->rx_buf_size < 128U || cfg->tx_buf_size < 128U) {
        return SYN_INVALID_PARAM;
    }

    (void)memset(client, 0, sizeof(*client));
    client->cfg = *cfg;
    if (client->cfg.client_key == 0U) {
        client->cfg.client_key = SYN_XRCE_CLIENT_KEY_DEFAULT;
    }
    if (client->cfg.session_id == 0U) {
        client->cfg.session_id = SYN_XRCE_SESSION_ID_DEFAULT;
    }
    if (client->cfg.heartbeat_period_ms == 0U) {
        client->cfg.heartbeat_period_ms = SYN_XRCE_DEFAULT_HEARTBEAT_MS;
    }

    client->state = SYN_XRCE_STATE_DISCONNECTED;
    client->next_req_id = 1U;
    client->stream_seq_num = 1U;
    return SYN_OK;
}

SYN_Status syn_xrce_client_create_session(SYN_XRCE_Client *client)
{
    if (client == NULL || client->cfg.transport == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    /* Sessionless Header */
    xrce_write_header(&w, 0x00U, 0x00U, 0U, client->cfg.client_key);

    /* CREATE_CLIENT Submessage */
    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_CREATE_CLIENT, 0U);
    (void)syn_cdr_write_u32(&w, client->cfg.client_key);
    (void)syn_cdr_write_u8(&w, client->cfg.session_id);
    (void)syn_cdr_write_u16(&w, (uint16_t)client->cfg.tx_buf_size);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        client->state = SYN_XRCE_STATE_ERROR;
        return SYN_ERROR;
    }

    client->state = SYN_XRCE_STATE_CONNECTING;
    return SYN_OK;
}

static SYN_Status xrce_register_local_object(SYN_XRCE_Client *client, uint16_t id, uint8_t kind,
                                             uint16_t parent)
{
    if (client->object_count >= SYN_XRCE_MAX_OBJECTS) {
        return SYN_ERROR;
    }
    SYN_XRCE_Object *obj = &client->objects[client->object_count++];
    obj->id = id;
    obj->kind = kind;
    obj->parent = parent;
    obj->configured = true;
    return SYN_OK;
}

SYN_Status syn_xrce_client_create_participant(SYN_XRCE_Client *client, uint16_t participant_id)
{
    if (client == NULL || client->cfg.transport == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_CREATE, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, participant_id);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_OBJKIND_PARTICIPANT);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_REPRESENTATION_BY_REF);
    (void)syn_cdr_write_string(&w, "default_participant");
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return xrce_register_local_object(client, participant_id, SYN_XRCE_OBJKIND_PARTICIPANT, 0U);
}

SYN_Status syn_xrce_client_create_topic(SYN_XRCE_Client *client, uint16_t topic_id,
                                        uint16_t participant_id, const char *topic_name,
                                        const char *type_name)
{
    if (client == NULL || topic_name == NULL || type_name == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_CREATE, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, topic_id);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_OBJKIND_TOPIC);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_REPRESENTATION_AS_STRING);
    (void)syn_cdr_write_u16(&w, participant_id);
    (void)syn_cdr_write_string(&w, topic_name);
    (void)syn_cdr_write_string(&w, type_name);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return xrce_register_local_object(client, topic_id, SYN_XRCE_OBJKIND_TOPIC, participant_id);
}

SYN_Status syn_xrce_client_create_publisher(SYN_XRCE_Client *client, uint16_t publisher_id,
                                            uint16_t participant_id)
{
    if (client == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_CREATE, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, publisher_id);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_OBJKIND_PUBLISHER);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_REPRESENTATION_BY_REF);
    (void)syn_cdr_write_u16(&w, participant_id);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return xrce_register_local_object(client, publisher_id, SYN_XRCE_OBJKIND_PUBLISHER,
                                      participant_id);
}

SYN_Status syn_xrce_client_create_subscriber(SYN_XRCE_Client *client, uint16_t subscriber_id,
                                             uint16_t participant_id)
{
    if (client == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_CREATE, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, subscriber_id);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_OBJKIND_SUBSCRIBER);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_REPRESENTATION_BY_REF);
    (void)syn_cdr_write_u16(&w, participant_id);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return xrce_register_local_object(client, subscriber_id, SYN_XRCE_OBJKIND_SUBSCRIBER,
                                      participant_id);
}

SYN_Status syn_xrce_client_create_datawriter(SYN_XRCE_Client *client, uint16_t writer_id,
                                             uint16_t publisher_id, uint16_t topic_id)
{
    if (client == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_CREATE, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, writer_id);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_OBJKIND_DATAWRITER);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_REPRESENTATION_BY_REF);
    (void)syn_cdr_write_u16(&w, publisher_id);
    (void)syn_cdr_write_u16(&w, topic_id);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return xrce_register_local_object(client, writer_id, SYN_XRCE_OBJKIND_DATAWRITER, publisher_id);
}

SYN_Status syn_xrce_client_create_datareader(SYN_XRCE_Client *client, uint16_t reader_id,
                                             uint16_t subscriber_id, uint16_t topic_id)
{
    if (client == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_CREATE, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, reader_id);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_OBJKIND_DATAREADER);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_REPRESENTATION_BY_REF);
    (void)syn_cdr_write_u16(&w, subscriber_id);
    (void)syn_cdr_write_u16(&w, topic_id);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return xrce_register_local_object(client, reader_id, SYN_XRCE_OBJKIND_DATAREADER,
                                      subscriber_id);
}

SYN_Status syn_xrce_client_write_data(SYN_XRCE_Client *client, uint16_t writer_id,
                                      const uint8_t *data, size_t len)
{
    if (client == NULL || (data == NULL && len > 0U)) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_WRITE_DATA, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, writer_id);
    (void)syn_cdr_write_u8(&w, SYN_XRCE_DATA_FORMAT_DATA);
    (void)syn_cdr_write_bytes(&w, data, len);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return SYN_OK;
}

SYN_Status syn_xrce_client_read_data(SYN_XRCE_Client *client, uint16_t reader_id)
{
    if (client == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_CDR_Writer w;
    syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

    xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_BEST_EFFORT,
                      client->stream_seq_num++, client->cfg.client_key);

    size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_READ_DATA, 0U);
    uint16_t req_id = client->next_req_id++;
    (void)syn_cdr_write_u16(&w, req_id);
    (void)syn_cdr_write_u16(&w, reader_id);
    (void)syn_cdr_write_u16(&w, 1U); /* max_messages: 1 */
    (void)syn_cdr_write_u8(&w, SYN_XRCE_DATA_FORMAT_DATA);
    xrce_finish_submessage(&w, len_pos);

    if (w.error || !syn_transport_send(client->cfg.transport, w.data, w.pos)) {
        return SYN_ERROR;
    }

    return SYN_OK;
}

SYN_Status syn_xrce_client_step(SYN_XRCE_Client *client, uint32_t now_ms)
{
    if (client == NULL || client->cfg.transport == NULL) {
        return SYN_INVALID_PARAM;
    }

    /* 1. Receive incoming frame from transport */
    size_t rx_len = 0U;
    if (syn_transport_recv(client->cfg.transport, client->cfg.rx_buf, client->cfg.rx_buf_size,
                           &rx_len) &&
        rx_len >= 4U) {
        SYN_CDR_Reader r;
        syn_cdr_reader_init(&r, client->cfg.rx_buf, rx_len);

        uint8_t session_id = 0U;
        uint8_t stream_id = 0U;
        uint16_t seq_num = 0U;
        (void)syn_cdr_read_u8(&r, &session_id);
        (void)syn_cdr_read_u8(&r, &stream_id);
        (void)syn_cdr_read_u16(&r, &seq_num);

        if (session_id == 0x00U || session_id >= 0x80U) {
            uint32_t client_key = 0U;
            (void)syn_cdr_read_u32(&r, &client_key);
        }

        /* Parse Submessages in payload */
        while (r.pos + 4U <= r.size && !r.error) {
            uint8_t submsg_id = 0U;
            uint8_t flags = 0U;
            uint16_t submsg_len = 0U;
            (void)syn_cdr_read_u8(&r, &submsg_id);
            (void)syn_cdr_read_u8(&r, &flags);
            (void)syn_cdr_read_u16(&r, &submsg_len);

            size_t submsg_start = r.pos;

            if (submsg_id == SYN_XRCE_SUBMSG_STATUS) {
                uint16_t req_id = 0U;
                uint16_t obj_id = 0U;
                uint8_t status = 0U;
                uint8_t impl_status = 0U;
                (void)syn_cdr_read_u16(&r, &req_id);
                (void)syn_cdr_read_u16(&r, &obj_id);
                (void)syn_cdr_read_u8(&r, &status);
                (void)syn_cdr_read_u8(&r, &impl_status);

                if (client->state == SYN_XRCE_STATE_CONNECTING && status == SYN_XRCE_STATUS_OK) {
                    client->state = SYN_XRCE_STATE_CONNECTED;
                }

                if (client->cfg.on_status != NULL) {
                    client->cfg.on_status(req_id, status, client->cfg.user_data);
                }
            } else if (submsg_id == SYN_XRCE_SUBMSG_DATA) {
                uint16_t req_id = 0U;
                uint16_t obj_id = 0U;
                uint8_t data_fmt = 0U;
                (void)syn_cdr_read_u16(&r, &req_id);
                (void)syn_cdr_read_u16(&r, &obj_id);
                (void)syn_cdr_read_u8(&r, &data_fmt);

                size_t pld_len = (r.size > r.pos) ? r.size - r.pos : 0U;
                if (client->cfg.on_data != NULL && pld_len > 0U) {
                    client->cfg.on_data(obj_id, &r.data[r.pos], pld_len, client->cfg.user_data);
                }
            } else {
                /* Skip other submessages */
            }

            r.pos = submsg_start + submsg_len;
        }
    }

    /* 2. Check periodic heartbeat / keep-alive */
    if (client->state == SYN_XRCE_STATE_CONNECTED &&
        (now_ms - client->last_heartbeat_ms >= client->cfg.heartbeat_period_ms ||
         client->last_heartbeat_ms == 0U)) {
        SYN_CDR_Writer w;
        syn_cdr_writer_init(&w, client->cfg.tx_buf, client->cfg.tx_buf_size);

        xrce_write_header(&w, client->cfg.session_id, SYN_XRCE_STREAM_RELIABLE,
                          client->stream_seq_num++, client->cfg.client_key);

        size_t len_pos = xrce_start_submessage(&w, SYN_XRCE_SUBMSG_HEARTBEAT, 0U);
        (void)syn_cdr_write_u16(&w, 1U);
        (void)syn_cdr_write_u16(&w, client->stream_seq_num);
        (void)syn_cdr_write_u8(&w, SYN_XRCE_STREAM_RELIABLE);
        xrce_finish_submessage(&w, len_pos);

        if (!w.error) {
            (void)syn_transport_send(client->cfg.transport, w.data, w.pos);
            client->last_heartbeat_ms = now_ms;
        }
    }

    return SYN_OK;
}

SYN_PT_Status syn_xrce_client_pt(SYN_PT *pt, SYN_Task *task)
{
    if (pt == NULL || task == NULL || task->user_data == NULL) {
        return PT_ENDED;
    }

    SYN_XRCE_Client *client = (SYN_XRCE_Client *)task->user_data;

    PT_BEGIN(pt);

    for (;;) {
        uint32_t now = syn_port_get_tick_ms();
        if (syn_xrce_client_step(client, now) != SYN_OK) {
            break;
        }
        PT_YIELD(pt);
    }

    PT_END(pt);
}

#endif /* SYN_USE_XRCE_DDS */