Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
},
{
"id": "REQ-MOCK-001",
"title": "Server starts HW_UNCONFIGURED with a representative six-endpoint register map",
"text": "rcp::mock::Server shall construct with rcp::lifecycle::ServerLifecycle in state HwUnconfigured and an rcp::regmap::RegisterMap whose endpoint_count, generic_configs, functional_configs, and ep_id_mapping describe exactly six endpoints: GPIO at endpoint id/byte_bus_id 1, SPI at endpoint id/byte_bus_id 2, I2C at endpoint id/byte_bus_id 3, ADC at endpoint id/byte_bus_id 4, PWM_IN at endpoint id/byte_bus_id 5, and LIN at endpoint id/byte_bus_id 6.",
"title": "Server starts HW_UNCONFIGURED with a representative seven-endpoint register map",
"text": "rcp::mock::Server shall construct with rcp::lifecycle::ServerLifecycle in state HwUnconfigured and an rcp::regmap::RegisterMap whose endpoint_count, generic_configs, functional_configs, and ep_id_mapping describe exactly seven endpoints: GPIO at endpoint id/byte_bus_id 1, SPI at endpoint id/byte_bus_id 2, I2C at endpoint id/byte_bus_id 3, ADC at endpoint id/byte_bus_id 4, PWM_IN at endpoint id/byte_bus_id 5, LIN at endpoint id/byte_bus_id 6, and CAN at endpoint id/byte_bus_id 7.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
Expand Down Expand Up @@ -232,6 +232,24 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-019",
"title": "CAN dispatch applies Table 33 Row 2 evt[2:0] classification before touching endpoint state",
"text": "rcp::mock::Server::dispatch, for a request addressed to the CAN endpoint while RcpConfigured, shall decode the request's evt[2:0] field via endpoint::evt_row2_kind_of and drive can::CanEndpoint::handle_request with it: a Plain (000b) request shall transmit a can::CanDataFrame built from the request payload and be answered as a WriteResponse with an empty byte_msg_payload, a Reserved (001b-110b) evt shall be rejected with endpoint::EndpointErrc::reserved_evt_row2 (wire error code UNSUPPORTED_CMD) without recording anything as transmitted, and a ConfigWrite (111b) evt shall be rejected with can::CanErrc::config_write_not_supported (wire error code UNSUPPORTED_CMD) rather than crashing or being treated as a plain transmit.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B",
"tc18": "§13.5 Table 33 (\"ADC, PWM_IN, I²C, LIN, CAN, UART, ISELED, MDIO: 000b to 110b reserved – request to be rejected with error code = UNSUPPORTED_CMD\" / \"111b: The byte_msg_payload is not presented to the interface but used to change the configuration of the endpoint (see 12.7.1)\"), TC18.txt L4085-4092; corroborating worked example resolving the evt=000b ambiguity in Table 33's own favor: §13.7.9.3 Figure 34 (\"RC Client sends a standard read request\", ADC endpoint, evt field all-zero), TC18.txt L5590-5607",
"tc18_master_id": "TC18-13.5-001"
},
{
"id": "REQ-MOCK-020",
"title": "Operational CAN requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC/PWM_IN/LIN",
"text": "rcp::mock::Server::dispatch, for a request addressed to the CAN endpoint while the server's lifecycle state is not RcpConfigured, shall return regmap::RegMapErrc::request_rejected and an ErrorResponse without touching the endpoint's live state, the same operational-request gating REQ-MOCK-006/REQ-MOCK-012/REQ-MOCK-014/REQ-MOCK-016/REQ-MOCK-018 already apply to GPIO/SPI/I2C/ADC/PWM_IN/LIN.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-UDP-001",
"title": "Frame round-trips an NTSCF-framed ACF_ABB request",
Expand Down Expand Up @@ -3264,6 +3282,27 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-CANEP-008",
"title": "CanEndpoint::handle_request applies Table 33 Row 2 evt[2:0] classification before transmitting",
"text": "rcp::can::CanEndpoint::handle_request shall classify its evt_op argument via rcp::endpoint::evt_row2_kind_of (masking to the low 3 bits first) and, for EvtRow2Kind::Plain, shall delegate to transmit() with frame unchanged; for EvtRow2Kind::Reserved, shall return endpoint::EndpointErrc::reserved_evt_row2 without recording anything as transmitted (last_transmitted() left untouched).",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B",
"tc18": "§13.5 Table 33 (\"ADC, PWM_IN, I²C, LIN, CAN, UART, ISELED, MDIO: 000b to 110b reserved – request to be rejected with error code = UNSUPPORTED_CMD\"), TC18.txt L4085-4092; corroborating worked example resolving the evt=000b ambiguity in Table 33's own favor: §13.7.9.3 Figure 34 (\"RC Client sends a standard read request\", ADC endpoint, evt field all-zero), TC18.txt L5590-5607",
"tc18_master_id": "TC18-13.5-001",
"severity": "cybersecurity"
},
{
"id": "REQ-CANEP-009",
"title": "CanEndpoint::handle_request reports config-write (evt[2:0]==111b) as not yet supported",
"text": "rcp::can::CanEndpoint::handle_request, for EvtRow2Kind::ConfigWrite (evt[2:0]==111b), shall return CanErrc::config_write_not_supported without recording anything as transmitted and without crashing, rather than silently accepting the request as an ordinary transmit or silently doing nothing; full §12.7.1 configuration-write handling is deliberately deferred to a follow-up milestone, the same scope decision I2C's I2cErrc::config_write_not_supported, ADC's AdcErrc::config_write_not_supported, PWM_IN's PwmErrc::config_write_not_supported, and LIN's LinErrc::config_write_not_supported already made. CanErrc::config_write_not_supported shall report a non-empty diagnostic message in its own error_category.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B",
"tc18": "§12.7.1 (\"This can be done by setting evt[2:0] = 111b in request as described above.\"; Figure 19 – \"Configuration request\"; \"The data of the byte_msg_payload from a write request is written into the section of the EP that is addressed by the byte_bus_id.\"), TC18.txt L2844-2865",
"tc18_master_id": "TC18-12.7.1-001"
},
{
"id": "REQ-ISELED-001",
"title": "ISELED's ep_type id is 0x0C",
Expand Down
93 changes: 92 additions & 1 deletion include/rcp/can.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// fusa:req REQ-CANEP-005
// fusa:req REQ-CANEP-006
// fusa:req REQ-CANEP-007
// fusa:req REQ-CANEP-008
// fusa:req REQ-CANEP-009

// CAN controller endpoint (ep_type 0x0B) — the OPEN Alliance TC18 Remote
// Control Protocol Specification v0.5.1_RC's Classical/FD/XL frame-format
Expand Down Expand Up @@ -46,15 +48,31 @@
// should first confirm the extraction actually defines one for this
// endpoint type, not assume it was merely forgotten here.
//
// Table 30/33 Row 2 evt[2:0] validation (post-v2.7.0, fifth endpoint type
// after I2C, ADC, PWM_IN, and LIN): CanEndpoint::handle_request is this
// header's own wiring of rcp::endpoint::evt_row2_kind_of — the shared
// 3-way evt[2:0] classifier for Table 33's {ADC, PWM_IN, I2C, LIN, CAN,
// UART, ISELED, MDIO} row — into CAN's request decode, following the exact
// shape rcp/i2c.hpp's I2cEndpoint::handle_request, rcp/adc.hpp's
// AdcEndpoint::handle_request, rcp/pwm.hpp's PwmInEndpoint::handle_request,
// and rcp/lin.hpp's LinEndpoint::handle_request established. See
// handle_request's own doc comment for why this is a completely separate
// concern from Figure 40's FrameFormat sub-field and from point 1 above's
// "no remote-frame concept" — evt[2:0] classification must not be confused
// with either.
//
// Field names and behavior below implement TC18's *behavior* as described in
// an internal structured extraction of the specification named above; no
// text from that document is reproduced here. The concrete bit-timing
// register field layout, the accepted single-AVTPDU XL payload bound, and
// the acceptance-filter matching rule chosen in this file are this
// implementation's own, same as the equivalent disclaimers in rcp/avtp.hpp,
// rcp/regmap.hpp, rcp/endpoint.hpp, and rcp/uart.hpp.
// rcp/regmap.hpp, rcp/endpoint.hpp, rcp/uart.hpp, rcp/i2c.hpp, rcp/adc.hpp,
// rcp/pwm.hpp, and rcp/lin.hpp.
#pragma once

#include <rcp/endpoint.hpp>

#include <algorithm>
#include <cstddef>
#include <cstdint>
Expand Down Expand Up @@ -118,6 +136,14 @@ enum class CanErrc : int {
payload_exceeds_format_limit = 2, // payload length exceeds the selected FrameFormat's own max
xl_payload_exceeds_single_avtpdu_bound = 3, // within the spec's 2054-byte ceiling, but beyond this
// implementation's accepted single-AVTPDU bound (see header comment)
// evt_row2_kind_of classified the request as ConfigWrite (evt[2:0] ==
// 111b, §12.7.1). This milestone's follow-up deliberately does not
// implement the configuration-write shape (relative EP_functional-
// config start address + configuration data) — see handle_request's
// own comment. Reported explicitly rather than silently accepted as a
// plain transmit or silently ignored, same as I2C's, ADC's, PWM_IN's,
// and LIN's own config_write_not_supported variants.
config_write_not_supported = 4,
};

inline const std::error_category& can_category() noexcept {
Expand All @@ -131,6 +157,8 @@ inline const std::error_category& can_category() noexcept {
return "rcp/can: payload exceeds the selected FrameFormat's payload limit";
case CanErrc::xl_payload_exceeds_single_avtpdu_bound:
return "rcp/can: CAN XL payload requires multi-AVTPDU fragmentation, not supported this cycle";
case CanErrc::config_write_not_supported:
return "rcp/can: evt[2:0]=111b configuration-write requests are not yet implemented";
default:
return "rcp/can: unknown error";
}
Expand Down Expand Up @@ -255,6 +283,69 @@ class CanEndpoint {
return {};
}

// handle_request is CAN's request-decode entry point — the piece this
// header previously had none of, mirroring rcp::i2c::I2cEndpoint::
// handle_request's shape (this repo's fifth Table 33 Row 2 endpoint
// type after I2C, ADC, PWM_IN, and LIN). It classifies the incoming
// request's evt[2:0] field via rcp::endpoint::evt_row2_kind_of before
// doing anything else, so a Reserved value can never reach transmit()
// and be misread as an ordinary transmit request, and a ConfigWrite
// value can never be silently accepted or silently dropped:
// - Plain (evt[2:0] == 000b): delegates straight to transmit() with
// `frame` unchanged — CAN's existing data-frame transmit model
// (extraction §13.7.11.3) already IS this row's correct "plain
// request" behavior; evt[2:0] carries no combinable value or
// channel selector for this row.
// - Reserved (evt[2:0] in 001b-110b): returns
// endpoint::EndpointErrc::reserved_evt_row2 without touching any
// endpoint state (last_tx_ is left exactly as it was — `frame` is
// simply discarded) — TC18 requires this be rejected with error
// code UNSUPPORTED_CMD.
// - ConfigWrite (evt[2:0] == 111b): §12.7.1's configuration-write
// shape targets the CAN EP's own functional-config block (Table 56
// — bit-timing registers, acceptance/receive filters, clock
// divider, ...), not a frame transmission at all. Full handling is
// deliberately out of scope for this milestone (nontrivial — it
// needs EP_functional-config wiring this header does not yet have,
// the same gap I2C's, ADC's, PWM_IN's, and LIN's own handle_request
// comments defer for the identical reason); this returns
// CanErrc::config_write_not_supported rather than crashing,
// silently accepting the request as a transmit, or silently doing
// nothing.
//
// NOT to be confused with Figure 40's own "FrameFormat" sub-field
// (CBFF/CEFF/FBFF/FEFF/XL-classic/XL-new, Table 57), carried inside the
// request's byte_msg_payload alongside the CAN ID and CAN data: that
// field selects the CAN frame's own on-wire format and lives entirely
// in the payload, verified directly against TC18.txt's own Figure 40
// layout (§13.7.11.3), where the Message Info octet's evt[2:0] bits and
// the Payload's FrameFormat/CAN-ID/CAN-data fields are drawn as
// distinct, non-overlapping regions of the request. Reading evt[2:0] as
// if it also selected (or combined with) FrameFormat, or as if it
// selected a remote-frame vs data-frame request — this endpoint type
// has no remote-frame concept at all (see header comment point 1;
// TC18's own text states outright "Sending remote frames is not
// supported") — would be exactly the kind of invented, non-spec-
// derived field encoding this codebase has had to remove elsewhere once
// discovered (e.g. rcp/iseled.hpp's and rcp/mdio.hpp's own header
// comments on previously invented, non-spec-derived field encodings
// later corrected, and the identical class of mistake rcp/lin.hpp's own
// handle_request comment calls out against confusing evt[2:0] with
// §13.7.10.1's separate compound-wait match-condition text).
// handle_request below calls the same shared evt_row2_kind_of every
// other Row 2 endpoint type uses and invents nothing of its own.
std::error_code handle_request(uint8_t evt_op, CanDataFrame frame) {
switch (endpoint::evt_row2_kind_of(evt_op)) {
case endpoint::EvtRow2Kind::Plain:
return transmit(std::move(frame));
case endpoint::EvtRow2Kind::Reserved:
return endpoint::make_error_code(endpoint::EndpointErrc::reserved_evt_row2);
case endpoint::EvtRow2Kind::ConfigWrite:
return make_error_code(CanErrc::config_write_not_supported);
}
return endpoint::make_error_code(endpoint::EndpointErrc::reserved_evt_row2); // unreachable
}

// receive models one inbound data frame arriving off the bus. Returns
// false (and does not record the frame) when the frame's format-scoped
// filter bank is non-empty and none of its entries match — true
Expand Down
Loading
Loading