Skip to content

File syn_isotp.c

File List > proto > syn_isotp.c

Go to the documentation of this file

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

#if !defined(SYN_USE_ISOTP) || SYN_USE_ISOTP

#include "../util/syn_assert.h"
#include "../util/syn_pack.h"
#include "syn_isotp.h"

#include <string.h>

#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
/* ── CAN FD length padding helper (ISO 11898-1 valid lengths) ───────────── */
static uint8_t syn_can_fd_pad_len(uint8_t len)
{
    if (len <= 8)
        return len;
    if (len <= 12)
        return 12;
    if (len <= 16)
        return 16;
    if (len <= 20)
        return 20;
    if (len <= 24)
        return 24;
    if (len <= 32)
        return 32;
    if (len <= 48)
        return 48;
    return 64;
}
#endif

/* ── STmin conversion helper (microsecond resolution) ────────────────────── */
static uint32_t syn_isotp_decode_stmin_us(uint8_t st_min)
{
    if (st_min <= 0x7F) {
        return (uint32_t)st_min * 1000U; /* 0..127 ms -> us */
    }
    if (st_min >= 0xF1 && st_min <= 0xF9) {
        return (uint32_t)(st_min - 0xF0) * 100U; /* 100..900 us */
    }
    return 0;
}

void syn_isotp_set_timeouts(SYN_ISOTP_Link *link, uint32_t n_bs_ms, uint32_t n_cr_ms)
{
    if (link == NULL)
        return;
    link->n_bs_timeout_us = (n_bs_ms > 0) ? (n_bs_ms * 1000U) : (SYN_ISOTP_DEFAULT_N_BS_MS * 1000U);
    link->n_cr_timeout_us = (n_cr_ms > 0) ? (n_cr_ms * 1000U) : (SYN_ISOTP_DEFAULT_N_CR_MS * 1000U);
}

void syn_isotp_init(SYN_ISOTP_Link *link, uint32_t rx_id, uint32_t tx_id, uint8_t *rx_buf,
                    size_t rx_buf_size, uint8_t *tx_buf, size_t tx_buf_size)
{
#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
    syn_isotp_init_fd(link, rx_id, tx_id, rx_buf, rx_buf_size, tx_buf, tx_buf_size, false);
#else
    SYN_ASSERT(link != NULL);

    memset(link, 0, sizeof(*link));
    link->rx_id = rx_id;
    link->tx_id = tx_id;
    link->rx_buf = rx_buf;
    link->rx_buf_size = rx_buf_size;
    link->tx_buf = tx_buf;
    link->tx_buf_size = tx_buf_size;
    link->tx_state = SYN_ISOTP_TX_IDLE;
    link->rx_state = SYN_ISOTP_RX_IDLE;
    link->n_bs_timeout_us = SYN_ISOTP_DEFAULT_N_BS_MS * 1000U;
    link->n_cr_timeout_us = SYN_ISOTP_DEFAULT_N_CR_MS * 1000U;
#endif
}

#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
void syn_isotp_init_fd(SYN_ISOTP_Link *link, uint32_t rx_id, uint32_t tx_id, uint8_t *rx_buf,
                       size_t rx_buf_size, uint8_t *tx_buf, size_t tx_buf_size, bool is_fd)
{
    SYN_ASSERT(link != NULL);

    memset(link, 0, sizeof(*link));
    link->rx_id = rx_id;
    link->tx_id = tx_id;
    link->is_fd = is_fd;
    link->rx_buf = rx_buf;
    link->rx_buf_size = rx_buf_size;
    link->tx_buf = tx_buf;
    link->tx_buf_size = tx_buf_size;
    link->tx_state = SYN_ISOTP_TX_IDLE;
    link->rx_state = SYN_ISOTP_RX_IDLE;
    link->n_bs_timeout_us = SYN_ISOTP_DEFAULT_N_BS_MS * 1000U;
    link->n_cr_timeout_us = SYN_ISOTP_DEFAULT_N_CR_MS * 1000U;
}
#endif

SYN_Status syn_isotp_send(SYN_ISOTP_Link *link, const uint8_t *payload, size_t payload_len)
{
    if (link == NULL || payload == NULL || payload_len < 1) {
        return SYN_INVALID_PARAM;
    }
    if (link->tx_state != SYN_ISOTP_TX_IDLE) {
        return SYN_BUSY;
    }
    if (payload_len > link->tx_buf_size) {
        return SYN_INVALID_PARAM;
    }

    memcpy(link->tx_buf, payload, payload_len);
    link->tx_len = payload_len;
    link->tx_offset = 0;
    link->tx_seq = 1;
    link->tx_bs_count = 0;

#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
    size_t max_sf_len = link->is_fd ? 62 : 7;
#else
    size_t max_sf_len = 7;
#endif

    if (payload_len <= max_sf_len) {
        link->tx_state = SYN_ISOTP_TX_SEND_SF;
    } else {
        link->tx_state = SYN_ISOTP_TX_SEND_FF;
    }

    return SYN_OK;
}

bool syn_isotp_get_tx_frame(SYN_ISOTP_Link *link, SYN_CAN_Frame *frame)
{
    if (link == NULL || frame == NULL)
        return false;

    memset(frame, 0, sizeof(*frame));
    frame->id = link->tx_id;
#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
    frame->is_fd = link->is_fd;
#endif

    /* Flow Control response takes priority */
    if (link->rx_fc_pending) {
        link->rx_fc_pending = false;
        frame->data[0] = SYN_ISOTP_PCI_FC | link->rx_fc_status;
        frame->data[1] = 0; /* BS = 0 (unlimited) */
        frame->data[2] = 0; /* STmin = 0 ms */
        frame->dlc = 8;
        return true;
    }

    switch (link->tx_state) {
    case SYN_ISOTP_TX_SEND_SF:
#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
        if (link->is_fd) {
            size_t sf_len = link->tx_len;
            if (sf_len <= 7) {
                frame->data[0] = SYN_ISOTP_PCI_SF | (uint8_t)sf_len;
                memcpy(&frame->data[1], link->tx_buf, sf_len);
                frame->dlc = syn_can_fd_pad_len((uint8_t)(sf_len + 1));
            } else {
                frame->data[0] = SYN_ISOTP_PCI_SF;
                frame->data[1] = (uint8_t)sf_len;
                memcpy(&frame->data[2], link->tx_buf, sf_len);
                frame->dlc = syn_can_fd_pad_len((uint8_t)(sf_len + 2));
            }
        } else {
            frame->data[0] = SYN_ISOTP_PCI_SF | (uint8_t)link->tx_len;
            memcpy(&frame->data[1], link->tx_buf, link->tx_len);
            frame->dlc = 8;
        }
#else
        frame->data[0] = SYN_ISOTP_PCI_SF | (uint8_t)link->tx_len;
        memcpy(&frame->data[1], link->tx_buf, link->tx_len);
        frame->dlc = 8;
#endif
        link->tx_state = SYN_ISOTP_TX_IDLE;
        return true;

    case SYN_ISOTP_TX_SEND_FF: {
        size_t payload_in_ff;
#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
        if (link->is_fd) {
            if (link->tx_len <= 4095) {
                frame->data[0] = SYN_ISOTP_PCI_FF | (uint8_t)((link->tx_len >> 8) & 0x0F);
                frame->data[1] = (uint8_t)(link->tx_len & 0xFF);
                payload_in_ff = (link->tx_len > 62) ? 62 : link->tx_len;
                memcpy(&frame->data[2], link->tx_buf, payload_in_ff);
                frame->dlc = syn_can_fd_pad_len((uint8_t)(payload_in_ff + 2));
            } else {
                frame->data[0] = SYN_ISOTP_PCI_FF;
                frame->data[1] = 0;
                syn_poke_u32((uint32_t)link->tx_len, frame->data, 2);
                payload_in_ff = (link->tx_len > 58) ? 58 : link->tx_len;
                memcpy(&frame->data[6], link->tx_buf, payload_in_ff);
                frame->dlc = syn_can_fd_pad_len((uint8_t)(payload_in_ff + 6));
            }
        } else {
            frame->data[0] = SYN_ISOTP_PCI_FF | (uint8_t)((link->tx_len >> 8) & 0x0F);
            frame->data[1] = (uint8_t)(link->tx_len & 0xFF);
            payload_in_ff = 6;
            memcpy(&frame->data[2], link->tx_buf, payload_in_ff);
            frame->dlc = 8;
        }
#else
        frame->data[0] = SYN_ISOTP_PCI_FF | (uint8_t)((link->tx_len >> 8) & 0x0F);
        frame->data[1] = (uint8_t)(link->tx_len & 0xFF);
        payload_in_ff = 6;
        memcpy(&frame->data[2], link->tx_buf, payload_in_ff);
        frame->dlc = 8;
#endif
        link->tx_offset = payload_in_ff;
        link->tx_state = SYN_ISOTP_TX_WAIT_FC;
        link->tx_timeout_timer_us = link->n_bs_timeout_us;
        return true;
    }

    case SYN_ISOTP_TX_WAIT_FC:
        return false;

    case SYN_ISOTP_TX_SEND_CF: {
        if (link->tx_st_timer_us > 0) {
            return false;
        }

        size_t rem = link->tx_len - link->tx_offset;
        if (rem == 0) {
            link->tx_state = SYN_ISOTP_TX_IDLE;
            return false;
        }

        frame->data[0] = SYN_ISOTP_PCI_CF | (link->tx_seq & 0x0F);
        size_t payload_len;

#if defined(SYN_USE_CAN_FD) && SYN_USE_CAN_FD
        if (link->is_fd) {
            payload_len = (rem > 63) ? 63 : rem;
            memcpy(&frame->data[1], &link->tx_buf[link->tx_offset], payload_len);
            frame->dlc = syn_can_fd_pad_len((uint8_t)(payload_len + 1));
        } else {
            payload_len = (rem > 7) ? 7 : rem;
            memcpy(&frame->data[1], &link->tx_buf[link->tx_offset], payload_len);
            frame->dlc = 8;
        }
#else
        payload_len = (rem > 7) ? 7 : rem;
        memcpy(&frame->data[1], &link->tx_buf[link->tx_offset], payload_len);
        frame->dlc = 8;
#endif

        link->tx_offset += payload_len;
        link->tx_seq = (uint8_t)((link->tx_seq + 1) & 0x0F);
        link->tx_st_timer_us = syn_isotp_decode_stmin_us(link->tx_st_min);

        if (link->tx_bs > 0) {
            link->tx_bs_count++;
            if (link->tx_bs_count >= link->tx_bs && link->tx_offset < link->tx_len) {
                link->tx_bs_count = 0;
                link->tx_state = SYN_ISOTP_TX_WAIT_FC;
                link->tx_timeout_timer_us = link->n_bs_timeout_us;
            }
        }

        if (link->tx_offset >= link->tx_len) {
            link->tx_state = SYN_ISOTP_TX_IDLE;
        }
        return true;
    }

    default:
        break;
    }

    return false;
}

void syn_isotp_process_rx_frame(SYN_ISOTP_Link *link, const SYN_CAN_Frame *frame)
{
    if (link == NULL || frame == NULL)
        return;
    if (frame->id != link->rx_id)
        return;

    uint8_t pci_type = frame->data[0] & 0xF0U;

    switch (pci_type) {
    case SYN_ISOTP_PCI_SF: {
        size_t sf_len = frame->data[0] & 0x0FU;
        size_t data_offset = 1;

        if (sf_len == 0) {
            /* 8-bit length SF in CAN FD */
            sf_len = frame->data[1];
            data_offset = 2;
        }

        if (sf_len >= 1 && sf_len <= link->rx_buf_size && link->rx_buf != NULL) {
            memcpy(link->rx_buf, &frame->data[data_offset], sf_len);
            link->rx_len = sf_len;
            link->rx_state = SYN_ISOTP_RX_COMPLETE;
        }
        break;
    }

    case SYN_ISOTP_PCI_FF: {
        size_t ff_len;
        size_t data_offset;

        uint16_t len_12 = syn_peek_u16(frame->data, 0) & 0x0FFFU;
        if (len_12 != 0) {
            ff_len = len_12;
            data_offset = 2;
        } else {
            /* 32-bit Extended First Frame */
            ff_len = (size_t)syn_peek_u32(frame->data, 2);
            data_offset = 6;
        }

        if (ff_len > 7 && link->rx_buf != NULL) {
            if (ff_len > link->rx_buf_size) {
                link->rx_fc_pending = true;
                link->rx_fc_status = SYN_ISOTP_FC_OVERFLOW;
                link->rx_state = SYN_ISOTP_RX_IDLE;
            } else {
                size_t frame_len = (frame->dlc > 0) ? frame->dlc : 8;
                size_t payload_in_ff = (frame_len > data_offset) ? (frame_len - data_offset) : 0;
                if (payload_in_ff > ff_len)
                    payload_in_ff = ff_len;

                memcpy(link->rx_buf, &frame->data[data_offset], payload_in_ff);
                link->rx_len = payload_in_ff;
                link->rx_expected = ff_len;
                link->rx_seq = 1;
                link->rx_fc_pending = true;
                link->rx_fc_status = SYN_ISOTP_FC_CTS;
                link->rx_state = SYN_ISOTP_RX_WAIT_CF;
                link->rx_timeout_timer_us = link->n_cr_timeout_us;
            }
        }
        break;
    }

    case SYN_ISOTP_PCI_CF: {
        if (link->rx_state == SYN_ISOTP_RX_WAIT_CF) {
            uint8_t seq = frame->data[0] & 0x0FU;
            if (seq == link->rx_seq) {
                size_t frame_len = (frame->dlc > 0) ? frame->dlc : 8;
                size_t rem = link->rx_expected - link->rx_len;
                size_t cf_payload = (frame_len > 1) ? (frame_len - 1) : 0;
                if (cf_payload > rem)
                    cf_payload = rem;

                memcpy(&link->rx_buf[link->rx_len], &frame->data[1], cf_payload);

                link->rx_len += cf_payload;
                link->rx_seq = (uint8_t)((link->rx_seq + 1) & 0x0F);

                if (link->rx_len >= link->rx_expected) {
                    link->rx_state = SYN_ISOTP_RX_COMPLETE;
                    link->rx_timeout_timer_us = 0;
                } else {
                    link->rx_timeout_timer_us = link->n_cr_timeout_us;
                }
            } else {
                /* Sequence mismatch error */
                link->rx_state = SYN_ISOTP_RX_IDLE;
                link->rx_timeout_timer_us = 0;
            }
        }
        break;
    }

    case SYN_ISOTP_PCI_FC: {
        if (link->tx_state == SYN_ISOTP_TX_WAIT_FC) {
            uint8_t fc_status = frame->data[0] & 0x0FU;
            if (fc_status == SYN_ISOTP_FC_CTS) {
                link->tx_bs = frame->data[1];
                link->tx_st_min = frame->data[2];
                link->tx_st_timer_us = 0;
                link->tx_timeout_timer_us = 0;
                link->tx_state = SYN_ISOTP_TX_SEND_CF;
            } else if (fc_status == SYN_ISOTP_FC_OVERFLOW) {
                link->tx_state = SYN_ISOTP_TX_IDLE;
                link->tx_timeout_timer_us = 0;
            }
        }
        break;
    }

    default:
        break;
    }
}

ssize_t syn_isotp_receive(SYN_ISOTP_Link *link, uint8_t *out_buf, size_t max_len)
{
    if (link == NULL || out_buf == NULL) {
        return -1;
    }

    if (link->rx_state == SYN_ISOTP_RX_COMPLETE) {
        size_t copy_len = (link->rx_len > max_len) ? max_len : link->rx_len;
        memcpy(out_buf, link->rx_buf, copy_len);
        link->rx_state = SYN_ISOTP_RX_IDLE;
        if (copy_len > 32767U)
            copy_len = 32767U;
        return (ssize_t)copy_len;
    }

    return -1;
}

void syn_isotp_step(SYN_ISOTP_Link *link, uint32_t dt_ms)
{
    syn_isotp_step_us(link, dt_ms * 1000U);
}

void syn_isotp_step_us(SYN_ISOTP_Link *link, uint32_t dt_us)
{
    if (link == NULL)
        return;

    if (link->tx_st_timer_us > 0) {
        if (dt_us >= link->tx_st_timer_us) {
            link->tx_st_timer_us = 0;
        } else {
            link->tx_st_timer_us -= dt_us;
        }
    }

    /* N_Bs timeout (waiting for Flow Control frame on sender side) */
    if (link->tx_state == SYN_ISOTP_TX_WAIT_FC && link->tx_timeout_timer_us > 0) {
        if (dt_us >= link->tx_timeout_timer_us) {
            link->tx_timeout_timer_us = 0;
            link->tx_state = SYN_ISOTP_TX_IDLE; /* Abort Tx on N_Bs timeout */
        } else {
            link->tx_timeout_timer_us -= dt_us;
        }
    }

    /* N_Cr timeout (waiting for Consecutive Frame on receiver side) */
    if (link->rx_state == SYN_ISOTP_RX_WAIT_CF && link->rx_timeout_timer_us > 0) {
        if (dt_us >= link->rx_timeout_timer_us) {
            link->rx_timeout_timer_us = 0;
            link->rx_state = SYN_ISOTP_RX_IDLE; /* Abort Rx on N_Cr timeout */
        } else {
            link->rx_timeout_timer_us -= dt_us;
        }
    }
}

#endif /* SYN_USE_ISOTP */