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
40 changes: 20 additions & 20 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@
{
"id": "REQ-PWR-002",
"title": "Ordinary power-mode transition shape",
"text": "is_power_mode_transition_defined allows only Normal<->StandBy and StandBy<->Sleep; the direct Normal<->Sleep hop, any pair naming Unpowered, and staying in the same mode are all undefined",
"text": "is_power_mode_transition_defined allows exactly the two ordinary (non-start-up) edges of TC18 v0.5.1_RC §12.4 Figure 17 'power and operation modes' (p.46): Normal->StandBy ('Go to StandBy') and Normal->Sleep ('Go to Sleep'). Every other ordered pair is undefined, including StandBy<->Sleep in both directions (Figure 17 draws no edge between the two low-power modes; each is reached from and returns to Normal only), the two wake-up edges back to Normal (start-up paths owned by try_hot_start/try_cold_start), any pair naming Unpowered, and staying in the same mode",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
Expand Down Expand Up @@ -363,17 +363,17 @@
},
{
"id": "REQ-PWRSTART-001",
"title": "try_cold_start requires Unpowered origin and gate",
"text": "try_cold_start returns Ok(PowerMode::Normal) only when from == PowerMode::Unpowered and gate satisfies is_power_mode_gate_satisfied; Err(RcpError::RequestRejected) otherwise, including from any powered mode or an ungated Unpowered origin",
"title": "try_cold_start admits both TC18 cold-start origins (Unpowered and Sleep)",
"text": "TC18 v0.5.1_RC §12.4.1 'Power-On / Wake-Up / Start-Up behavior' (p.46) defines the cold start with two origins — 'a cold start (after power-on or wake-up from sleep)' — and §12.4 Figure 17 labels both the Unpowered->Normal and the Sleep->Normal arrow 'Cold start'. try_cold_start therefore returns Ok(PowerMode::Normal) when from is PowerMode::Unpowered or PowerMode::Sleep and gate satisfies is_power_mode_gate_satisfied, and Err(RcpError::RequestRejected) otherwiseincluding from PowerMode::Normal, from PowerMode::StandBy (whose resume is the hot start, REQ-PWRSTART-002), and from a valid origin whose gate is unsatisfied. No WakeUp handshake gates this path: §12.4.1 attaches the handshake to the 'Hot-start-up procedure' only",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
"verificationMethod": "test"
},
{
"id": "REQ-PWRSTART-002",
"title": "try_hot_start requires Sleep origin, acknowledged handshake, and gate",
"text": "try_hot_start returns Ok(PowerMode::Normal) only when from == PowerMode::Sleep, wakeup == WakeUpHandshakeState::Acknowledged, and gate satisfies is_power_mode_gate_satisfied; Err(RcpError::RequestRejected) otherwise",
"title": "try_hot_start requires StandBy origin, acknowledged handshake, and gate",
"text": "TC18 v0.5.1_RC §12.4.1 (p.46) defines the hot start with exactly one origin — 'a hot start (=wake-up from StandBy)' — drawn in §12.4 Figure 17 as the single arrow labelled 'Hot start', and it is §12.4.1's 'Hot-start-up procedure' that specifies the repeated WakeUp message awaiting 'a valid AVTPDU from the sleep request Client', which WakeUpHandshakeState models. try_hot_start therefore returns Ok(PowerMode::Normal) only when from == PowerMode::StandBy, wakeup == WakeUpHandshakeState::Acknowledged, and gate satisfies is_power_mode_gate_satisfied; Err(RcpError::RequestRejected) otherwise, including from PowerMode::Sleep even with an acknowledged handshake (that origin is a cold start, REQ-PWRSTART-001)",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
Expand Down Expand Up @@ -2694,44 +2694,44 @@
},
{
"id": "REQ-RMAP-015",
"title": "RequestStreamConfigEntry models the §3.8 per-stream config row",
"text": "RequestStreamConfigEntry carries rx_stream_id and the remaining fifteen per-stream fields this crate's own §3.8 extraction names, and reports CATEGORY == lifecycle::RegisterCategory::RcpConfig",
"title": "RequestStreamConfigEntry models TC18 Table 22's per-stream config row",
"text": "RequestStreamConfigEntry carries the sixteen fields TC18 v0.5.1_RC §12.7.7 Table 22 'Request stream configuration' (pp.57-58) tabulates — rx_stream_id (0x0000, 64 bit), rx_stream_max_request_size (0x0008, 16 bit), rx_wd_timeout_interval (0x000A, 16 bit), rx_secure_channel_index (0x000C, 8 bit), the eight 1-bit flags rx_enforce_e2e through rx_wd_info_enable sharing the bit-addressed byte 0x000D, rx_safestate_sequencer (0x000E), rx_safe_sequencer_state (0x000F), rx_ack_stream_index (0x0010), and rx_resp_stream_index (0x0011) — with the eight flags typed bool to match their 1-bit width, and reports CATEGORY == lifecycle::RegisterCategory::RcpConfig",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
"verificationMethod": "test"
},
{
"id": "REQ-RMAP-016",
"title": "RequestStreamConfigEntry encode/decode round-trip",
"text": "RequestStreamConfigEntry::encode/decode round-trip every field exactly (including all-default and all-max-value instances) via a fixed-length big-endian wire form, and decode ignores any trailing bytes beyond ENCODED_LEN",
"title": "RequestStreamConfigEntry encodes TC18 Table 22's 24-byte row layout",
"text": "RequestStreamConfigEntry::ENCODED_LEN is 24, the row stride fixed by Table 22's own tabulation of the next row's rx_stream_id2 at relative address 0x0018. encode places every field at its tabulated relative address, packs the eight 1-bit flags into the single byte at FLAGS_OFFSET == 0x000D with rx_enforce_e2e at bit .0 through rx_wd_info_enable at bit .7 in the order Table 22 lists them, and writes the reserved 16-bit block at 0x0012 and reserved 32-bit block at 0x0014 as zero. encode/decode round-trip every specified field exactly (including all-default and all-flags-set instances) and decode ignores any trailing bytes beyond ENCODED_LEN",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
"verificationMethod": "test"
},
{
"id": "REQ-RMAP-017",
"title": "RequestStreamConfigEntry decode rejects short input and never panics",
"text": "RequestStreamConfigEntry::decode returns Err(RcpError::ShortFrame) for every input shorter than ENCODED_LEN and never panics for any input length",
"title": "RequestStreamConfigEntry decode rejects short input, ignores reserved bytes, and never panics",
"text": "RequestStreamConfigEntry::decode returns Err(RcpError::ShortFrame) for every input shorter than ENCODED_LEN, ignores the content of TC18 Table 22's reserved bytes 0x0012-0x0017 entirely (so any reserved content decodes to the same value), and never panics for any input length",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
"verificationMethod": "test"
},
{
"id": "REQ-RMAP-018",
"title": "EpByteBusIdMapEntry models the §3.9 stream/byte_bus_id-to-endpoint row and its end-of-table sentinel",
"text": "EpByteBusIdMapEntry carries map_stream_index, map_byte_bus_id, and map_ep_nr, reports CATEGORY == lifecycle::RegisterCategory::RcpConfig, and is_end_of_table returns true iff map_stream_index == END_OF_TABLE_STREAM_INDEX (0); this module defines no ascending-order validation or enforcement over a table's rows",
"title": "EpByteBusIdMapEntry models TC18 Table 23's EP_ID_config row and its end-of-table sentinel",
"text": "EpByteBusIdMapEntry carries the three fields TC18 v0.5.1_RC §12.7.8 Table 23 'EP_ID_config' (p.59) tabulates — map_stream_index (Request_Stream_Index, 0x0000, 8 bit), map_ep_nr (EP_Nr, 0x0001, 8 bit), and map_byte_bus_id (BBID, 0x0002, 16 bit carrying an 11-bit byte_bus_id) — reports CATEGORY == lifecycle::RegisterCategory::RcpConfig, and is_end_of_table returns true iff map_stream_index == END_OF_TABLE_STREAM_INDEX (0), per Table 23's note 'A Request_Stream_Index = 0 denotes the end of the table'. Per that same section, keeping rows in ascending order is the writing client's responsibility, so this module defines no ascending-order validation or enforcement",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
"verificationMethod": "test"
},
{
"id": "REQ-RMAP-019",
"title": "EpByteBusIdMapEntry encode/decode round-trip",
"text": "EpByteBusIdMapEntry::encode/decode round-trip every field exactly via a fixed-length big-endian wire form, and decode ignores any trailing bytes beyond ENCODED_LEN",
"title": "EpByteBusIdMapEntry encodes TC18 Table 23's 4-byte row layout",
"text": "EpByteBusIdMapEntry::ENCODED_LEN is 4, the row stride fixed by Table 23's own tabulation of the next row's 2_Request_Stream_Index at relative address 0x0004. encode emits [Request_Stream_Index, EP_Nr, BBID big-endian] in that order — EP_Nr at 0x0001 precedes BBID at 0x0002 — and encode/decode round-trip every field exactly; decode ignores any trailing bytes beyond ENCODED_LEN",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
Expand Down Expand Up @@ -2775,17 +2775,17 @@
},
{
"id": "REQ-RMAP-024",
"title": "SequencerStateEntry models the §3.11 per-sequencer state register and its power-on default",
"text": "SequencerStateEntry carries seq_state, reports CATEGORY == lifecycle::RegisterCategory::RcpConfig, and power_on_default() returns seq_state == 1",
"title": "SequencerStateEntry models both TC18 Table 25 fields and the power-on default",
"text": "SequencerStateEntry carries both fields TC18 v0.5.1_RC §12.7.10 Table 25 'SEQUENCER_config' (p.61) tabulates per sequencer — seq_state (0x0000, 8 bit) and request_stream_index (0x0001, 8 bit, 'refers the Client Nr allowed to access this sequencer', the access-control binding §12.7.10 describes as 'Each sequencer is dedicated to a specific RC Client and its bound endpoints') — giving a 2-byte row stride, fixed by Table 25's own tabulation of Seq_2's Seq_state at relative address 0x0002. It reports CATEGORY == lifecycle::RegisterCategory::RcpConfig, and power_on_default() returns seq_state == 1 per §12.7.10 ('Upon power-on reset all sequencer state values are set to \"1\"') with request_stream_index == 0, Table 25 documenting no default for that field",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
"verificationMethod": "test"
},
{
"id": "REQ-RMAP-025",
"title": "SequencerStateEntry encode/decode round-trip",
"text": "SequencerStateEntry::encode/decode round-trip every field exactly via a fixed-length wire form, and decode ignores any trailing bytes beyond ENCODED_LEN",
"title": "SequencerStateEntry encodes TC18 Table 25's 2-byte row layout",
"text": "SequencerStateEntry::ENCODED_LEN is 2 and encode emits [seq_state, request_stream_index] at Table 25's relative addresses 0x0000 and 0x0001, so that a multi-sequencer table decoded through decode_rows resolves Seq_2's Seq_state at offset 0x0002. encode/decode round-trip both fields exactly, and decode ignores any trailing bytes beyond ENCODED_LEN",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
Expand All @@ -2794,7 +2794,7 @@
{
"id": "REQ-RMAP-026",
"title": "SequencerStateEntry decode rejects short input and never panics",
"text": "SequencerStateEntry::decode returns Err(RcpError::ShortFrame) for every input shorter than ENCODED_LEN and never panics for any input length",
"text": "SequencerStateEntry::decode returns Err(RcpError::ShortFrame) for every input shorter than ENCODED_LEN (2), including a 1-byte input, and never panics for any input length",
"standard": "iso26262",
"level": "HLR",
"asil": "ASIL-B",
Expand Down
94 changes: 94 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,100 @@ OPEN Alliance TC18 core replacement; from `v1.0.0` on, each entry is a real
release. See `docs/SEMVER.md` for the versioning scheme, including why a
wire-format change is a MAJOR bump even when it is a fix.

## v5.0.0 (2026-07-31 TC18-conformant power-mode model + register-map config tables) — closed

**Breaking**, on the wire for three register-map config-table row types and
behaviourally for the power-mode model. Four independently-confirmed
findings, each verified against the specification's own tables and figures
before being changed — and, where the layout question was one of bit
packing or table structure that text extraction cannot settle, against a
600/300 dpi render of the relevant page rather than extracted text.

The register-map row types are not yet reachable from any live decode path
(nothing in this crate performs register I/O against a real RC Server), so
the interop urgency is lower than `v3.0.0`/`v4.0.0`; the power-mode finding
is a real behavioural bug in code that is callable today.

A common root cause runs through three of the four: this crate's own
provenance notes asserted that the specification records these tables'
field names and purpose "in prose" with "no explicit per-field bit-width or
byte-offset table" and "no textual basis for a specific bit-position
assignment". That was false — §12.7.7 Table 22, §12.7.8 Table 23 and
§12.7.10 Table 25 each carry explicit "Relative address" and "Type"
columns, and Table 22 additionally gives `0x000D.0`-`0x000D.7` bit
addresses. The layouts built on that false scarcity were wrong, and the
notes have been corrected rather than merely the code.

A second contributing cause: every affected encoder's tests asserted only
`decode(encode(x)) == x`. A round trip through one's own encoder cannot
detect a wrong layout. Every fix below adds literal expected-byte vectors
laid out by hand from the specification's address columns, chosen so that a
transposition or a mis-sized row cannot pass.

- **rust-RCP-P01 (BREAKING, ASIL-B):** the cold-/hot-start mapping in
`src/powerstate.rs` was inverted. §12.4.1 "Power-On / Wake-Up / Start-Up
behavior" (p.46) states it in one sentence — "There are two types of
start-up: a cold start (after power-on **or wake-up from sleep**) and a
hot start (**=wake-up from StandBy**)" — and §12.4 Figure 17 labels the
arrows to match. `try_cold_start` accepted only `Unpowered -> Normal`,
omitting the `Sleep -> Normal` cold start entirely; `try_hot_start`
claimed `Sleep -> Normal` and gated it behind the WakeUp handshake, when
TC18's hot start is `StandBy -> Normal` and it is the *hot*-start
procedure that §12.4.1 attaches that handshake to. Net effect on a caller
wiring this up: every wake-from-sleep was rejected until a handshake that
the specification does not require there had completed, and every
wake-from-standby was rejected outright, there being no path admitting
that origin at all. `try_cold_start` now admits both documented origins
and `try_hot_start` admits `StandBy`.
- **rust-RCP-P02 (BREAKING):** `is_power_mode_transition_defined` accepted
`StandBy <-> Sleep` as an ordinary transition. Figure 17 draws no edge of
any kind between the two low-power modes — it places `Normal`/`StandBy`
in a "Powered" box and `Sleep` in a separate "Only part of PHY powered"
box, with both low-power modes entered from and returned to `Normal`
only. It also omitted `Normal -> Sleep`, which Figure 17 *does* draw
("Go to Sleep"). The function now returns `true` for exactly Figure 17's
two "Go to ..." edges, `Normal -> StandBy` and `Normal -> Sleep`, and
`false` for all fourteen other ordered pairs. The prior set was derived
from reading the four mode *names* as a depth ordering, which the
specification never states; that inference is what produced P01 as well,
and the module's doc comment now reproduces Figure 17's edge list
directly instead.
- **rust-RCP-P03 (BREAKING, wire):** `regmap::RequestStreamConfigEntry`
used the wrong row layout. §12.7.7 Table 22 (pp.57-58) packs eight
per-stream flags (`rx_enforce_e2e` .. `rx_wd_info_enable`) into the
single bit-addressed byte at `0x000D` — they are the only fields in the
table addressed with a `.bit` suffix — and closes each row with a 16-bit
reserved word at `0x0012` and a 32-bit reserved block at `0x0014`, the
next row's `rx_stream_id2` at `0x0018` fixing the stride at **24 bytes**.
This crate gave each flag a whole byte of its own and dropped all six
reserved bytes, for `ENCODED_LEN = 25` and a wrong offset for every field
from `0x000D` onward. `ENCODED_LEN` is now 24, the eight flag fields are
typed `bool` to match their 1-bit width (a `u8` could not round-trip
losslessly through one bit), and `FLAGS_OFFSET` plus eight `FLAG_*` mask
constants and a `flags_byte()` accessor expose the packing.
- **rust-RCP-P04 (BREAKING, wire):** `regmap::EpByteBusIdMapEntry`
transposed two fields. §12.7.8 Table 23 "EP_ID_config" (p.59) tabulates
`Request_Stream_Index` at `0x0000`, `EP_Nr` at `0x0001` and `BBID` at
`0x0002`; this crate emitted `[stream_index, BBID_hi, BBID_lo, EP_Nr]`.
The row length was coincidentally right; the middle three bytes were not.
- **rust-RCP-P05 (BREAKING, wire):** `regmap::SequencerStateEntry` modeled
one of the row's two fields. §12.7.10 Table 25 "SEQUENCER_config" (p.61)
gives each sequencer `Seq_state` at `0x0000` **and**
`Request_stream_index` at `0x0001` — the latter being the access-control
binding the section describes ("Each sequencer is dedicated to a specific
RC Client and its bound endpoints"; the field "refers the Client Nr
allowed to access this sequencer") — with `Seq_2` at `0x0002` fixing the
stride at 2 bytes. This crate carried only `seq_state` with
`ENCODED_LEN = 1`, so a multi-sequencer table read through `decode_rows`
both lost every sequencer's client binding and misaligned every row after
the first. The field is now present and `ENCODED_LEN` is 2.

Not changed, and still to reconcile: `regmap::ResponseStreamConfigEntry`'s
layout has not been checked against §12.7.9 in this pass and remains this
crate's own inference; and `RequestStreamConfigEntry::default()` is still
all-zero, where Table 22 documents a default of `1` for
`rx_resp_stream_index`.

## v4.0.0 (2026-07-31 TC18-conformant NTSCF/TSCF AVTPDU header) — closed

**Breaking, on the wire.** `src/avtp.rs`'s NTSCF and TSCF header
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rcp"
version = "4.0.0"
version = "5.0.0"
edition = "2021"
rust-version = "1.75"
license = "MPL-2.0"
Expand Down
Loading
Loading