diff --git a/.fusa-reqs.json b/.fusa-reqs.json index 1040087..78b2a97 100644 --- a/.fusa-reqs.json +++ b/.fusa-reqs.json @@ -5427,6 +5427,15 @@ "level": "HLR", "asil": "ASIL-B", "verificationMethod": "test" + }, + { + "id": "REQ-RESP-004", + "title": "ByteMessageInfo::response_kind classifies per TC18 Table 15", + "text": "ByteMessageInfo::response_kind() shall return ResponseKind::Acknowledge whenever evt.raw_nibble() equals 0xF, regardless of err or op, per TC18 Table 15/\u00a711.3.1 -- a rejected Acknowledge (err=true) is still an Acknowledge, not a ResponseKind::Error. Otherwise it shall return ResponseKind::Error whenever err is true, and otherwise ResponseKind::Write when op is true or ResponseKind::Read.", + "standard": "iso26262", + "level": "HLR", + "asil": "ASIL-B", + "verificationMethod": "test" } ] } diff --git a/Cargo.lock b/Cargo.lock index 3d1a6b9..eae72f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,7 +333,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rcp" -version = "5.0.1" +version = "5.1.0" dependencies = [ "async-trait", "base64", diff --git a/Cargo.toml b/Cargo.toml index 2e9cb53..6c67539 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rcp" -version = "5.0.1" +version = "5.1.0" edition = "2021" rust-version = "1.75" license = "MPL-2.0" diff --git a/docs/PUBLIC_API.txt b/docs/PUBLIC_API.txt index 0f2c6dc..61abeac 100644 --- a/docs/PUBLIC_API.txt +++ b/docs/PUBLIC_API.txt @@ -1,5 +1,26 @@ pub mod rcp pub mod rcp::acf +pub enum rcp::acf::ResponseKind +pub rcp::acf::ResponseKind::Acknowledge +pub rcp::acf::ResponseKind::Error +pub rcp::acf::ResponseKind::Read +pub rcp::acf::ResponseKind::Write +impl core::clone::Clone for rcp::acf::ResponseKind +pub fn rcp::acf::ResponseKind::clone(&self) -> rcp::acf::ResponseKind +impl core::cmp::Eq for rcp::acf::ResponseKind +impl core::cmp::PartialEq for rcp::acf::ResponseKind +pub fn rcp::acf::ResponseKind::eq(&self, &rcp::acf::ResponseKind) -> bool +impl core::fmt::Debug for rcp::acf::ResponseKind +pub fn rcp::acf::ResponseKind::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Copy for rcp::acf::ResponseKind +impl core::marker::StructuralPartialEq for rcp::acf::ResponseKind +impl core::marker::Freeze for rcp::acf::ResponseKind +impl core::marker::Send for rcp::acf::ResponseKind +impl core::marker::Sync for rcp::acf::ResponseKind +impl core::marker::Unpin for rcp::acf::ResponseKind +impl core::marker::UnsafeUnpin for rcp::acf::ResponseKind +impl core::panic::unwind_safe::RefUnwindSafe for rcp::acf::ResponseKind +impl core::panic::unwind_safe::UnwindSafe for rcp::acf::ResponseKind pub struct rcp::acf::AcfAbbMessage pub rcp::acf::AcfAbbMessage::info: rcp::acf::ByteMessageInfo pub rcp::acf::AcfAbbMessage::payload: alloc::vec::Vec @@ -58,6 +79,7 @@ pub rcp::acf::ByteMessageInfo::rsp: bool pub rcp::acf::ByteMessageInfo::transaction_num: u8 impl rcp::acf::ByteMessageInfo pub fn rcp::acf::ByteMessageInfo::read_size(&self) -> core::option::Option +pub fn rcp::acf::ByteMessageInfo::response_kind(&self) -> rcp::acf::ResponseKind pub fn rcp::acf::ByteMessageInfo::segment_num(&self) -> core::option::Option impl core::clone::Clone for rcp::acf::ByteMessageInfo pub fn rcp::acf::ByteMessageInfo::clone(&self) -> rcp::acf::ByteMessageInfo @@ -84,6 +106,8 @@ impl core::panic::unwind_safe::UnwindSafe for rcp::acf::ByteMessageInfo pub struct rcp::acf::Evt pub rcp::acf::Evt::ack: bool pub rcp::acf::Evt::sub_opcode: u8 +impl rcp::acf::Evt +pub fn rcp::acf::Evt::raw_nibble(&self) -> u8 impl core::clone::Clone for rcp::acf::Evt pub fn rcp::acf::Evt::clone(&self) -> rcp::acf::Evt impl core::cmp::Eq for rcp::acf::Evt @@ -132,6 +156,7 @@ pub const rcp::acf::ACF_MSG_LENGTH_9BIT_MAX: u16 pub const rcp::acf::ACF_MSG_TYPE_7BIT_MAX: u8 pub const rcp::acf::BYTE_MESSAGE_INFO_11BIT_MAX: u16 pub const rcp::acf::BYTE_MESSAGE_INFO_LEN: usize +pub const rcp::acf::EVT_RESPONSE_ACKNOWLEDGE: u8 pub const rcp::acf::EVT_SUB_OPCODE_MAX: u8 pub const rcp::acf::PAD_2BIT_MAX: u8 pub const rcp::acf::QUADLET_LEN: usize diff --git a/src/acf.rs b/src/acf.rs index 6b227c3..de804d8 100644 --- a/src/acf.rs +++ b/src/acf.rs @@ -225,6 +225,19 @@ pub struct Evt { pub sub_opcode: u8, } +impl Evt { + /// The raw 4-bit `evt` nibble this value packs to on the wire + /// (`ack << 3 | sub_opcode`), the same arithmetic + /// [`encode_byte_message_info`] uses. Needed by + /// [`ByteMessageInfo::response_kind`], which reads `evt` under TC18 + /// §11.3 Table 15's whole-nibble response encoding rather than this + /// type's own request-side ack+sub_opcode split — see this struct's + /// doc comment. + pub fn raw_nibble(&self) -> u8 { + ((self.ack as u8) << 3) | (self.sub_opcode & 0x7) + } +} + /// The dual-purpose 12-bit field TC18 Table 4 calls /// `read_size_or_segment_num` (row 2, octet 6 bits 3:0 + octet 7) — a /// requested read byte count when the enclosing [`ByteMessageInfo::op`] @@ -361,6 +374,57 @@ impl ByteMessageInfo { None } } + + /// Classify this header under TC18 §11.3 Table 15/§11.3.1-§11.3.4's + /// response-direction reading of `evt` — the whole-nibble split + /// [`Evt`]'s own doc comment says its `ack`+`sub_opcode` fields do + /// *not* model. Meaningless for a request header, whose `evt` carries + /// §13.5's entirely different per-endpoint meaning instead (see + /// [`Evt`]). + /// + /// `evt[3:0] == 0xF` (§11.3.1) identifies an Acknowledge + /// unconditionally, checked before `err`/`op`: a rejected Acknowledge + /// (`err == true`) is still an Acknowledge, not a + /// [`ResponseKind::Error`], which is a distinct `evt[3:0] < 0x9` + /// case (§11.3.4). + //fusa:req REQ-RESP-004 + pub fn response_kind(&self) -> ResponseKind { + if self.evt.raw_nibble() == EVT_RESPONSE_ACKNOWLEDGE { + ResponseKind::Acknowledge + } else if self.err { + ResponseKind::Error + } else if self.op { + ResponseKind::Write + } else { + ResponseKind::Read + } + } +} + +/// TC18 §11.3 Table 15's `evt[3:0] == 0xF` response-kind marker +/// (TC18.txt line 1876: "evt[3:0] = 0xF - acknowledge"). +pub const EVT_RESPONSE_ACKNOWLEDGE: u8 = 0x0F; + +/// The four response semantics TC18 §11.3 Table 15/§11.3.1-§11.3.4 define +/// for a decoded [`ByteMessageInfo`] whose `rsp` bit is set. See +/// [`ByteMessageInfo::response_kind`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ResponseKind { + /// `evt[3:0] == 0xF` (§11.3.1): `err == false` confirms the request + /// was filed into the endpoint's request storage with no + /// `byte_msg_payload`; `err == true` indicates the request was + /// rejected and `byte_msg_payload` carries an error code. A rejected + /// Acknowledge stays an Acknowledge — it is not [`ResponseKind::Error`]. + Acknowledge, + /// `evt[3:0] < 0x9` and `op == true` (§11.3.2): confirms successful + /// execution of a write request, no `byte_msg_payload`. + Write, + /// `evt[3:0] < 0x9` and `op == false` (§11.3.3): confirms successful + /// execution of a read request, `byte_msg_payload` present. + Read, + /// `evt[3:0] < 0x9` and `err == true` (§11.3.4): execution of the + /// request failed, `byte_msg_payload` carries an error code. + Error, } /// Encode a [`ByteMessageInfo`] to its 8-byte wire representation, per this @@ -407,8 +471,7 @@ pub fn encode_byte_message_info( // octet 4: evt (ack:1 + sub_opcode:3) in bits 7:4, rsv(2 bits, zero) in // bits 3:2, hs in bit 1, cs in bit 0. - let evt_bits = ((info.evt.ack as u8) << 3) | (info.evt.sub_opcode & 0x7); - buf[4] = (evt_bits << 4) | ((info.hs as u8) << 1) | (info.cs as u8); + buf[4] = (info.evt.raw_nibble() << 4) | ((info.hs as u8) << 1) | (info.cs as u8); // octet 5: transaction_num, full byte — comes before the op/rsp/err/ms // group, per Table 4's row-2 ordering. @@ -1167,6 +1230,120 @@ mod tests { } } + // ── response_kind (TC18 §11.3 Table 15) ───────────────────────────────── + + #[test] + //fusa:test REQ-RESP-004 + fn response_kind_acknowledge() { + let info = ByteMessageInfo { + evt: Evt { + ack: true, + sub_opcode: 0x7, + }, + op: true, + err: false, + ..Default::default() + }; + assert_eq!(info.response_kind(), ResponseKind::Acknowledge); + } + + #[test] + //fusa:test REQ-RESP-004 + fn response_kind_acknowledge_from_read_op() { + let info = ByteMessageInfo { + evt: Evt { + ack: true, + sub_opcode: 0x7, + }, + op: false, + err: false, + ..Default::default() + }; + assert_eq!(info.response_kind(), ResponseKind::Acknowledge); + } + + // A rejected Acknowledge (evt[3:0] = 0xF, err = true) is still an + // Acknowledge per TC18 §11.3.1, not a ResponseKind::Error -- that's a + // distinct evt[3:0] < 0x9 case (§11.3.4). err must not take priority + // over evt here. + #[test] + //fusa:test REQ-RESP-004 + fn response_kind_acknowledge_rejected_is_not_error() { + let info = ByteMessageInfo { + evt: Evt { + ack: true, + sub_opcode: 0x7, + }, + op: true, + err: true, + ..Default::default() + }; + assert_eq!(info.response_kind(), ResponseKind::Acknowledge); + } + + #[test] + //fusa:test REQ-RESP-004 + fn response_kind_write() { + let info = ByteMessageInfo { + evt: Evt { + ack: false, + sub_opcode: 0, + }, + op: true, + err: false, + ..Default::default() + }; + assert_eq!(info.response_kind(), ResponseKind::Write); + } + + #[test] + //fusa:test REQ-RESP-004 + fn response_kind_read() { + let info = ByteMessageInfo { + evt: Evt { + ack: false, + sub_opcode: 0, + }, + op: false, + err: false, + ..Default::default() + }; + assert_eq!(info.response_kind(), ResponseKind::Read); + } + + #[test] + //fusa:test REQ-RESP-004 + fn response_kind_error() { + let info = ByteMessageInfo { + evt: Evt { + ack: false, + sub_opcode: 0, + }, + op: false, + err: true, + ..Default::default() + }; + assert_eq!(info.response_kind(), ResponseKind::Error); + } + + // evt[3:0] = 0x1..0x8 is the multi-response counter range (§11.3, not + // yet modeled as its own concept), which must still fall through to + // the ordinary err/op classification, not be misread as Acknowledge. + #[test] + //fusa:test REQ-RESP-004 + fn response_kind_counter_range_falls_through_to_write() { + let info = ByteMessageInfo { + evt: Evt { + ack: false, + sub_opcode: 0x3, + }, + op: true, + err: false, + ..Default::default() + }; + assert_eq!(info.response_kind(), ResponseKind::Write); + } + // ── Canonical layout: bit-position pins ───────────────────────────────── // // Directly pins the octet-by-octet packing this module's "Canonical