From 2aff630caaec6a18deff0a3701cb34e34ae707dd Mon Sep 17 00:00:00 2001 From: Matt <47545907+SoundMatt@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:37:10 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20v0.108.0=20=E2=80=94=20enforce=20TC18?= =?UTF-8?q?=20Table=2030's=20Row-2=20evt[2:0]=20rule=20for=20I2C/UART/ISEL?= =?UTF-8?q?ED/MDIO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the Table 30 centralization started in #153 (ADC): none of I2C's decode_transfer_request, UART's decode_write_request/ decode_read_request, ISELED's decode_command_request, or MDIO's decode_read_request/decode_write_request checked evt[2:0] at all, despite every one of their own encoders already hardcoding it to 0 ("no channel selector" per each module's own file header). Per TC18 §13.5 Table 30, this endpoint-type row's only legal evt[2:0] value for a plain request is 000b; every other value is either reserved (reject with UNSUPPORTED_CMD) or selects a differently-shaped configuration-write request (111b, §12.7.1) these decoders must not accept. All five now call the shared rcp_acf_evt_row2_is_plain() primitive (acf.h/acf.c, introduced in #153) at the same point in their validation sequence ADC already established. None of these decode functions are wired into any real server dispatch loop today (same finding as #153), so no observed behavioral impact on any live path -- but each is a real conformance defect any future dispatch wiring would inherit. Mutation-tested per endpoint: reverting each evt check makes exactly its own new rejection test(s) fail, and no others. Remaining Table 30 work, tracked separately: CAN and LIN each invented a different, spec-incompatible use of evt[2:0] (frame-format selection, comparison-mode selection) and need a real redesign, not this same additive fix. New public API (5 new error variants) and real decode-behavior tightening across 5 modules -- MINOR bump. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --- .fusa-reqs.json | 32 ++++++++++++++++------------ .fusa.json | 2 +- CMakeLists.txt | 2 +- include/rcp/ep_i2c.h | 11 ++++++++-- include/rcp/ep_iseled.h | 10 ++++++++- include/rcp/ep_mdio.h | 15 +++++++++++-- include/rcp/ep_uart.h | 15 +++++++++++-- include/rcp/version.h | 2 +- src/ep_i2c.c | 2 ++ src/ep_iseled.c | 2 ++ src/ep_mdio.c | 3 +++ src/ep_uart.c | 3 +++ tests/test_ep_i2c.c | 26 +++++++++++++++++++++++ tests/test_ep_iseled.c | 24 +++++++++++++++++++++ tests/test_ep_mdio.c | 47 +++++++++++++++++++++++++++++++++++++++++ tests/test_ep_uart.c | 45 +++++++++++++++++++++++++++++++++++++++ 16 files changed, 218 insertions(+), 23 deletions(-) diff --git a/.fusa-reqs.json b/.fusa-reqs.json index 7cc0c7e..1885482 100644 --- a/.fusa-reqs.json +++ b/.fusa-reqs.json @@ -3325,11 +3325,12 @@ { "id": "REQ-I2C-012", "title": "rcp_ep_i2c_decode_transfer_request() rejects a malformed or misaddressed frame and accepts both directions", - "text": "rcp_ep_i2c_decode_transfer_request() shall return RCP_EP_I2C_ERR_SHORT_FRAME for a frame too short for the ACF_ABB fixed header or its declared payload length, RCP_EP_I2C_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame, and RCP_EP_I2C_ERR_WRONG_BUS when byte_bus_id != expected_bus_id. It shall accept BOTH op senses -- an I2C transfer is half duplex and either-directional, so neither is malformed -- and report which via *out_direction.", + "text": "rcp_ep_i2c_decode_transfer_request() shall return RCP_EP_I2C_ERR_SHORT_FRAME for a frame too short for the ACF_ABB fixed header or its declared payload length, RCP_EP_I2C_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame, RCP_EP_I2C_ERR_WRONG_BUS when byte_bus_id != expected_bus_id, and RCP_EP_I2C_ERR_BAD_EVT when evt[2:0] is not 0b000 -- TC18 §13.5 Table 30's only legal value for a plain request in I2C's endpoint-type row; every other value shall be rejected with error code UNSUPPORTED_CMD at the dispatch layer. It shall accept BOTH op senses -- an I2C transfer is half duplex and either-directional, so neither is malformed -- and report which via *out_direction.", "standard": "iso26262", "level": "ASIL-B", "asil": "ASIL-B", - "scope": "tc18" + "scope": "tc18", + "tc18": "§13.5 Table 30, TC18.txt L3666-3710" }, { "id": "REQ-I2C-013", @@ -3558,12 +3559,13 @@ }, { "id": "REQ-UART-020", - "title": "rcp_ep_uart_decode_write_request() rejects a malformed, misaddressed, or misdirected frame", - "text": "rcp_ep_uart_decode_write_request() shall return RCP_EP_UART_ERR_SHORT_FRAME for a frame too short for the ACF_ABB fixed header or its declared payload length, RCP_EP_UART_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame, RCP_EP_UART_ERR_WRONG_BUS when byte_bus_id != expected_bus_id, and RCP_EP_UART_ERR_WRONG_OP when op is not RCP_ACF_OP_WRITE.", + "title": "rcp_ep_uart_decode_write_request() rejects a malformed, misaddressed, misdirected, or wrong-evt frame", + "text": "rcp_ep_uart_decode_write_request() shall return RCP_EP_UART_ERR_SHORT_FRAME for a frame too short for the ACF_ABB fixed header or its declared payload length, RCP_EP_UART_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame, RCP_EP_UART_ERR_WRONG_BUS when byte_bus_id != expected_bus_id, RCP_EP_UART_ERR_WRONG_OP when op is not RCP_ACF_OP_WRITE, and RCP_EP_UART_ERR_BAD_EVT when evt[2:0] is not 0b000 -- TC18 §13.5 Table 30's only legal value for a plain request in UART's endpoint-type row; every other value shall be rejected with error code UNSUPPORTED_CMD at the dispatch layer.", "standard": "iso26262", "level": "ASIL-B", "asil": "ASIL-B", - "scope": "tc18" + "scope": "tc18", + "tc18": "§13.5 Table 30, TC18.txt L3666-3710" }, { "id": "REQ-UART-021", @@ -3604,11 +3606,12 @@ { "id": "REQ-UART-025", "title": "rcp_ep_uart_decode_read_request() rejects a payload-bearing read request with RCP_EP_UART_ERR_UNKNOWN_CMD", - "text": "rcp_ep_uart_decode_read_request() shall return RCP_EP_UART_ERR_UNKNOWN_CMD, rather than RCP_EP_UART_OK, when an otherwise well-formed read request carries a non-empty payload -- the deliberate asymmetry against GPIO/PWM_OUT request types documented in ep_uart.h.", + "text": "rcp_ep_uart_decode_read_request() shall return RCP_EP_UART_ERR_UNKNOWN_CMD, rather than RCP_EP_UART_OK, when an otherwise well-formed read request carries a non-empty payload -- the deliberate asymmetry against GPIO/PWM_OUT request types documented in ep_uart.h. It shall also return RCP_EP_UART_ERR_BAD_EVT when evt[2:0] is not 0b000 -- TC18 §13.5 Table 30's only legal value for a plain request in UART's endpoint-type row; every other value shall be rejected with error code UNSUPPORTED_CMD at the dispatch layer.", "standard": "iso26262", "level": "ASIL-B", "asil": "ASIL-B", - "scope": "tc18" + "scope": "tc18", + "tc18": "§13.5 Table 30, TC18.txt L3666-3710" }, { "id": "REQ-UART-026", @@ -6019,11 +6022,12 @@ { "id": "REQ-ISELED-022", "title": "rcp_ep_iseled_decode_command_request() validates and decodes an ACF-level command request", - "text": "rcp_ep_iseled_decode_command_request() shall return RCP_EP_ISELED_ERR_SHORT_FRAME for a frame shorter than the ACF_ABB header or its declared payload, RCP_EP_ISELED_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame, RCP_EP_ISELED_ERR_WRONG_BUS for a mismatched byte_bus_id, RCP_EP_ISELED_ERR_WRONG_OP for an op other than RCP_ACF_OP_WRITE, and otherwise RCP_EP_ISELED_OK with the plain payload and transaction number populated.", + "text": "rcp_ep_iseled_decode_command_request() shall return RCP_EP_ISELED_ERR_SHORT_FRAME for a frame shorter than the ACF_ABB header or its declared payload, RCP_EP_ISELED_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame, RCP_EP_ISELED_ERR_WRONG_BUS for a mismatched byte_bus_id, RCP_EP_ISELED_ERR_WRONG_OP for an op other than RCP_ACF_OP_WRITE, RCP_EP_ISELED_ERR_BAD_EVT when evt[2:0] is not 0b000 (TC18 §13.5 Table 30's only legal value for a plain request in ISELED's endpoint-type row; every other value shall be rejected with error code UNSUPPORTED_CMD at the dispatch layer), and otherwise RCP_EP_ISELED_OK with the plain payload and transaction number populated.", "standard": "iso26262", "level": "ASIL-B", "asil": "ASIL-B", - "scope": "tc18" + "scope": "tc18", + "tc18": "§13.5 Table 30, TC18.txt L3666-3710" }, { "id": "REQ-ISELED-023", @@ -6154,11 +6158,12 @@ { "id": "REQ-MDIO-013", "title": "rcp_ep_mdio_decode_read_request() validates and decodes an ACF-level MDIO read request", - "text": "rcp_ep_mdio_decode_read_request() shall return RCP_EP_MDIO_ERR_SHORT_FRAME for a frame shorter than the ACF_ABB header, its declared payload, or the 7-byte request prefix; RCP_EP_MDIO_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame; RCP_EP_MDIO_ERR_WRONG_BUS for a mismatched byte_bus_id; RCP_EP_MDIO_ERR_WRONG_OP for an op other than RCP_ACF_OP_READ; RCP_EP_MDIO_ERR_BAD_ADDR when the decoded address fails rcp_ep_mdio_addr_valid(); RCP_EP_MDIO_ERR_BAD_WORD_COUNT when the decoded word_count is 0 or exceeds RCP_EP_MDIO_MAX_BURST_WORDS; and otherwise RCP_EP_MDIO_OK with the address, word count, and transaction number populated.", + "text": "rcp_ep_mdio_decode_read_request() shall return RCP_EP_MDIO_ERR_SHORT_FRAME for a frame shorter than the ACF_ABB header, its declared payload, or the 7-byte request prefix; RCP_EP_MDIO_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame; RCP_EP_MDIO_ERR_WRONG_BUS for a mismatched byte_bus_id; RCP_EP_MDIO_ERR_WRONG_OP for an op other than RCP_ACF_OP_READ; RCP_EP_MDIO_ERR_BAD_EVT when evt[2:0] is not 0b000 (TC18 §13.5 Table 30's only legal value for a plain request in MDIO's endpoint-type row; every other value shall be rejected with error code UNSUPPORTED_CMD at the dispatch layer); RCP_EP_MDIO_ERR_BAD_ADDR when the decoded address fails rcp_ep_mdio_addr_valid(); RCP_EP_MDIO_ERR_BAD_WORD_COUNT when the decoded word_count is 0 or exceeds RCP_EP_MDIO_MAX_BURST_WORDS; and otherwise RCP_EP_MDIO_OK with the address, word count, and transaction number populated.", "standard": "iso26262", "level": "ASIL-B", "asil": "ASIL-B", - "scope": "tc18" + "scope": "tc18", + "tc18": "§13.5 Table 30, TC18.txt L3666-3710" }, { "id": "REQ-MDIO-014", @@ -6190,11 +6195,12 @@ { "id": "REQ-MDIO-017", "title": "rcp_ep_mdio_decode_write_request() validates and decodes an ACF-level MDIO write request", - "text": "rcp_ep_mdio_decode_write_request() shall return RCP_EP_MDIO_ERR_SHORT_FRAME for a frame shorter than the ACF_ABB header, its declared payload, or the 5-byte address prefix; RCP_EP_MDIO_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame; RCP_EP_MDIO_ERR_WRONG_BUS for a mismatched byte_bus_id; RCP_EP_MDIO_ERR_WRONG_OP for an op other than RCP_ACF_OP_WRITE; RCP_EP_MDIO_ERR_BAD_ADDR when the decoded address fails rcp_ep_mdio_addr_valid(); RCP_EP_MDIO_ERR_BAD_WORD_COUNT when the trailing words region's byte length is odd, is 0, or represents more than RCP_EP_MDIO_MAX_BURST_WORDS words; and otherwise RCP_EP_MDIO_OK with the address, packed word bytes, word count, and transaction number populated.", + "text": "rcp_ep_mdio_decode_write_request() shall return RCP_EP_MDIO_ERR_SHORT_FRAME for a frame shorter than the ACF_ABB header, its declared payload, or the 5-byte address prefix; RCP_EP_MDIO_ERR_BAD_MSG_TYPE for a non-ACF_ABB frame; RCP_EP_MDIO_ERR_WRONG_BUS for a mismatched byte_bus_id; RCP_EP_MDIO_ERR_WRONG_OP for an op other than RCP_ACF_OP_WRITE; RCP_EP_MDIO_ERR_BAD_EVT when evt[2:0] is not 0b000 (TC18 §13.5 Table 30's only legal value for a plain request in MDIO's endpoint-type row; every other value shall be rejected with error code UNSUPPORTED_CMD at the dispatch layer); RCP_EP_MDIO_ERR_BAD_ADDR when the decoded address fails rcp_ep_mdio_addr_valid(); RCP_EP_MDIO_ERR_BAD_WORD_COUNT when the trailing words region's byte length is odd, is 0, or represents more than RCP_EP_MDIO_MAX_BURST_WORDS words; and otherwise RCP_EP_MDIO_OK with the address, packed word bytes, word count, and transaction number populated.", "standard": "iso26262", "level": "ASIL-B", "asil": "ASIL-B", - "scope": "tc18" + "scope": "tc18", + "tc18": "§13.5 Table 30, TC18.txt L3666-3710" }, { "id": "REQ-MDIO-018", diff --git a/.fusa.json b/.fusa.json index 52279ab..c5b1f68 100644 --- a/.fusa.json +++ b/.fusa.json @@ -2,7 +2,7 @@ "configVersion": "1.0", "project": { "name": "c-RCP", - "version": "0.107.0" + "version": "0.108.0" }, "standard": "iso26262", "strict": false, diff --git a/CMakeLists.txt b/CMakeLists.txt index 23f1411..9e04a41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(c-rcp - VERSION 0.107.0 + VERSION 0.108.0 DESCRIPTION "OPEN Alliance TC18 Remote Control Protocol (C)" HOMEPAGE_URL "https://github.com/SoundMatt/c-RCP" LANGUAGES C diff --git a/include/rcp/ep_i2c.h b/include/rcp/ep_i2c.h index 9a557ed..cedd52d 100644 --- a/include/rcp/ep_i2c.h +++ b/include/rcp/ep_i2c.h @@ -261,6 +261,11 @@ typedef enum { * this module: both op senses are valid on an I2C transfer (see the * file header), so there is no longer a "wrong" one to reject. */ RCP_EP_I2C_ERR_WRONG_OP = 4, + /* evt[2:0] is not 0b000, TC18 §13.5 Table 30's only legal value for a + * plain (non-configuration) request in I2C's endpoint-type row -- + * caller shall respond with error code UNSUPPORTED_CMD (see + * rcp_acf_evt_row2_is_plain()). */ + RCP_EP_I2C_ERR_BAD_EVT = 5, } rcp_ep_i2c_errc_t; /* Human-readable message for an rcp_ep_i2c_errc_t value. Never returns NULL. */ @@ -299,8 +304,10 @@ rcp_bytes_t rcp_ep_i2c_encode_transfer_request(rcp_byte_bus_id_t byte_bus_id, * Fails with RCP_EP_I2C_ERR_SHORT_FRAME if b is shorter than the ACF_ABB * fixed header or its declared payload length; RCP_EP_I2C_ERR_BAD_MSG_TYPE * if b is not an ACF_ABB message; RCP_EP_I2C_ERR_WRONG_BUS if its - * byte_bus_id != expected_bus_id. Both op senses are accepted -- see the - * file header -- and reported via *out_direction. + * byte_bus_id != expected_bus_id; RCP_EP_I2C_ERR_BAD_EVT if its evt[2:0] + * is not 0b000 (rcp_acf_evt_row2_is_plain(), TC18 §13.5 Table 30 -- the + * caller shall respond with error code UNSUPPORTED_CMD). Both op senses + * are accepted -- see the file header -- and reported via *out_direction. * * On RCP_EP_I2C_OK, *out_direction, *out_read_size (the requested * read_size for RCP_EP_I2C_DIR_READ, 0 for RCP_EP_I2C_DIR_WRITE, whose diff --git a/include/rcp/ep_iseled.h b/include/rcp/ep_iseled.h index 9062313..1d51a1b 100644 --- a/include/rcp/ep_iseled.h +++ b/include/rcp/ep_iseled.h @@ -369,6 +369,11 @@ typedef enum { RCP_EP_ISELED_ERR_CRC_MISMATCH = 6, RCP_EP_ISELED_ERR_ODD_SYMBOL_COUNT = 7, RCP_EP_ISELED_ERR_ALLOC = 8, + /* evt[2:0] is not 0b000, TC18 §13.5 Table 30's only legal value for a + * plain (non-configuration) request in ISELED's endpoint-type row -- + * caller shall respond with error code UNSUPPORTED_CMD (see + * rcp_acf_evt_row2_is_plain()). */ + RCP_EP_ISELED_ERR_BAD_EVT = 9, } rcp_ep_iseled_errc_t; /* Human-readable message for an rcp_ep_iseled_errc_t value. Never returns NULL. */ @@ -410,7 +415,10 @@ rcp_bytes_t rcp_ep_iseled_encode_command_request(rcp_byte_bus_id_t byte_bus_id, * the ACF_ABB fixed header or its declared payload length; * RCP_EP_ISELED_ERR_BAD_MSG_TYPE if b is not an ACF_ABB message; * RCP_EP_ISELED_ERR_WRONG_BUS if its byte_bus_id != expected_bus_id; - * RCP_EP_ISELED_ERR_WRONG_OP if its op is not RCP_ACF_OP_WRITE. On + * RCP_EP_ISELED_ERR_WRONG_OP if its op is not RCP_ACF_OP_WRITE; + * RCP_EP_ISELED_ERR_BAD_EVT if its evt[2:0] is not 0b000 + * (rcp_acf_evt_row2_is_plain(), TC18 §13.5 Table 30 -- the caller shall + * respond with error code UNSUPPORTED_CMD). On * RCP_EP_ISELED_OK, *out_transaction_num is populated, and *out_tx_data / * *out_tx_len are set to a *borrowed* view into b (not copied) of the raw * plain content. */ diff --git a/include/rcp/ep_mdio.h b/include/rcp/ep_mdio.h index 6028db2..be8c22b 100644 --- a/include/rcp/ep_mdio.h +++ b/include/rcp/ep_mdio.h @@ -357,6 +357,11 @@ typedef enum { RCP_EP_MDIO_ERR_BAD_ADDR = 5, RCP_EP_MDIO_ERR_BAD_WORD_COUNT = 6, RCP_EP_MDIO_ERR_ALLOC = 7, + /* evt[2:0] is not 0b000, TC18 §13.5 Table 30's only legal value for a + * plain (non-configuration) request in MDIO's endpoint-type row -- + * caller shall respond with error code UNSUPPORTED_CMD (see + * rcp_acf_evt_row2_is_plain()). */ + RCP_EP_MDIO_ERR_BAD_EVT = 8, } rcp_ep_mdio_errc_t; /* Human-readable message for an rcp_ep_mdio_errc_t value. Never returns NULL. */ @@ -382,7 +387,10 @@ rcp_bytes_t rcp_ep_mdio_encode_read_request(rcp_byte_bus_id_t byte_bus_id, * RCP_EP_MDIO_ERR_WRONG_OP if its op is not RCP_ACF_OP_READ; * RCP_EP_MDIO_ERR_BAD_ADDR if the decoded address fails * rcp_ep_mdio_addr_valid(); RCP_EP_MDIO_ERR_BAD_WORD_COUNT if the decoded - * word_count is 0 or exceeds RCP_EP_MDIO_MAX_BURST_WORDS. On + * word_count is 0 or exceeds RCP_EP_MDIO_MAX_BURST_WORDS; + * RCP_EP_MDIO_ERR_BAD_EVT if its evt[2:0] is not 0b000 + * (rcp_acf_evt_row2_is_plain(), TC18 §13.5 Table 30 -- the caller shall + * respond with error code UNSUPPORTED_CMD). On * RCP_EP_MDIO_OK, *out_addr, *out_word_count, and *out_transaction_num * are populated. */ rcp_ep_mdio_errc_t rcp_ep_mdio_decode_read_request(const uint8_t *b, size_t len, @@ -448,7 +456,10 @@ rcp_bytes_t rcp_ep_mdio_encode_write_request(rcp_byte_bus_id_t byte_bus_id, * RCP_EP_MDIO_ERR_BAD_ADDR if the decoded address fails * rcp_ep_mdio_addr_valid(); RCP_EP_MDIO_ERR_BAD_WORD_COUNT if the words * region's own byte length is odd, is 0, or represents more than - * RCP_EP_MDIO_MAX_BURST_WORDS words. On RCP_EP_MDIO_OK, *out_addr and + * RCP_EP_MDIO_MAX_BURST_WORDS words; RCP_EP_MDIO_ERR_BAD_EVT if its + * evt[2:0] is not 0b000 (rcp_acf_evt_row2_is_plain(), TC18 §13.5 + * Table 30 -- the caller shall respond with error code UNSUPPORTED_CMD). + * On RCP_EP_MDIO_OK, *out_addr and * *out_transaction_num are populated, and *out_words_data / * *out_word_count are set to a *borrowed* view into b (not copied) of the * packed word bytes following the address prefix. */ diff --git a/include/rcp/ep_uart.h b/include/rcp/ep_uart.h index 4668b16..5159d94 100644 --- a/include/rcp/ep_uart.h +++ b/include/rcp/ep_uart.h @@ -297,6 +297,11 @@ typedef enum { RCP_EP_UART_ERR_WRONG_OP = 4, RCP_EP_UART_ERR_UNKNOWN_CMD = 5, /* payload-bearing read request -- see the file header */ + /* evt[2:0] is not 0b000, TC18 §13.5 Table 30's only legal value for a + * plain (non-configuration) request in UART's endpoint-type row -- + * caller shall respond with error code UNSUPPORTED_CMD (see + * rcp_acf_evt_row2_is_plain()). */ + RCP_EP_UART_ERR_BAD_EVT = 6, } rcp_ep_uart_errc_t; /* Human-readable message for an rcp_ep_uart_errc_t value. Never returns NULL. */ @@ -319,7 +324,10 @@ rcp_bytes_t rcp_ep_uart_encode_write_request(rcp_byte_bus_id_t byte_bus_id, * fixed header or its declared payload length; RCP_EP_UART_ERR_BAD_MSG_TYPE * if b is not an ACF_ABB message; RCP_EP_UART_ERR_WRONG_BUS if its * byte_bus_id != expected_bus_id; RCP_EP_UART_ERR_WRONG_OP if its op is not - * RCP_ACF_OP_WRITE. On RCP_EP_UART_OK, *out_transaction_num is populated, + * RCP_ACF_OP_WRITE; RCP_EP_UART_ERR_BAD_EVT if its evt[2:0] is not 0b000 + * (rcp_acf_evt_row2_is_plain(), TC18 §13.5 Table 30 -- the caller shall + * respond with error code UNSUPPORTED_CMD). On RCP_EP_UART_OK, + * *out_transaction_num is populated, * and *out_tx_data / *out_tx_len are set to a *borrowed* view into b (not * copied -- see the file header) of the raw outgoing payload. */ rcp_ep_uart_errc_t rcp_ep_uart_decode_write_request(const uint8_t *b, size_t len, @@ -376,7 +384,10 @@ rcp_bytes_t rcp_ep_uart_encode_read_request(rcp_byte_bus_id_t byte_bus_id, uint8 * RCP_EP_UART_ERR_WRONG_BUS if its byte_bus_id != expected_bus_id; * RCP_EP_UART_ERR_WRONG_OP if its op is not RCP_ACF_OP_READ; * RCP_EP_UART_ERR_UNKNOWN_CMD if it carries any payload at all -- the - * deliberate asymmetry documented in the file header. On RCP_EP_UART_OK, + * deliberate asymmetry documented in the file header; RCP_EP_UART_ERR_BAD_EVT + * if its evt[2:0] is not 0b000 (rcp_acf_evt_row2_is_plain(), TC18 §13.5 + * Table 30 -- the caller shall respond with error code UNSUPPORTED_CMD). + * On RCP_EP_UART_OK, * *out_read_size and *out_transaction_num are populated. */ rcp_ep_uart_errc_t rcp_ep_uart_decode_read_request(const uint8_t *b, size_t len, rcp_byte_bus_id_t expected_bus_id, diff --git a/include/rcp/version.h b/include/rcp/version.h index a1cbe27..7eb8d7b 100644 --- a/include/rcp/version.h +++ b/include/rcp/version.h @@ -6,6 +6,6 @@ #ifndef RCP_VERSION_H #define RCP_VERSION_H -#define RCP_VERSION "0.107.0" +#define RCP_VERSION "0.108.0" #endif /* RCP_VERSION_H */ diff --git a/src/ep_i2c.c b/src/ep_i2c.c index 8ae9dc4..086f122 100644 --- a/src/ep_i2c.c +++ b/src/ep_i2c.c @@ -81,6 +81,7 @@ const char *rcp_ep_i2c_strerror(rcp_ep_i2c_errc_t e) case RCP_EP_I2C_ERR_BAD_MSG_TYPE: return "rcp/ep_i2c: unexpected ACF message type"; case RCP_EP_I2C_ERR_WRONG_BUS: return "rcp/ep_i2c: wrong byte_bus_id"; case RCP_EP_I2C_ERR_WRONG_OP: return "rcp/ep_i2c: wrong ACF op"; + case RCP_EP_I2C_ERR_BAD_EVT: return "rcp/ep_i2c: evt[2:0] is not 0b000"; default: return "rcp/ep_i2c: unknown error"; } } @@ -136,6 +137,7 @@ rcp_ep_i2c_errc_t rcp_ep_i2c_decode_transfer_request(const uint8_t *b, size_t le if (acf_rc != RCP_ACF_OK) return RCP_EP_I2C_ERR_BAD_MSG_TYPE; if (hdr.byte_bus_id != expected_bus_id) return RCP_EP_I2C_ERR_WRONG_BUS; + if (!rcp_acf_evt_row2_is_plain(hdr.evt)) return RCP_EP_I2C_ERR_BAD_EVT; /* Both op senses are valid on an I2C transfer request -- see the file * header. The direction is reported, not rejected. */ diff --git a/src/ep_iseled.c b/src/ep_iseled.c index 4b473fe..905f1f4 100644 --- a/src/ep_iseled.c +++ b/src/ep_iseled.c @@ -200,6 +200,7 @@ const char *rcp_ep_iseled_strerror(rcp_ep_iseled_errc_t e) case RCP_EP_ISELED_ERR_ODD_SYMBOL_COUNT: return "rcp/ep_iseled: odd symbol count -- every octet frames to two symbols"; case RCP_EP_ISELED_ERR_ALLOC: return "rcp/ep_iseled: allocation failure"; + case RCP_EP_ISELED_ERR_BAD_EVT: return "rcp/ep_iseled: evt[2:0] is not 0b000"; default: return "rcp/ep_iseled: unknown error"; } } @@ -296,6 +297,7 @@ rcp_ep_iseled_errc_t rcp_ep_iseled_decode_command_request(const uint8_t *b, size if (hdr.byte_bus_id != expected_bus_id) return RCP_EP_ISELED_ERR_WRONG_BUS; if (hdr.op != RCP_ACF_OP_WRITE) return RCP_EP_ISELED_ERR_WRONG_OP; + if (!rcp_acf_evt_row2_is_plain(hdr.evt)) return RCP_EP_ISELED_ERR_BAD_EVT; /* payload is round-tripped verbatim, byte for byte -- see the file * header; this endpoint's own bit-framing happens only when it diff --git a/src/ep_mdio.c b/src/ep_mdio.c index fb7be0e..dbb1de6 100644 --- a/src/ep_mdio.c +++ b/src/ep_mdio.c @@ -134,6 +134,7 @@ const char *rcp_ep_mdio_strerror(rcp_ep_mdio_errc_t e) case RCP_EP_MDIO_ERR_BAD_ADDR: return "rcp/ep_mdio: invalid MDIO address"; case RCP_EP_MDIO_ERR_BAD_WORD_COUNT: return "rcp/ep_mdio: invalid register-word count"; case RCP_EP_MDIO_ERR_ALLOC: return "rcp/ep_mdio: allocation failure"; + case RCP_EP_MDIO_ERR_BAD_EVT: return "rcp/ep_mdio: evt[2:0] is not 0b000"; default: return "rcp/ep_mdio: unknown error"; } } @@ -204,6 +205,7 @@ rcp_ep_mdio_errc_t rcp_ep_mdio_decode_read_request(const uint8_t *b, size_t len, if (hdr.byte_bus_id != expected_bus_id) return RCP_EP_MDIO_ERR_WRONG_BUS; if (hdr.op != RCP_ACF_OP_READ) return RCP_EP_MDIO_ERR_WRONG_OP; + if (!rcp_acf_evt_row2_is_plain(hdr.evt)) return RCP_EP_MDIO_ERR_BAD_EVT; if (payload_len < READ_REQUEST_PAYLOAD_LEN) return RCP_EP_MDIO_ERR_SHORT_FRAME; addr = get_addr_prefix(payload); @@ -378,6 +380,7 @@ rcp_ep_mdio_errc_t rcp_ep_mdio_decode_write_request(const uint8_t *b, size_t len if (hdr.byte_bus_id != expected_bus_id) return RCP_EP_MDIO_ERR_WRONG_BUS; if (hdr.op != RCP_ACF_OP_WRITE) return RCP_EP_MDIO_ERR_WRONG_OP; + if (!rcp_acf_evt_row2_is_plain(hdr.evt)) return RCP_EP_MDIO_ERR_BAD_EVT; if (payload_len < ADDR_PREFIX_LEN) return RCP_EP_MDIO_ERR_SHORT_FRAME; addr = get_addr_prefix(payload); diff --git a/src/ep_uart.c b/src/ep_uart.c index 33a44cf..9b255b4 100644 --- a/src/ep_uart.c +++ b/src/ep_uart.c @@ -117,6 +117,7 @@ const char *rcp_ep_uart_strerror(rcp_ep_uart_errc_t e) case RCP_EP_UART_ERR_WRONG_BUS: return "rcp/ep_uart: wrong byte_bus_id"; case RCP_EP_UART_ERR_WRONG_OP: return "rcp/ep_uart: wrong ACF op"; case RCP_EP_UART_ERR_UNKNOWN_CMD: return "rcp/ep_uart: unrecognized command (payload-bearing read request)"; + case RCP_EP_UART_ERR_BAD_EVT: return "rcp/ep_uart: evt[2:0] is not 0b000"; default: return "rcp/ep_uart: unknown error"; } } @@ -157,6 +158,7 @@ rcp_ep_uart_errc_t rcp_ep_uart_decode_write_request(const uint8_t *b, size_t len if (hdr.byte_bus_id != expected_bus_id) return RCP_EP_UART_ERR_WRONG_BUS; if (hdr.op != RCP_ACF_OP_WRITE) return RCP_EP_UART_ERR_WRONG_OP; + if (!rcp_acf_evt_row2_is_plain(hdr.evt)) return RCP_EP_UART_ERR_BAD_EVT; *out_tx_data = payload; *out_tx_len = payload_len; @@ -281,6 +283,7 @@ rcp_ep_uart_errc_t rcp_ep_uart_decode_read_request(const uint8_t *b, size_t len, if (hdr.byte_bus_id != expected_bus_id) return RCP_EP_UART_ERR_WRONG_BUS; if (hdr.op != RCP_ACF_OP_READ) return RCP_EP_UART_ERR_WRONG_OP; + if (!rcp_acf_evt_row2_is_plain(hdr.evt)) return RCP_EP_UART_ERR_BAD_EVT; /* A UART read request has nothing meaningful a payload could carry * (read_size already rides the ACF header itself) -- a payload-bearing diff --git a/tests/test_ep_i2c.c b/tests/test_ep_i2c.c index 41a060f..28896b7 100644 --- a/tests/test_ep_i2c.c +++ b/tests/test_ep_i2c.c @@ -375,6 +375,31 @@ static void test_transfer_request_rejects_wrong_bus(void) rcp_bytes_free(&frame); } +/* TC18 §13.5 Table 30: evt[2:0] = 000b is the only legal value for a + * plain I2C transfer request; every other value (here, 0b011, a reserved + * value in I2C's endpoint-type row) shall be rejected. */ +static void test_transfer_request_rejects_nonzero_evt(void) +{ + rcp_acf_byte_message_info_t hdr = {0}; + rcp_bytes_t frame; + rcp_ep_i2c_dir_t dir; + const uint8_t *out_tx; + size_t out_tx_len; + uint16_t read_size; + uint8_t txn; + + hdr.byte_bus_id = 4; + hdr.op = RCP_ACF_OP_WRITE; + hdr.evt = 0x3; + frame = rcp_acf_encode_abb(&hdr, NULL, 0); + + TEST_ASSERT_EQUAL(RCP_EP_I2C_ERR_BAD_EVT, + rcp_ep_i2c_decode_transfer_request(frame.data, frame.len, 4, &dir, &out_tx, &out_tx_len, + &read_size, &txn)); + + rcp_bytes_free(&frame); +} + /* The former test_transfer_request_rejects_wrong_op() asserted that a * read-direction (op=0) frame is not an I2C transfer request. It is one -- * see the direction tests above -- so its replacement asserts the @@ -594,6 +619,7 @@ int main(void) RUN_TEST(test_transfer_request_round_trip_read_direction); RUN_TEST(test_transfer_request_round_trip_empty_payload); RUN_TEST(test_transfer_request_rejects_wrong_bus); + RUN_TEST(test_transfer_request_rejects_nonzero_evt); RUN_TEST(test_transfer_request_accepts_hand_built_read_direction_frame); RUN_TEST(test_transfer_request_rejects_bad_msg_type); RUN_TEST(test_transfer_request_rejects_short_frame); diff --git a/tests/test_ep_iseled.c b/tests/test_ep_iseled.c index d710342..e6c7b69 100644 --- a/tests/test_ep_iseled.c +++ b/tests/test_ep_iseled.c @@ -489,6 +489,29 @@ static void test_command_request_rejects_wrong_op(void) rcp_bytes_free(&frame); } +/* TC18 §13.5 Table 30: evt[2:0] = 000b is the only legal value for a + * plain ISELED command request; every other value (here, 0b010, a + * reserved value in ISELED's endpoint-type row) shall be rejected. */ +static void test_command_request_rejects_nonzero_evt(void) +{ + rcp_acf_byte_message_info_t hdr = {0}; + rcp_bytes_t frame; + const uint8_t *out_tx; + size_t out_tx_len; + uint8_t txn; + + hdr.byte_bus_id = 4; + hdr.op = RCP_ACF_OP_WRITE; + hdr.evt = 0x2; + frame = rcp_acf_encode_abb(&hdr, NULL, 0); + + TEST_ASSERT_EQUAL(RCP_EP_ISELED_ERR_BAD_EVT, + rcp_ep_iseled_decode_command_request(frame.data, frame.len, 4, &out_tx, &out_tx_len, + &txn)); + + rcp_bytes_free(&frame); +} + static void test_command_request_rejects_bad_msg_type(void) { rcp_acf_gbb_header_t gbb_hdr = {0}; @@ -645,6 +668,7 @@ int main(void) RUN_TEST(test_command_request_round_trip_empty_payload); RUN_TEST(test_command_request_rejects_wrong_bus); RUN_TEST(test_command_request_rejects_wrong_op); + RUN_TEST(test_command_request_rejects_nonzero_evt); RUN_TEST(test_command_request_rejects_bad_msg_type); RUN_TEST(test_command_request_rejects_short_frame); diff --git a/tests/test_ep_mdio.c b/tests/test_ep_mdio.c index d4913af..4d27193 100644 --- a/tests/test_ep_mdio.c +++ b/tests/test_ep_mdio.c @@ -370,6 +370,28 @@ static void test_read_request_decode_rejects_wrong_op(void) rcp_bytes_free(&frame); } +/* TC18 §13.5 Table 30: evt[2:0] = 000b is the only legal value for a + * plain MDIO read request; every other value (here, 0b100, reserved in + * MDIO's endpoint-type row) shall be rejected. */ +static void test_read_request_decode_rejects_nonzero_evt(void) +{ + rcp_acf_byte_message_info_t hdr = {0}; + rcp_bytes_t frame; + rcp_ep_mdio_addr_t out_addr; + size_t out_word_count; + uint8_t txn; + + hdr.byte_bus_id = 2; + hdr.op = RCP_ACF_OP_READ; + hdr.evt = 0x4; + frame = rcp_acf_encode_abb(&hdr, NULL, 0); + + TEST_ASSERT_EQUAL(RCP_EP_MDIO_ERR_BAD_EVT, rcp_ep_mdio_decode_read_request( + frame.data, frame.len, 2, &out_addr, &out_word_count, &txn)); + + rcp_bytes_free(&frame); +} + static void test_read_request_decode_rejects_bad_msg_type(void) { rcp_acf_gbb_header_t gbb_hdr = {0}; @@ -674,6 +696,29 @@ static void test_write_request_decode_rejects_wrong_op(void) rcp_bytes_free(&frame); } +/* TC18 §13.5 Table 30: evt[2:0] = 000b is the only legal value for a + * plain MDIO write request; every other value (here, 0b001, reserved in + * MDIO's endpoint-type row) shall be rejected. */ +static void test_write_request_decode_rejects_nonzero_evt(void) +{ + rcp_acf_byte_message_info_t hdr = {0}; + rcp_bytes_t frame; + rcp_ep_mdio_addr_t out_addr; + const uint8_t *out_words_data; + size_t out_word_count; + uint8_t txn; + + hdr.byte_bus_id = 2; + hdr.op = RCP_ACF_OP_WRITE; + hdr.evt = 0x1; + frame = rcp_acf_encode_abb(&hdr, NULL, 0); + + TEST_ASSERT_EQUAL(RCP_EP_MDIO_ERR_BAD_EVT, rcp_ep_mdio_decode_write_request( + frame.data, frame.len, 2, &out_addr, &out_words_data, &out_word_count, &txn)); + + rcp_bytes_free(&frame); +} + static void test_write_request_decode_rejects_short_frame(void) { rcp_acf_byte_message_info_t hdr = {0}; @@ -867,6 +912,7 @@ int main(void) RUN_TEST(test_read_request_encode_rejects_word_count_above_max); RUN_TEST(test_read_request_decode_rejects_wrong_bus); RUN_TEST(test_read_request_decode_rejects_wrong_op); + RUN_TEST(test_read_request_decode_rejects_nonzero_evt); RUN_TEST(test_read_request_decode_rejects_bad_msg_type); RUN_TEST(test_read_request_decode_rejects_short_frame); RUN_TEST(test_read_request_decode_rejects_bad_addr); @@ -886,6 +932,7 @@ int main(void) RUN_TEST(test_write_request_encode_rejects_word_count_above_max); RUN_TEST(test_write_request_decode_rejects_wrong_bus); RUN_TEST(test_write_request_decode_rejects_wrong_op); + RUN_TEST(test_write_request_decode_rejects_nonzero_evt); RUN_TEST(test_write_request_decode_rejects_short_frame); RUN_TEST(test_write_request_decode_rejects_bad_addr); RUN_TEST(test_write_request_decode_rejects_zero_words); diff --git a/tests/test_ep_uart.c b/tests/test_ep_uart.c index 49db630..adff483 100644 --- a/tests/test_ep_uart.c +++ b/tests/test_ep_uart.c @@ -349,6 +349,28 @@ static void test_write_request_rejects_wrong_bus_op_short_frame_bad_type(void) &txn)); } +/* TC18 §13.5 Table 30: evt[2:0] = 000b is the only legal value for a + * plain UART write request; every other value (here, 0b110, a reserved + * value in UART's endpoint-type row) shall be rejected. */ +static void test_write_request_rejects_nonzero_evt(void) +{ + rcp_acf_byte_message_info_t hdr = {0}; + rcp_bytes_t frame; + const uint8_t *out_tx; + size_t out_tx_len; + uint8_t txn; + + hdr.byte_bus_id = 4; + hdr.op = RCP_ACF_OP_WRITE; + hdr.evt = 0x6; + frame = rcp_acf_encode_abb(&hdr, NULL, 0); + + TEST_ASSERT_EQUAL(RCP_EP_UART_ERR_BAD_EVT, + rcp_ep_uart_decode_write_request(frame.data, frame.len, 4, &out_tx, &out_tx_len, &txn)); + + rcp_bytes_free(&frame); +} + static void test_write_response_round_trip_untimed_and_timed(void) { uint8_t accepted[2] = {0x55, 0x66}; @@ -462,6 +484,27 @@ static void test_read_request_rejects_wrong_bus_op_short_frame(void) rcp_ep_uart_decode_read_request(too_short, sizeof(too_short), 6, &read_size, &txn)); } +/* TC18 §13.5 Table 30: evt[2:0] = 000b is the only legal value for a + * plain UART read request; every other value (here, 0b101, a reserved + * value in UART's endpoint-type row) shall be rejected. */ +static void test_read_request_rejects_nonzero_evt(void) +{ + rcp_acf_byte_message_info_t hdr = {0}; + rcp_bytes_t frame; + uint8_t read_size; + uint8_t txn; + + hdr.byte_bus_id = 6; + hdr.op = RCP_ACF_OP_READ; + hdr.evt = 0x5; + frame = rcp_acf_encode_abb(&hdr, NULL, 0); + + TEST_ASSERT_EQUAL(RCP_EP_UART_ERR_BAD_EVT, + rcp_ep_uart_decode_read_request(frame.data, frame.len, 6, &read_size, &txn)); + + rcp_bytes_free(&frame); +} + static void test_read_response_round_trip_full_length(void) { uint8_t rx[4] = {0xDE, 0xAD, 0xBE, 0xEF}; @@ -675,12 +718,14 @@ int main(void) RUN_TEST(test_write_request_round_trip); RUN_TEST(test_write_request_rejects_wrong_bus_op_short_frame_bad_type); + RUN_TEST(test_write_request_rejects_nonzero_evt); RUN_TEST(test_write_response_round_trip_untimed_and_timed); RUN_TEST(test_write_response_decode_rejects_wrong_bus_and_short_frame); RUN_TEST(test_read_request_round_trip); RUN_TEST(test_read_request_rejects_payload_with_unknown_cmd); RUN_TEST(test_read_request_rejects_wrong_bus_op_short_frame); + RUN_TEST(test_read_request_rejects_nonzero_evt); RUN_TEST(test_read_response_round_trip_full_length); RUN_TEST(test_read_response_round_trip_short_read_single_avtpdu); RUN_TEST(test_read_response_decode_rejects_wrong_bus_and_short_frame);