Skip to content

Integration Paths

Transport splits the library cleanly in two sections. Both halves produce the same decoded oscp_frame_t, such that an application can stay transport-agnostic.


Which one to use?

Variant What to call Why
RS422 (stream) oscp_parser_* Bytes arrive with no message boundaries. The parser syncs on the 0x00 delimiter, COBS-decodes, checks CRC, and invokes a given callback once per valid frame.
CAN-FD (framed) oscp_frame_decode() The controller already delivers complete messages. One stateless call. No parser, no ring buffer, no COBS.

Rule of thumb

If the hardware hands a byte at a time (a UART/RS422 line), use the parser. If it hands a complete payload (an FD-CAN mailbox), use the decoder.


Transport Examples

Use this when bytes arrive without message boundaries. Feed the parser bytes as they arrive. Could be a single byte from an ISR, or a buffer drained from a ring. Parser calls the callback once per valid frame.

bytes ─► parser_feed() ─► sync on 0x00 ─► COBS decode ─► CRC check ─► callback

The parser owns a fixed-size buffer inside oscp_parser_t; there is no dynamic allocation. Framing, COBS, CRC and overflow errors are counted in oscp_stats_t rather than thrown.

oscp_parser_t parser;
oscp_parser_init(&parser, on_frame, NULL);

uint8_t buf[256];
size_t  n;
while ((n = uart_read(buf, sizeof(buf))) > 0) {
    oscp_parser_feed_buf(&parser, buf, n); /* on_frame() fires per frame */
}

See Examples for more details.

Use this when the transport already delivers whole messages. A single stateless call decodes one payload into a frame, no parser state to carry between calls.

void on_can_message(const uint8_t *data, size_t len) {
    oscp_frame_t frame;
    if (oscp_frame_decode(data, len, &frame) == OSCP_OK) {
        /* frame is valid and CRC-checked */
    }
}

DLC padding is handled by the decoder

CAN-FD pads payloads up to the nearest DLC bucket, so a 61-byte RAW frame rides inside a 64-byte message. len may therefore exceed the frame's own length. The decoder derives the true length from the frame type and verifies the CRC over the frame only. Padding is ignored.

See Examples for more details.


Both paths converge

Whichever transport used, decoding yields a tagged union:

typedef struct {
    oscp_frame_type_t type;
    union {
        oscp_raw_t     raw;
        oscp_euler_t   euler;
        oscp_quat_t    quat;
        oscp_rot_mat_t rot_mat;
        oscp_gnss_t    gnss;
        oscp_debug_1_t debug1;
        oscp_debug_2_t debug2;
        oscp_startup_t startup;
    } content;
} oscp_frame_t;

Ease of use

Branch on frame.type and read the matching member through the accessor macros (oscp_euler(&frame), oscp_raw(&frame), …). The full frame catalogue is in the Frame Reference.


Next: Frame reference →