Skip to main content

rtlp_lib/
lib.rs

1#![warn(missing_docs)]
2#![deny(unsafe_code)]
3
4//! Rust library for parsing PCI Express Transaction Layer Packets (TLPs).
5//!
6//! Supports both non-flit (PCIe 1.0–5.0) and flit-mode (PCIe 6.0+) framing.
7
8use std::fmt::{self, Display};
9
10use bitfield::bitfield;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14/// Selects the framing mode used to interpret the raw byte buffer.
15///
16/// Non-flit mode covers PCIe 1.0 through 5.0. Flit mode was introduced
17/// in PCIe 6.0 and uses fixed 256-byte containers.
18#[non_exhaustive]
19#[derive(Debug, Clone, Copy, PartialEq)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21pub enum TlpMode {
22    /// Standard non-flit TLP framing (PCIe 1.0 – 5.0).
23    /// Bytes are interpreted directly as a TLP header followed by optional payload.
24    NonFlit,
25
26    /// Flit-mode TLP framing (introduced in PCIe 6.0 Base Spec; can operate at any link speed when both endpoints support flit mode).
27    /// Supported by `TlpPacket::new` and `TlpPacket::flit_type()`.
28    /// `TlpPacketHeader::new` with this mode returns `Err(TlpError::NotImplemented)`.
29    Flit,
30}
31
32/// Errors that can occur when parsing TLP packets
33#[derive(Debug, Clone, PartialEq)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35pub enum TlpError {
36    /// Invalid format field value (bits don't match any known format)
37    InvalidFormat,
38    /// Invalid type field value (bits don't match any known type encoding)
39    InvalidType,
40    /// Unsupported combination of format and type
41    UnsupportedCombination,
42    /// Payload/header byte slice is too short to contain the expected fields
43    InvalidLength,
44    /// Feature exists in the API but is not yet implemented
45    NotImplemented,
46    /// A TLP type that requires a mandatory OHC word was parsed without it
47    /// (e.g. I/O Write missing OHC-A2, Configuration Write missing OHC-A3)
48    MissingMandatoryOhc,
49}
50
51impl fmt::Display for TlpError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self {
54            TlpError::InvalidFormat => write!(f, "invalid TLP format field"),
55            TlpError::InvalidType => write!(f, "invalid TLP type field"),
56            TlpError::UnsupportedCombination => write!(f, "unsupported format/type combination"),
57            TlpError::InvalidLength => write!(f, "byte slice too short for expected TLP fields"),
58            TlpError::NotImplemented => write!(f, "feature not yet implemented"),
59            TlpError::MissingMandatoryOhc => {
60                write!(f, "mandatory OHC word missing for this TLP type")
61            }
62        }
63    }
64}
65
66impl std::error::Error for TlpError {}
67
68/// TLP format field encoding — encodes header size and whether a data payload is present.
69#[repr(u8)]
70#[derive(Debug, PartialEq, Copy, Clone)]
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72pub enum TlpFmt {
73    /// 3 DW header, no data payload.
74    NoDataHeader3DW = 0b000,
75    /// 4 DW header, no data payload.
76    NoDataHeader4DW = 0b001,
77    /// 3 DW header with data payload.
78    WithDataHeader3DW = 0b010,
79    /// 4 DW header with data payload.
80    WithDataHeader4DW = 0b011,
81    /// TLP Prefix (not a request or completion).
82    TlpPrefix = 0b100,
83}
84
85impl Display for TlpFmt {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        let name = match self {
88            TlpFmt::NoDataHeader3DW => "3DW no Data Header",
89            TlpFmt::NoDataHeader4DW => "4DW no Data Header",
90            TlpFmt::WithDataHeader3DW => "3DW with Data Header",
91            TlpFmt::WithDataHeader4DW => "4DW with Data Header",
92            TlpFmt::TlpPrefix => "Tlp Prefix",
93        };
94        write!(f, "{name}")
95    }
96}
97
98impl TryFrom<u32> for TlpFmt {
99    type Error = TlpError;
100
101    fn try_from(v: u32) -> Result<Self, Self::Error> {
102        match v {
103            0b000 => Ok(TlpFmt::NoDataHeader3DW),
104            0b001 => Ok(TlpFmt::NoDataHeader4DW),
105            0b010 => Ok(TlpFmt::WithDataHeader3DW),
106            0b011 => Ok(TlpFmt::WithDataHeader4DW),
107            0b100 => Ok(TlpFmt::TlpPrefix),
108            _ => Err(TlpError::InvalidFormat),
109        }
110    }
111}
112
113/// Atomic operation discriminant
114#[derive(Debug, Copy, Clone, PartialEq)]
115#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
116pub enum AtomicOp {
117    /// Fetch-and-Add atomic operation.
118    FetchAdd,
119    /// Unconditional Swap atomic operation.
120    Swap,
121    /// Compare-and-Swap atomic operation.
122    CompareSwap,
123}
124
125/// Operand width — derived from TLP format: 3DW → 32-bit, 4DW → 64-bit
126#[derive(Debug, Copy, Clone, PartialEq)]
127#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
128pub enum AtomicWidth {
129    /// 32-bit operand (3 DW header).
130    W32,
131    /// 64-bit operand (4 DW header).
132    W64,
133}
134
135#[derive(PartialEq)]
136pub(crate) enum TlpFormatEncodingType {
137    MemoryRequest = 0b00000,
138    MemoryLockRequest = 0b00001,
139    IORequest = 0b00010,
140    ConfigType0Request = 0b00100,
141    ConfigType1Request = 0b00101,
142    Completion = 0b01010,
143    CompletionLocked = 0b01011,
144    FetchAtomicOpRequest = 0b01100,
145    UnconSwapAtomicOpRequest = 0b01101,
146    CompSwapAtomicOpRequest = 0b01110,
147    DeferrableMemoryWriteRequest = 0b11011,
148    /// Message Request — covers all 6 routing sub-types (0b10000..=0b10101).
149    /// Fmt=000/001 → MsgReq, Fmt=010/011 → MsgReqData.
150    MessageRequest = 0b10000,
151}
152
153impl TryFrom<u32> for TlpFormatEncodingType {
154    type Error = TlpError;
155
156    fn try_from(v: u32) -> Result<Self, Self::Error> {
157        match v {
158            0b00000 => Ok(TlpFormatEncodingType::MemoryRequest),
159            0b00001 => Ok(TlpFormatEncodingType::MemoryLockRequest),
160            0b00010 => Ok(TlpFormatEncodingType::IORequest),
161            0b00100 => Ok(TlpFormatEncodingType::ConfigType0Request),
162            0b00101 => Ok(TlpFormatEncodingType::ConfigType1Request),
163            0b01010 => Ok(TlpFormatEncodingType::Completion),
164            0b01011 => Ok(TlpFormatEncodingType::CompletionLocked),
165            0b01100 => Ok(TlpFormatEncodingType::FetchAtomicOpRequest),
166            0b01101 => Ok(TlpFormatEncodingType::UnconSwapAtomicOpRequest),
167            0b01110 => Ok(TlpFormatEncodingType::CompSwapAtomicOpRequest),
168            0b11011 => Ok(TlpFormatEncodingType::DeferrableMemoryWriteRequest),
169            // All message routing sub-types: route-to-RC, by-addr, by-ID,
170            // broadcast, local, gathered — Type[4:3]=10, bits[2:0]=routing
171            0b10000..=0b10101 => Ok(TlpFormatEncodingType::MessageRequest),
172            _ => Err(TlpError::InvalidType),
173        }
174    }
175}
176
177/// High-level TLP transaction type decoded from the DW0 Format and Type fields.
178#[derive(PartialEq, Debug)]
179#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
180pub enum TlpType {
181    /// 32-bit or 64-bit Memory Read Request.
182    MemReadReq,
183    /// Locked Memory Read Request.
184    MemReadLockReq,
185    /// 32-bit or 64-bit Memory Write Request.
186    MemWriteReq,
187    /// I/O Read Request.
188    IOReadReq,
189    /// I/O Write Request.
190    IOWriteReq,
191    /// Configuration Type 0 Read Request.
192    ConfType0ReadReq,
193    /// Configuration Type 0 Write Request.
194    ConfType0WriteReq,
195    /// Configuration Type 1 Read Request.
196    ConfType1ReadReq,
197    /// Configuration Type 1 Write Request.
198    ConfType1WriteReq,
199    /// Message Request (no data).
200    MsgReq,
201    /// Message Request with data payload.
202    MsgReqData,
203    /// Completion without data.
204    Cpl,
205    /// Completion with data.
206    CplData,
207    /// Locked Completion without data.
208    CplLocked,
209    /// Locked Completion with data.
210    CplDataLocked,
211    /// Fetch-and-Add AtomicOp Request.
212    FetchAddAtomicOpReq,
213    /// Unconditional Swap AtomicOp Request.
214    SwapAtomicOpReq,
215    /// Compare-and-Swap AtomicOp Request.
216    CompareSwapAtomicOpReq,
217    /// Deferrable Memory Write Request.
218    DeferrableMemWriteReq,
219    /// Local TLP Prefix.
220    LocalTlpPrefix,
221    /// End-to-End TLP Prefix.
222    EndToEndTlpPrefix,
223}
224
225impl Display for TlpType {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        let name = match self {
228            TlpType::MemReadReq => "Memory Read Request",
229            TlpType::MemReadLockReq => "Locked Memory Read Request",
230            TlpType::MemWriteReq => "Memory Write Request",
231            TlpType::IOReadReq => "IO Read Request",
232            TlpType::IOWriteReq => "IO Write Request",
233            TlpType::ConfType0ReadReq => "Type 0 Config Read Request",
234            TlpType::ConfType0WriteReq => "Type 0 Config Write Request",
235            TlpType::ConfType1ReadReq => "Type 1 Config Read Request",
236            TlpType::ConfType1WriteReq => "Type 1 Config Write Request",
237            TlpType::MsgReq => "Message Request",
238            TlpType::MsgReqData => "Message with Data Request",
239            TlpType::Cpl => "Completion",
240            TlpType::CplData => "Completion with Data",
241            TlpType::CplLocked => "Locked Completion",
242            TlpType::CplDataLocked => "Locked Completion with Data",
243            TlpType::FetchAddAtomicOpReq => "Fetch Add Atomic Op Request",
244            TlpType::SwapAtomicOpReq => "Swap Atomic Op Request",
245            TlpType::CompareSwapAtomicOpReq => "Compare Swap Atomic Op Request",
246            TlpType::DeferrableMemWriteReq => "Deferrable Memory Write Request",
247            TlpType::LocalTlpPrefix => "Local Tlp Prefix",
248            TlpType::EndToEndTlpPrefix => "End To End Tlp Prefix",
249        };
250        write!(f, "{name}")
251    }
252}
253
254impl TlpType {
255    /// Returns `true` for non-posted TLP types (requests that expect a Completion).
256    ///
257    /// Non-posted transactions include memory reads, I/O, configuration, atomics,
258    /// and Deferrable Memory Write. Posted writes (`MemWriteReq`, messages) return `false`.
259    pub fn is_non_posted(&self) -> bool {
260        matches!(
261            self,
262            TlpType::MemReadReq
263                | TlpType::MemReadLockReq
264                | TlpType::IOReadReq
265                | TlpType::IOWriteReq
266                | TlpType::ConfType0ReadReq
267                | TlpType::ConfType0WriteReq
268                | TlpType::ConfType1ReadReq
269                | TlpType::ConfType1WriteReq
270                | TlpType::FetchAddAtomicOpReq
271                | TlpType::SwapAtomicOpReq
272                | TlpType::CompareSwapAtomicOpReq
273                | TlpType::DeferrableMemWriteReq
274        )
275    }
276
277    /// Returns `true` for posted TLP types (no Completion expected).
278    ///
279    /// Convenience inverse of [`TlpType::is_non_posted`].
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// use rtlp_lib::TlpType;
285    ///
286    /// assert!(TlpType::MemWriteReq.is_posted());    // posted write
287    /// assert!(TlpType::MsgReq.is_posted());         // message
288    /// assert!(!TlpType::MemReadReq.is_posted());    // non-posted
289    /// ```
290    pub fn is_posted(&self) -> bool {
291        !self.is_non_posted()
292    }
293}
294
295bitfield! {
296        struct TlpHeader(MSB0 [u8]);
297        u32;
298        get_format, _: 2, 0;
299        get_type,   _: 7, 3;
300        get_t9,     _: 8, 8;
301        get_tc,     _: 11, 9;
302        get_t8,     _: 12, 12;
303        get_attr_b2, _: 13, 13;
304        get_ln,     _: 14, 14;
305        get_th,     _: 15, 15;
306        get_td,     _: 16, 16;
307        get_ep,     _: 17, 17;
308        get_attr,   _: 19, 18;
309        get_at,     _: 21, 20;
310        get_length, _: 31, 22;
311}
312
313impl<T: AsRef<[u8]>> TlpHeader<T> {
314    fn get_tlp_type(&self) -> Result<TlpType, TlpError> {
315        let tlp_type = self.get_type();
316        let tlp_fmt = self.get_format();
317
318        // TLP Prefix is identified by Fmt=0b100 alone, regardless of the Type field.
319        // Type[4]=0 → Local TLP Prefix; Type[4]=1 → End-to-End TLP Prefix.
320        if let Ok(TlpFmt::TlpPrefix) = TlpFmt::try_from(tlp_fmt) {
321            return if tlp_type & 0b10000 != 0 {
322                Ok(TlpType::EndToEndTlpPrefix)
323            } else {
324                Ok(TlpType::LocalTlpPrefix)
325            };
326        }
327
328        match TlpFormatEncodingType::try_from(tlp_type) {
329            Ok(TlpFormatEncodingType::MemoryRequest) => match TlpFmt::try_from(tlp_fmt) {
330                Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::MemReadReq),
331                Ok(TlpFmt::NoDataHeader4DW) => Ok(TlpType::MemReadReq),
332                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::MemWriteReq),
333                Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::MemWriteReq),
334                Ok(_) => Err(TlpError::UnsupportedCombination),
335                Err(e) => Err(e),
336            },
337            Ok(TlpFormatEncodingType::MemoryLockRequest) => match TlpFmt::try_from(tlp_fmt) {
338                Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::MemReadLockReq),
339                Ok(TlpFmt::NoDataHeader4DW) => Ok(TlpType::MemReadLockReq),
340                Ok(_) => Err(TlpError::UnsupportedCombination),
341                Err(e) => Err(e),
342            },
343            Ok(TlpFormatEncodingType::IORequest) => match TlpFmt::try_from(tlp_fmt) {
344                Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::IOReadReq),
345                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::IOWriteReq),
346                Ok(_) => Err(TlpError::UnsupportedCombination),
347                Err(e) => Err(e),
348            },
349            Ok(TlpFormatEncodingType::ConfigType0Request) => match TlpFmt::try_from(tlp_fmt) {
350                Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::ConfType0ReadReq),
351                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::ConfType0WriteReq),
352                Ok(_) => Err(TlpError::UnsupportedCombination),
353                Err(e) => Err(e),
354            },
355            Ok(TlpFormatEncodingType::ConfigType1Request) => match TlpFmt::try_from(tlp_fmt) {
356                Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::ConfType1ReadReq),
357                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::ConfType1WriteReq),
358                Ok(_) => Err(TlpError::UnsupportedCombination),
359                Err(e) => Err(e),
360            },
361            Ok(TlpFormatEncodingType::Completion) => match TlpFmt::try_from(tlp_fmt) {
362                Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::Cpl),
363                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::CplData),
364                Ok(_) => Err(TlpError::UnsupportedCombination),
365                Err(e) => Err(e),
366            },
367            Ok(TlpFormatEncodingType::CompletionLocked) => match TlpFmt::try_from(tlp_fmt) {
368                Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::CplLocked),
369                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::CplDataLocked),
370                Ok(_) => Err(TlpError::UnsupportedCombination),
371                Err(e) => Err(e),
372            },
373            Ok(TlpFormatEncodingType::FetchAtomicOpRequest) => match TlpFmt::try_from(tlp_fmt) {
374                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::FetchAddAtomicOpReq),
375                Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::FetchAddAtomicOpReq),
376                Ok(_) => Err(TlpError::UnsupportedCombination),
377                Err(e) => Err(e),
378            },
379            Ok(TlpFormatEncodingType::UnconSwapAtomicOpRequest) => {
380                match TlpFmt::try_from(tlp_fmt) {
381                    Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::SwapAtomicOpReq),
382                    Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::SwapAtomicOpReq),
383                    Ok(_) => Err(TlpError::UnsupportedCombination),
384                    Err(e) => Err(e),
385                }
386            }
387            Ok(TlpFormatEncodingType::CompSwapAtomicOpRequest) => match TlpFmt::try_from(tlp_fmt) {
388                Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::CompareSwapAtomicOpReq),
389                Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::CompareSwapAtomicOpReq),
390                Ok(_) => Err(TlpError::UnsupportedCombination),
391                Err(e) => Err(e),
392            },
393            Ok(TlpFormatEncodingType::DeferrableMemoryWriteRequest) => {
394                match TlpFmt::try_from(tlp_fmt) {
395                    Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::DeferrableMemWriteReq),
396                    Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::DeferrableMemWriteReq),
397                    Ok(_) => Err(TlpError::UnsupportedCombination),
398                    Err(e) => Err(e),
399                }
400            }
401            // Message Requests: all 6 routing sub-types map here.
402            // Fmt=000/001 (no data) → MsgReq; Fmt=010/011 (with data) → MsgReqData.
403            Ok(TlpFormatEncodingType::MessageRequest) => match TlpFmt::try_from(tlp_fmt) {
404                Ok(TlpFmt::NoDataHeader3DW) | Ok(TlpFmt::NoDataHeader4DW) => Ok(TlpType::MsgReq),
405                Ok(TlpFmt::WithDataHeader3DW) | Ok(TlpFmt::WithDataHeader4DW) => {
406                    Ok(TlpType::MsgReqData)
407                }
408                Ok(_) => Err(TlpError::UnsupportedCombination),
409                Err(e) => Err(e),
410            },
411            Err(e) => Err(e),
412        }
413    }
414}
415
416/// Memory Request Trait:
417/// Applies to 32 and 64 bits requests as well as legacy IO-Request
418/// (Legacy IO Request has the same structure as MemRead3DW)
419/// Software using the library may want to use trait instead of bitfield structures
420/// Both 3DW (32-bit) and 4DW (64-bit) headers implement this trait
421/// 3DW header is also used for all Legacy IO Requests.
422pub trait MemRequest {
423    /// Returns the Requester ID field (Bus/Device/Function).
424    fn address(&self) -> u64;
425    /// Returns the 16-bit Requester ID.
426    fn req_id(&self) -> u16;
427    /// Returns the 8-bit Tag field.
428    fn tag(&self) -> u8;
429    /// Returns the Last DW Byte Enable nibble.
430    fn ldwbe(&self) -> u8;
431    /// Returns the First DW Byte Enable nibble.
432    fn fdwbe(&self) -> u8;
433}
434
435// Bitfield structure for both 3DW Memory Request and Legacy IO Request headers.
436// Structure for both 3DW Memory Request as well as Legacy IO Request
437bitfield! {
438    #[allow(missing_docs)]
439    pub struct MemRequest3DW(MSB0 [u8]);
440    u32;
441    /// Returns the Requester ID field.
442    pub get_requester_id,   _: 15, 0;
443    /// Returns the Tag field.
444    pub get_tag,            _: 23, 16;
445    /// Returns the Last DW Byte Enable nibble.
446    pub get_last_dw_be,     _: 27, 24;
447    /// Returns the First DW Byte Enable nibble.
448    pub get_first_dw_be,    _: 31, 28;
449    /// Returns the 32-bit address field.
450    pub get_address32,      _: 63, 32;
451}
452
453// Bitfield structure for 4DW Memory Request headers.
454bitfield! {
455    #[allow(missing_docs)]
456    pub struct MemRequest4DW(MSB0 [u8]);
457    u64;
458    /// Returns the Requester ID field.
459    pub get_requester_id,   _: 15, 0;
460    /// Returns the Tag field.
461    pub get_tag,            _: 23, 16;
462    /// Returns the Last DW Byte Enable nibble.
463    pub get_last_dw_be,     _: 27, 24;
464    /// Returns the First DW Byte Enable nibble.
465    pub get_first_dw_be,    _: 31, 28;
466    /// Returns the 64-bit address field.
467    pub get_address64,      _: 95, 32;
468}
469
470impl<T: AsRef<[u8]>> MemRequest for MemRequest3DW<T> {
471    fn address(&self) -> u64 {
472        self.get_address32().into()
473    }
474    fn req_id(&self) -> u16 {
475        self.get_requester_id() as u16
476    }
477    fn tag(&self) -> u8 {
478        self.get_tag() as u8
479    }
480    fn ldwbe(&self) -> u8 {
481        self.get_last_dw_be() as u8
482    }
483    fn fdwbe(&self) -> u8 {
484        self.get_first_dw_be() as u8
485    }
486}
487
488impl<T: AsRef<[u8]>> MemRequest for MemRequest4DW<T> {
489    fn address(&self) -> u64 {
490        self.get_address64()
491    }
492    fn req_id(&self) -> u16 {
493        self.get_requester_id() as u16
494    }
495    fn tag(&self) -> u8 {
496        self.get_tag() as u8
497    }
498    fn ldwbe(&self) -> u8 {
499        self.get_last_dw_be() as u8
500    }
501    fn fdwbe(&self) -> u8 {
502        self.get_first_dw_be() as u8
503    }
504}
505
506/// Obtain Memory Request trait from bytes in vector as dyn.
507/// This is the preferred way of dealing with TLP headers when the exact format
508/// (32-bit vs 64-bit) does not need to be known at the call site.
509///
510/// # Errors
511///
512/// - [`TlpError::UnsupportedCombination`] if `format` is `TlpFmt::TlpPrefix`.
513///
514/// # Examples
515///
516/// ```
517/// use rtlp_lib::TlpPacket;
518/// use rtlp_lib::TlpFmt;
519/// use rtlp_lib::TlpError;
520/// use rtlp_lib::TlpMode;
521/// use rtlp_lib::MemRequest;
522/// use rtlp_lib::new_mem_req;
523///
524/// fn decode(bytes: Vec<u8>) -> Result<(), TlpError> {
525///     let tlp = TlpPacket::new(bytes, TlpMode::NonFlit)?;
526///
527///     let tlpfmt = tlp.tlp_format()?;
528///     // MemRequest contains only fields specific to PCI Memory Requests
529///     let mem_req: Box<dyn MemRequest> = new_mem_req(tlp.data(), &tlpfmt)?;
530///
531///     // Address is 64 bits regardless of TLP format
532///     // println!("Memory Request Address: {:x}", mem_req.address());
533///
534///     // Format of TLP (3DW vs 4DW) is stored in the TLP header
535///     println!("This TLP size is: {}", tlpfmt);
536///     // Type LegacyIO vs MemRead vs MemWrite is stored in first DW of TLP
537///     println!("This TLP type is: {:?}", tlp.tlp_type());
538///     Ok(())
539/// }
540///
541///
542/// # let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
543/// # decode(bytes).unwrap();
544/// ```
545pub fn new_mem_req(
546    bytes: impl Into<Vec<u8>>,
547    format: &TlpFmt,
548) -> Result<Box<dyn MemRequest>, TlpError> {
549    let bytes = bytes.into();
550    match format {
551        TlpFmt::NoDataHeader3DW | TlpFmt::WithDataHeader3DW => {
552            if bytes.len() < 8 {
553                return Err(TlpError::InvalidLength);
554            }
555            Ok(Box::new(MemRequest3DW(bytes)))
556        }
557        TlpFmt::NoDataHeader4DW | TlpFmt::WithDataHeader4DW => {
558            if bytes.len() < 12 {
559                return Err(TlpError::InvalidLength);
560            }
561            Ok(Box::new(MemRequest4DW(bytes)))
562        }
563        TlpFmt::TlpPrefix => Err(TlpError::UnsupportedCombination),
564    }
565}
566
567/// Configuration Request Trait:
568/// Configuration Requests Headers are always same size (3DW),
569/// this trait is provided to have same API as other headers with variable size
570pub trait ConfigurationRequest {
571    /// Returns the 16-bit Requester ID.
572    fn req_id(&self) -> u16;
573    /// Returns the 8-bit Tag field.
574    fn tag(&self) -> u8;
575    /// Returns the Bus Number.
576    fn bus_nr(&self) -> u8;
577    /// Returns the Device Number.
578    fn dev_nr(&self) -> u8;
579    /// Returns the Function Number.
580    fn func_nr(&self) -> u8;
581    /// Returns the Extended Register Number.
582    fn ext_reg_nr(&self) -> u8;
583    /// Returns the Register Number.
584    fn reg_nr(&self) -> u8;
585}
586
587/// Obtain Configuration Request trait from bytes in vector as dyn.
588///
589/// **Note:** The `bytes` slice must contain the full **DW1+DW2 payload** (8 bytes).
590/// `TlpPacket::data()` returns exactly those bytes when the packet was
591/// constructed from a complete 12-byte configuration request header.
592///
593/// # Examples
594///
595/// ```
596/// use rtlp_lib::{TlpPacket, TlpMode, ConfigurationRequest, new_conf_req};
597///
598/// // 12 bytes: DW0 (ConfType0WriteReq) + DW1 (req_id, tag, BE) + DW2 (bus/dev/func/reg)
599/// // DW0: 0x44=ConfType0WriteReq, length=1
600/// // DW1: req_id=0x0001, tag=0x00, BE=0x0F
601/// // DW2: bus=0xC2, device=0x10>>3=1, func=0, ext_reg=0, reg=4
602/// let bytes = vec![
603///     0x44, 0x00, 0x00, 0x01,  // DW0
604///     0x00, 0x01, 0x00, 0x0F,  // DW1
605///     0xC2, 0x08, 0x00, 0x10,  // DW2
606/// ];
607/// let tlp = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
608///
609/// // data() returns DW1+DW2 (8 bytes) — exactly what ConfigRequest needs
610/// // data() returns &[u8] — pass it directly, no .to_vec() needed
611/// let config_req: Box<dyn ConfigurationRequest> = new_conf_req(tlp.data()).unwrap();
612/// assert_eq!(config_req.bus_nr(), 0xC2);
613/// ```
614///
615/// # Errors
616///
617/// - [`TlpError::InvalidLength`] if `bytes.len() < 8` (ConfigRequest reads a 64-bit field).
618pub fn new_conf_req(bytes: impl Into<Vec<u8>>) -> Result<Box<dyn ConfigurationRequest>, TlpError> {
619    let bytes = bytes.into();
620    if bytes.len() < 8 {
621        return Err(TlpError::InvalidLength);
622    }
623    Ok(Box::new(ConfigRequest(bytes)))
624}
625
626// Bitfield structure for Configuration Request headers (DW1+DW2).
627bitfield! {
628    #[allow(missing_docs)]
629    pub struct ConfigRequest(MSB0 [u8]);
630    u32;
631    /// Returns the Requester ID field.
632    pub get_requester_id,   _: 15, 0;
633    /// Returns the Tag field.
634    pub get_tag,            _: 23, 16;
635    /// Returns the Last DW Byte Enable nibble.
636    pub get_last_dw_be,     _: 27, 24;
637    /// Returns the First DW Byte Enable nibble.
638    pub get_first_dw_be,    _: 31, 28;
639    /// Returns the Bus Number field.
640    pub get_bus_nr,         _: 39, 32;
641    /// Returns the Device Number field.
642    pub get_dev_nr,         _: 44, 40;
643    /// Returns the Function Number field.
644    pub get_func_nr,        _: 47, 45;
645    /// Reserved field.
646    pub rsvd,               _: 51, 48;
647    /// Returns the Extended Register Number field.
648    pub get_ext_reg_nr,     _: 55, 52;
649    /// Returns the Register Number field.
650    pub get_register_nr,    _: 61, 56;
651    r,                      _: 63, 62;
652}
653
654impl<T: AsRef<[u8]>> ConfigurationRequest for ConfigRequest<T> {
655    fn req_id(&self) -> u16 {
656        self.get_requester_id() as u16
657    }
658    fn tag(&self) -> u8 {
659        self.get_tag() as u8
660    }
661    fn bus_nr(&self) -> u8 {
662        self.get_bus_nr() as u8
663    }
664    fn dev_nr(&self) -> u8 {
665        self.get_dev_nr() as u8
666    }
667    fn func_nr(&self) -> u8 {
668        self.get_func_nr() as u8
669    }
670    fn ext_reg_nr(&self) -> u8 {
671        self.get_ext_reg_nr() as u8
672    }
673    fn reg_nr(&self) -> u8 {
674        self.get_register_nr() as u8
675    }
676}
677
678/// Completion Request Trait
679/// Completions are always 3DW (for with data (fmt = b010) and without data (fmt = b000) )
680/// This trait is provided to have same API as other headers with variable size
681/// To obtain this trait `new_cmpl_req()` function has to be used
682/// Trait release user from dealing with bitfield structures.
683pub trait CompletionRequest {
684    /// Returns the 16-bit Completer ID.
685    fn cmpl_id(&self) -> u16;
686    /// Returns the 3-bit Completion Status field.
687    fn cmpl_stat(&self) -> u8;
688    /// Returns the BCM (Byte Count Modified) bit.
689    fn bcm(&self) -> u8;
690    /// Returns the 12-bit Byte Count field.
691    fn byte_cnt(&self) -> u16;
692    /// Returns the 16-bit Requester ID.
693    fn req_id(&self) -> u16;
694    /// Returns the 8-bit Tag field.
695    fn tag(&self) -> u8;
696    /// Returns the 7-bit Lower Address field.
697    fn laddr(&self) -> u8;
698}
699
700// Bitfield structure for Completion Request DW2+DW3 fields.
701bitfield! {
702    #[allow(missing_docs)]
703    pub struct CompletionReqDW23(MSB0 [u8]);
704    u16;
705    /// Returns the Completer ID field.
706    pub get_completer_id,   _: 15, 0;
707    /// Returns the Completion Status field.
708    pub get_cmpl_stat,      _: 18, 16;
709    /// Returns the BCM bit.
710    pub get_bcm,            _: 19, 19;
711    /// Returns the Byte Count field.
712    pub get_byte_cnt,       _: 31, 20;
713    /// Returns the Requester ID field.
714    pub get_req_id,         _: 47, 32;
715    /// Returns the Tag field.
716    pub get_tag,            _: 55, 48;
717    r,                      _: 56, 56;
718    /// Returns the Lower Address field.
719    pub get_laddr,          _: 63, 57;
720}
721
722impl<T: AsRef<[u8]>> CompletionRequest for CompletionReqDW23<T> {
723    fn cmpl_id(&self) -> u16 {
724        self.get_completer_id()
725    }
726    fn cmpl_stat(&self) -> u8 {
727        self.get_cmpl_stat() as u8
728    }
729    fn bcm(&self) -> u8 {
730        self.get_bcm() as u8
731    }
732    fn byte_cnt(&self) -> u16 {
733        self.get_byte_cnt()
734    }
735    fn req_id(&self) -> u16 {
736        self.get_req_id()
737    }
738    fn tag(&self) -> u8 {
739        self.get_tag() as u8
740    }
741    fn laddr(&self) -> u8 {
742        self.get_laddr() as u8
743    }
744}
745
746/// Obtain Completion Request dyn Trait:
747///
748/// # Examples
749///
750/// ```
751/// use rtlp_lib::TlpFmt;
752/// use rtlp_lib::CompletionRequest;
753/// use rtlp_lib::new_cmpl_req;
754///
755/// let bytes = vec![0x20, 0x01, 0xFF, 0xC2, 0x00, 0x00, 0x00, 0x00];
756/// // TLP Format usually comes from TlpPacket or Header here we made up one for example
757/// let tlpfmt = TlpFmt::WithDataHeader4DW;
758///
759/// let cmpl_req: Box<dyn CompletionRequest> = new_cmpl_req(bytes).unwrap();
760///
761/// println!("Requester ID from Completion{}", cmpl_req.req_id());
762/// ```
763///
764/// # Errors
765///
766/// - [`TlpError::InvalidLength`] if `bytes.len() < 8` (CompletionReqDW23 reads a 64-bit field).
767pub fn new_cmpl_req(bytes: impl Into<Vec<u8>>) -> Result<Box<dyn CompletionRequest>, TlpError> {
768    let bytes = bytes.into();
769    if bytes.len() < 8 {
770        return Err(TlpError::InvalidLength);
771    }
772    Ok(Box::new(CompletionReqDW23(bytes)))
773}
774
775/// Message Request trait
776/// Provide method to access fields in DW2-4 header is handled by TlpHeader
777pub trait MessageRequest {
778    /// Returns the 16-bit Requester ID.
779    fn req_id(&self) -> u16;
780    /// Returns the 8-bit Tag field.
781    fn tag(&self) -> u8;
782    /// Returns the 8-bit Message Code field.
783    fn msg_code(&self) -> u8;
784    /// DW3 content — interpretation varies with Message Code.
785    fn dw3(&self) -> u32;
786    /// DW4 content — interpretation varies with Message Code.
787    fn dw4(&self) -> u32;
788}
789
790// Bitfield structure for Message Request DW2–DW4 fields.
791bitfield! {
792    #[allow(missing_docs)]
793    pub struct MessageReqDW24(MSB0 [u8]);
794    u32;
795    /// Returns the Requester ID field.
796    pub get_requester_id,   _: 15, 0;
797    /// Returns the Tag field.
798    pub get_tag,            _: 23, 16;
799    /// Returns the Message Code field.
800    pub get_msg_code,       _: 31, 24;
801    /// Returns DW3 content.
802    pub get_dw3,            _: 63, 32;
803    /// Returns DW4 content.
804    pub get_dw4,            _: 95, 64;
805}
806
807impl<T: AsRef<[u8]>> MessageRequest for MessageReqDW24<T> {
808    fn req_id(&self) -> u16 {
809        self.get_requester_id() as u16
810    }
811    fn tag(&self) -> u8 {
812        self.get_tag() as u8
813    }
814    fn msg_code(&self) -> u8 {
815        self.get_msg_code() as u8
816    }
817    fn dw3(&self) -> u32 {
818        self.get_dw3()
819    }
820    fn dw4(&self) -> u32 {
821        self.get_dw4()
822    }
823    // TODO: implement routedby method based on type
824}
825
826/// Obtain Message Request dyn Trait:
827///
828/// # Examples
829///
830/// ```
831/// use rtlp_lib::TlpFmt;
832/// use rtlp_lib::MessageRequest;
833/// use rtlp_lib::new_msg_req;
834///
835/// let bytes = vec![0x20, 0x01, 0xFF, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
836/// let tlpfmt = TlpFmt::NoDataHeader3DW;
837///
838/// let msg_req: Box<dyn MessageRequest> = new_msg_req(bytes).unwrap();
839///
840/// println!("Requester ID from Message{}", msg_req.req_id());
841/// ```
842///
843/// # Errors
844///
845/// - [`TlpError::InvalidLength`] if `bytes.len() < 12` (MessageReqDW24 reads up to bit 95).
846pub fn new_msg_req(bytes: impl Into<Vec<u8>>) -> Result<Box<dyn MessageRequest>, TlpError> {
847    let bytes = bytes.into();
848    if bytes.len() < 12 {
849        return Err(TlpError::InvalidLength);
850    }
851    Ok(Box::new(MessageReqDW24(bytes)))
852}
853
854/// Atomic Request trait: header fields and operand(s) for atomic op TLPs.
855/// Use `new_atomic_req()` to obtain a trait object from raw packet bytes.
856pub trait AtomicRequest: std::fmt::Debug {
857    /// Returns the atomic operation type.
858    fn op(&self) -> AtomicOp;
859    /// Returns the operand width (32-bit or 64-bit).
860    fn width(&self) -> AtomicWidth;
861    /// Returns the 16-bit Requester ID.
862    fn req_id(&self) -> u16;
863    /// Returns the 8-bit Tag field.
864    fn tag(&self) -> u8;
865    /// Returns the target address.
866    fn address(&self) -> u64;
867    /// Primary operand: addend (FetchAdd), new value (Swap), compare value (CAS)
868    fn operand0(&self) -> u64;
869    /// Second operand: swap value for CAS; `None` for FetchAdd and Swap
870    fn operand1(&self) -> Option<u64>;
871}
872
873#[derive(Debug)]
874struct AtomicReq {
875    op: AtomicOp,
876    width: AtomicWidth,
877    req_id: u16,
878    tag: u8,
879    address: u64,
880    operand0: u64,
881    operand1: Option<u64>,
882}
883
884impl AtomicRequest for AtomicReq {
885    fn op(&self) -> AtomicOp {
886        self.op
887    }
888    fn width(&self) -> AtomicWidth {
889        self.width
890    }
891    fn req_id(&self) -> u16 {
892        self.req_id
893    }
894    fn tag(&self) -> u8 {
895        self.tag
896    }
897    fn address(&self) -> u64 {
898        self.address
899    }
900    fn operand0(&self) -> u64 {
901        self.operand0
902    }
903    fn operand1(&self) -> Option<u64> {
904        self.operand1
905    }
906}
907
908fn read_operand_be(b: &[u8], off: usize, width: AtomicWidth) -> u64 {
909    match width {
910        AtomicWidth::W32 => u32::from_be_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]]) as u64,
911        AtomicWidth::W64 => u64::from_be_bytes([
912            b[off],
913            b[off + 1],
914            b[off + 2],
915            b[off + 3],
916            b[off + 4],
917            b[off + 5],
918            b[off + 6],
919            b[off + 7],
920        ]),
921    }
922}
923
924/// Parse an atomic TLP request from a `TlpPacket`.
925///
926/// The TLP type and format are extracted from the packet header.
927///
928/// # Errors
929///
930/// - [`TlpError::UnsupportedCombination`] if the packet does not encode one of the
931///   three atomic op types, or if the format field is not `WithData3DW`/`WithData4DW`.
932/// - [`TlpError::InvalidLength`] if the data payload size does not match
933///   the expected header + operand(s) size for the detected atomic type and width.
934///
935/// # Examples
936///
937/// ```
938/// use rtlp_lib::{TlpPacket, TlpMode, AtomicRequest, new_atomic_req};
939///
940/// // FetchAdd 3DW: DW0 byte0 = (fmt=0b010 << 5) | typ=0b01100 = 0x4C
941/// let bytes = vec![
942///     0x4C, 0x00, 0x00, 0x00, // DW0: WithDataHeader3DW / FetchAdd
943///     0xAB, 0xCD, 0x01, 0x00, // DW1: req_id=0xABCD tag=1 BE=0
944///     0x00, 0x00, 0x10, 0x00, // DW2: address32=0x0000_1000
945///     0x00, 0x00, 0x00, 0x04, // operand: addend=4
946/// ];
947/// let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
948/// let ar = new_atomic_req(&pkt).unwrap();
949/// assert_eq!(ar.req_id(),   0xABCD);
950/// assert_eq!(ar.operand0(), 4);
951/// assert!(ar.operand1().is_none());
952/// ```
953pub fn new_atomic_req(pkt: &TlpPacket) -> Result<Box<dyn AtomicRequest>, TlpError> {
954    let tlp_type = pkt.tlp_type()?;
955    let format = pkt.tlp_format()?;
956    let bytes = pkt.data();
957
958    let op = match tlp_type {
959        TlpType::FetchAddAtomicOpReq => AtomicOp::FetchAdd,
960        TlpType::SwapAtomicOpReq => AtomicOp::Swap,
961        TlpType::CompareSwapAtomicOpReq => AtomicOp::CompareSwap,
962        _ => return Err(TlpError::UnsupportedCombination),
963    };
964    let (width, hdr_len) = match format {
965        TlpFmt::WithDataHeader3DW => (AtomicWidth::W32, 8usize),
966        TlpFmt::WithDataHeader4DW => (AtomicWidth::W64, 12usize),
967        _ => return Err(TlpError::UnsupportedCombination),
968    };
969
970    let op_size = match width {
971        AtomicWidth::W32 => 4usize,
972        AtomicWidth::W64 => 8usize,
973    };
974    let num_ops = if matches!(op, AtomicOp::CompareSwap) {
975        2
976    } else {
977        1
978    };
979    let needed = hdr_len + op_size * num_ops;
980    if bytes.len() != needed {
981        return Err(TlpError::InvalidLength);
982    }
983
984    let req_id = u16::from_be_bytes([bytes[0], bytes[1]]);
985    let tag = bytes[2];
986    let address = match width {
987        AtomicWidth::W32 => u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as u64,
988        AtomicWidth::W64 => u64::from_be_bytes([
989            bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11],
990        ]),
991    };
992
993    let operand0 = read_operand_be(bytes, hdr_len, width);
994    let operand1 = if matches!(op, AtomicOp::CompareSwap) {
995        Some(read_operand_be(bytes, hdr_len + op_size, width))
996    } else {
997        None
998    };
999
1000    Ok(Box::new(AtomicReq {
1001        op,
1002        width,
1003        req_id,
1004        tag,
1005        address,
1006        operand0,
1007        operand1,
1008    }))
1009}
1010
1011// ============================================================================
1012// Flit Mode types (introduced in PCIe 6.0 Base Spec)
1013// ============================================================================
1014
1015/// TLP type codes used in Flit Mode DW0 byte 0.
1016///
1017/// These are **completely different** from the non-flit `TlpType` encoding.
1018/// In flit mode, `DW0[7:0]` is a flat 8-bit type code rather than the
1019/// non-flit `Fmt[2:0] | Type[4:0]` split.
1020///
1021/// `#[non_exhaustive]` — future type codes will be added without breaking
1022/// downstream `match` arms.
1023#[non_exhaustive]
1024#[derive(Debug, Clone, Copy, PartialEq)]
1025#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1026pub enum FlitTlpType {
1027    /// NOP — smallest flit object, 1 DW base header, no payload.
1028    Nop,
1029    /// 32-bit Memory Read Request (3 DW base header, no payload despite Length field).
1030    MemRead32,
1031    /// UIO Memory Read — 64-bit address, 4 DW base header (PCIe 6.1+ UIO).
1032    UioMemRead,
1033    /// Message routed to Root Complex, no data.
1034    MsgToRc,
1035    /// 32-bit Memory Write Request (3 DW base header + payload).
1036    MemWrite32,
1037    /// I/O Write Request — requires mandatory OHC-A2.
1038    IoWrite,
1039    /// Type 0 Configuration Write Request — requires mandatory OHC-A3.
1040    CfgWrite0,
1041    /// 32-bit FetchAdd AtomicOp Request.
1042    FetchAdd32,
1043    /// 32-bit Compare-and-Swap AtomicOp Request (2 DW payload).
1044    CompareSwap32,
1045    /// 32-bit Deferrable Memory Write Request.
1046    DeferrableMemWrite32,
1047    /// UIO Memory Write — 64-bit address, 4 DW base header (PCIe 6.1+ UIO).
1048    UioMemWrite,
1049    /// Message with Data routed to Root Complex.
1050    MsgDToRc,
1051    /// Local TLP Prefix token (1 DW base header).
1052    LocalTlpPrefix,
1053}
1054
1055impl FlitTlpType {
1056    /// Base header size in DW, **not** counting OHC extension words.
1057    ///
1058    /// - NOP and LocalTlpPrefix: 1 DW
1059    /// - UIO types (64-bit address): 4 DW
1060    /// - All other types: 3 DW
1061    pub fn base_header_dw(&self) -> u8 {
1062        match self {
1063            FlitTlpType::Nop | FlitTlpType::LocalTlpPrefix => 1,
1064            FlitTlpType::UioMemRead | FlitTlpType::UioMemWrite => 4,
1065            _ => 3,
1066        }
1067    }
1068
1069    /// Returns `true` for read requests that carry **no payload** even when
1070    /// the `Length` field is non-zero.
1071    pub fn is_read_request(&self) -> bool {
1072        matches!(self, FlitTlpType::MemRead32 | FlitTlpType::UioMemRead)
1073    }
1074
1075    /// Returns `true` for TLP types that carry a data payload in the wire packet.
1076    ///
1077    /// When `false`, `total_bytes()` ignores the `Length` field and contributes
1078    /// zero payload bytes regardless of its value. This covers:
1079    /// - Read requests (payload is in the completion, not the request)
1080    /// - NOP and Local TLP Prefix (management objects with no data)
1081    /// - Message-without-data variants (`MsgToRc`)
1082    pub fn has_data_payload(&self) -> bool {
1083        matches!(
1084            self,
1085            FlitTlpType::MemWrite32
1086                | FlitTlpType::UioMemWrite
1087                | FlitTlpType::IoWrite
1088                | FlitTlpType::CfgWrite0
1089                | FlitTlpType::FetchAdd32
1090                | FlitTlpType::CompareSwap32
1091                | FlitTlpType::DeferrableMemWrite32
1092                | FlitTlpType::MsgDToRc
1093        )
1094    }
1095}
1096
1097impl Display for FlitTlpType {
1098    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1099        let name = match self {
1100            FlitTlpType::Nop => "NOP",
1101            FlitTlpType::MemRead32 => "Memory Read (32-bit)",
1102            FlitTlpType::UioMemRead => "UIO Memory Read (64-bit)",
1103            FlitTlpType::MsgToRc => "Message routed to RC",
1104            FlitTlpType::MemWrite32 => "Memory Write (32-bit)",
1105            FlitTlpType::IoWrite => "I/O Write",
1106            FlitTlpType::CfgWrite0 => "Config Type 0 Write",
1107            FlitTlpType::FetchAdd32 => "FetchAdd AtomicOp (32-bit)",
1108            FlitTlpType::CompareSwap32 => "CompareSwap AtomicOp (32-bit)",
1109            FlitTlpType::DeferrableMemWrite32 => "Deferrable Memory Write (32-bit)",
1110            FlitTlpType::UioMemWrite => "UIO Memory Write (64-bit)",
1111            FlitTlpType::MsgDToRc => "Message with Data routed to RC",
1112            FlitTlpType::LocalTlpPrefix => "Local TLP Prefix",
1113        };
1114        write!(f, "{name}")
1115    }
1116}
1117
1118impl TryFrom<u8> for FlitTlpType {
1119    type Error = TlpError;
1120
1121    fn try_from(v: u8) -> Result<Self, Self::Error> {
1122        match v {
1123            0x00 => Ok(FlitTlpType::Nop),
1124            0x03 => Ok(FlitTlpType::MemRead32),
1125            0x22 => Ok(FlitTlpType::UioMemRead),
1126            0x30 => Ok(FlitTlpType::MsgToRc),
1127            0x40 => Ok(FlitTlpType::MemWrite32),
1128            0x42 => Ok(FlitTlpType::IoWrite),
1129            0x44 => Ok(FlitTlpType::CfgWrite0),
1130            0x4C => Ok(FlitTlpType::FetchAdd32),
1131            0x4E => Ok(FlitTlpType::CompareSwap32),
1132            0x5B => Ok(FlitTlpType::DeferrableMemWrite32),
1133            0x61 => Ok(FlitTlpType::UioMemWrite),
1134            0x70 => Ok(FlitTlpType::MsgDToRc),
1135            0x8D => Ok(FlitTlpType::LocalTlpPrefix),
1136            _ => Err(TlpError::InvalidType),
1137        }
1138    }
1139}
1140
1141/// Parsed representation of a flit-mode DW0 (first 4 bytes of a flit TLP).
1142///
1143/// Flit-mode DW0 layout:
1144///
1145/// ```text
1146/// Byte 0: Type[7:0]            — flat 8-bit type code
1147/// Byte 1: TC[2:0] | OHC[4:0]  — traffic class + OHC presence bitmap
1148/// Byte 2: TS[2:0] | Attr[2:0] | Length[9:8]
1149/// Byte 3: Length[7:0]
1150/// ```
1151///
1152/// Use [`FlitDW0::from_dw0`] to parse from a byte slice.
1153#[derive(Debug, Clone, Copy, PartialEq)]
1154#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1155pub struct FlitDW0 {
1156    /// Decoded TLP type.
1157    pub tlp_type: FlitTlpType,
1158    /// Traffic Class (bits `[2:0]` of byte 1).
1159    pub tc: u8,
1160    /// OHC presence bitmap (bits `[4:0]` of byte 1).
1161    /// Each set bit indicates one Optional Header Content word appended
1162    /// after the base header. Use [`FlitDW0::ohc_count`] for the DW count.
1163    pub ohc: u8,
1164    /// Transaction Steering (bits `[7:5]` of byte 2).
1165    pub ts: u8,
1166    /// Attributes (bits `[4:2]` of byte 2).
1167    pub attr: u8,
1168    /// Payload length in DW. A value of `0` encodes 1024 DW.
1169    pub length: u16,
1170}
1171
1172impl FlitDW0 {
1173    /// Parse the flit-mode DW0 from the first 4 bytes of a byte slice.
1174    ///
1175    /// Returns `Err(TlpError::InvalidLength)` if `b.len() < 4`.
1176    /// Returns `Err(TlpError::InvalidType)` if the type code is unknown.
1177    pub fn from_dw0(b: &[u8]) -> Result<Self, TlpError> {
1178        if b.len() < 4 {
1179            return Err(TlpError::InvalidLength);
1180        }
1181        let tlp_type = FlitTlpType::try_from(b[0])?;
1182        let tc = (b[1] >> 5) & 0x07;
1183        let ohc = b[1] & 0x1F;
1184        let ts = (b[2] >> 5) & 0x07;
1185        let attr = (b[2] >> 2) & 0x07;
1186        let length = (((b[2] & 0x03) as u16) << 8) | (b[3] as u16);
1187        Ok(FlitDW0 {
1188            tlp_type,
1189            tc,
1190            ohc,
1191            ts,
1192            attr,
1193            length,
1194        })
1195    }
1196
1197    /// Number of OHC extension words present — popcount of [`FlitDW0::ohc`].
1198    pub fn ohc_count(&self) -> u8 {
1199        self.ohc.count_ones() as u8
1200    }
1201
1202    /// Total TLP size in bytes:
1203    /// `(base_header_dw + ohc_count) × 4 + payload_bytes`
1204    ///
1205    /// Per PCIe spec a `length` value of `0` encodes **1024 DW** (4096 bytes),
1206    /// but only for types that actually carry a data payload (see [`FlitTlpType::has_data_payload`]).
1207    /// Types that never carry payload (read requests, NOP, LocalTlpPrefix, MsgToRc)
1208    /// always contribute zero payload bytes.
1209    pub fn total_bytes(&self) -> usize {
1210        let header_bytes =
1211            (self.tlp_type.base_header_dw() as usize + self.ohc_count() as usize) * 4;
1212        let payload_bytes = if !self.tlp_type.has_data_payload() {
1213            0
1214        } else {
1215            let dw_count = if self.length == 0 {
1216                1024
1217            } else {
1218                self.length as usize
1219            };
1220            dw_count * 4
1221        };
1222        header_bytes + payload_bytes
1223    }
1224}
1225
1226/// Parsed OHC-A word — the byte layout is shared by OHC-A1, OHC-A2, and OHC-A3.
1227///
1228/// OHC-A word byte layout (4 bytes, on-wire order):
1229///
1230/// ```text
1231/// Byte 0: flags[7:4] | PASID[19:16]
1232/// Byte 1: PASID[15:8]
1233/// Byte 2: PASID[7:0]
1234/// Byte 3: ldwbe[7:4] | fdwbe[3:0]
1235/// ```
1236///
1237/// Use [`FlitOhcA::from_bytes`] to parse from a byte slice that starts at the
1238/// first byte of the OHC-A word (i.e. at offset `base_header_dw * 4` in the TLP).
1239#[derive(Debug, Clone, Copy, PartialEq)]
1240#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1241pub struct FlitOhcA {
1242    /// 20-bit PASID value extracted from bytes 0–2.
1243    pub pasid: u32,
1244    /// First DW byte enables (bits `[3:0]` of byte 3).
1245    pub fdwbe: u8,
1246    /// Last DW byte enables (bits `[7:4]` of byte 3).
1247    pub ldwbe: u8,
1248}
1249
1250impl FlitOhcA {
1251    /// Parse one OHC-A word from the first 4 bytes of `b`.
1252    ///
1253    /// Returns `Err(TlpError::InvalidLength)` if `b.len() < 4`.
1254    pub fn from_bytes(b: &[u8]) -> Result<Self, TlpError> {
1255        if b.len() < 4 {
1256            return Err(TlpError::InvalidLength);
1257        }
1258        let pasid = ((b[0] as u32 & 0x0F) << 16) | ((b[1] as u32) << 8) | (b[2] as u32);
1259        let fdwbe = b[3] & 0x0F;
1260        let ldwbe = (b[3] >> 4) & 0x0F;
1261        Ok(FlitOhcA {
1262            pasid,
1263            fdwbe,
1264            ldwbe,
1265        })
1266    }
1267}
1268
1269impl FlitDW0 {
1270    /// Validate mandatory OHC rules for this TLP type.
1271    ///
1272    /// Some flit-mode TLP types **require** an OHC word to be present:
1273    /// - `IoWrite` requires OHC-A2 (bit 0 of the OHC bitmap must be set)
1274    /// - `CfgWrite0` requires OHC-A3 (bit 0 of the OHC bitmap must be set)
1275    ///
1276    /// Returns `Err(TlpError::MissingMandatoryOhc)` if the rule is violated.
1277    pub fn validate_mandatory_ohc(&self) -> Result<(), TlpError> {
1278        match self.tlp_type {
1279            FlitTlpType::IoWrite | FlitTlpType::CfgWrite0 => {
1280                if self.ohc & 0x01 == 0 {
1281                    return Err(TlpError::MissingMandatoryOhc);
1282                }
1283                Ok(())
1284            }
1285            _ => Ok(()),
1286        }
1287    }
1288}
1289
1290/// Iterator over a packed stream of flit-mode TLPs.
1291///
1292/// Walks a byte slice containing back-to-back flit TLPs and yields
1293/// `Ok((offset, FlitTlpType, total_size))` for each one.
1294///
1295/// Returns `Some(Err(TlpError::InvalidLength))` if a TLP extends beyond
1296/// the end of the slice (truncated payload). After the first error,
1297/// subsequent calls to `next()` return `None`.
1298///
1299/// # Examples
1300///
1301/// ```
1302/// use rtlp_lib::{FlitStreamWalker, FlitTlpType};
1303///
1304/// let nop = [0x00u8, 0x00, 0x00, 0x00];
1305/// let (offset, typ, size) = FlitStreamWalker::new(&nop).next().unwrap().unwrap();
1306/// assert_eq!(offset, 0);
1307/// assert_eq!(typ, FlitTlpType::Nop);
1308/// assert_eq!(size, 4);
1309/// ```
1310pub struct FlitStreamWalker<'a> {
1311    data: &'a [u8],
1312    pos: usize,
1313    errored: bool,
1314}
1315
1316impl<'a> FlitStreamWalker<'a> {
1317    /// Create a new walker over a packed flit TLP byte stream.
1318    pub fn new(data: &'a [u8]) -> Self {
1319        FlitStreamWalker {
1320            data,
1321            pos: 0,
1322            errored: false,
1323        }
1324    }
1325}
1326
1327impl Iterator for FlitStreamWalker<'_> {
1328    type Item = Result<(usize, FlitTlpType, usize), TlpError>;
1329
1330    fn next(&mut self) -> Option<Self::Item> {
1331        if self.errored || self.pos >= self.data.len() {
1332            return None;
1333        }
1334        let offset = self.pos;
1335        let dw0 = match FlitDW0::from_dw0(&self.data[self.pos..]) {
1336            Ok(d) => d,
1337            Err(e) => {
1338                self.errored = true;
1339                return Some(Err(e));
1340            }
1341        };
1342        let total = dw0.total_bytes();
1343        if self.pos + total > self.data.len() {
1344            self.errored = true;
1345            return Some(Err(TlpError::InvalidLength));
1346        }
1347        self.pos += total;
1348        Some(Ok((offset, dw0.tlp_type, total)))
1349    }
1350}
1351
1352// ============================================================================
1353// End of Flit Mode types
1354// ============================================================================
1355
1356/// TLP Packet Header
1357/// Contains bytes for Packet header and informations about TLP type
1358pub struct TlpPacketHeader {
1359    header: TlpHeader<Vec<u8>>,
1360}
1361
1362impl fmt::Debug for TlpPacketHeader {
1363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1364        f.debug_struct("TlpPacketHeader")
1365            .field("format", &self.get_format())
1366            .field("type", &self.get_type())
1367            .field("tc", &self.get_tc())
1368            .field("t9", &self.get_t9())
1369            .field("t8", &self.get_t8())
1370            .field("attr_b2", &self.get_attr_b2())
1371            .field("ln", &self.get_ln())
1372            .field("th", &self.get_th())
1373            .field("td", &self.get_td())
1374            .field("ep", &self.get_ep())
1375            .field("attr", &self.get_attr())
1376            .field("at", &self.get_at())
1377            .field("length", &self.get_length())
1378            .finish()
1379    }
1380}
1381
1382impl TlpPacketHeader {
1383    /// Create a new `TlpPacketHeader` from raw bytes and the specified framing mode.
1384    ///
1385    /// Use `TlpMode::NonFlit` for PCIe 1.0–5.0 standard TLP framing.
1386    /// `TlpMode::Flit` is the flit TLP framing mode (introduced in PCIe 6.0 Base Spec). Currently
1387    /// returns `Err(TlpError::NotImplemented)`.
1388    ///
1389    /// # Errors
1390    ///
1391    /// - [`TlpError::InvalidLength`] if `bytes.len() < 4`.
1392    /// - [`TlpError::NotImplemented`] if `mode` is `TlpMode::Flit`.
1393    pub fn new(bytes: Vec<u8>, mode: TlpMode) -> Result<TlpPacketHeader, TlpError> {
1394        match mode {
1395            TlpMode::NonFlit => Self::new_non_flit(bytes),
1396            TlpMode::Flit => Err(TlpError::NotImplemented),
1397        }
1398    }
1399
1400    fn new_non_flit(bytes: Vec<u8>) -> Result<TlpPacketHeader, TlpError> {
1401        if bytes.len() < 4 {
1402            return Err(TlpError::InvalidLength);
1403        }
1404        let mut dw0 = vec![0; 4];
1405        dw0[..4].clone_from_slice(&bytes[0..4]);
1406
1407        Ok(TlpPacketHeader {
1408            header: TlpHeader(dw0),
1409        })
1410    }
1411
1412    /// Decode and return the TLP type from the DW0 header fields.
1413    ///
1414    /// # Errors
1415    ///
1416    /// - [`TlpError::InvalidFormat`] if the 3-bit Fmt field is not a known value.
1417    /// - [`TlpError::InvalidType`] if the 5-bit Type field is not a known value.
1418    /// - [`TlpError::UnsupportedCombination`] if Fmt and Type are individually valid
1419    ///   but not a legal pair (e.g. IO Request with 4DW header).
1420    pub fn tlp_type(&self) -> Result<TlpType, TlpError> {
1421        self.header.get_tlp_type()
1422    }
1423
1424    /// Decode and return the TLP type from the DW0 header fields.
1425    ///
1426    /// # Deprecation
1427    ///
1428    /// Prefer [`TlpPacketHeader::tlp_type`] which follows Rust naming conventions.
1429    #[deprecated(since = "0.5.0", note = "use tlp_type() instead")]
1430    pub fn get_tlp_type(&self) -> Result<TlpType, TlpError> {
1431        self.tlp_type()
1432    }
1433
1434    /// Raw Traffic Class field from DW0 (`bits[11:9]`).
1435    pub fn get_tc(&self) -> u32 {
1436        self.header.get_tc()
1437    }
1438
1439    /// Raw 3-bit Format field from DW0 (`bits[2:0]` of byte 0 in MSB0 layout).
1440    pub(crate) fn get_format(&self) -> u32 {
1441        self.header.get_format()
1442    }
1443    pub(crate) fn get_type(&self) -> u32 {
1444        self.header.get_type()
1445    }
1446    pub(crate) fn get_t9(&self) -> u32 {
1447        self.header.get_t9()
1448    }
1449    pub(crate) fn get_t8(&self) -> u32 {
1450        self.header.get_t8()
1451    }
1452    pub(crate) fn get_attr_b2(&self) -> u32 {
1453        self.header.get_attr_b2()
1454    }
1455    pub(crate) fn get_ln(&self) -> u32 {
1456        self.header.get_ln()
1457    }
1458    pub(crate) fn get_th(&self) -> u32 {
1459        self.header.get_th()
1460    }
1461    pub(crate) fn get_td(&self) -> u32 {
1462        self.header.get_td()
1463    }
1464    pub(crate) fn get_ep(&self) -> u32 {
1465        self.header.get_ep()
1466    }
1467    pub(crate) fn get_attr(&self) -> u32 {
1468        self.header.get_attr()
1469    }
1470    pub(crate) fn get_at(&self) -> u32 {
1471        self.header.get_at()
1472    }
1473    pub(crate) fn get_length(&self) -> u32 {
1474        self.header.get_length()
1475    }
1476}
1477
1478/// TLP Packet structure is high level abstraction for entire TLP packet
1479/// Contains Header and Data
1480///
1481/// # Examples
1482///
1483/// ```
1484/// use rtlp_lib::TlpPacket;
1485/// use rtlp_lib::TlpFmt;
1486/// use rtlp_lib::TlpType;
1487/// use rtlp_lib::TlpMode;
1488/// use rtlp_lib::new_msg_req;
1489/// use rtlp_lib::new_conf_req;
1490/// use rtlp_lib::new_mem_req;
1491/// use rtlp_lib::new_cmpl_req;
1492///
1493/// // Bytes for full TLP Packet
1494/// //               <------- DW1 -------->  <------- DW2 -------->  <------- DW3 -------->  <------- DW4 -------->
1495/// let bytes = vec![0x00, 0x00, 0x20, 0x01, 0x04, 0x00, 0x00, 0x01, 0x20, 0x01, 0xFF, 0x00, 0xC2, 0x81, 0xFF, 0x10];
1496/// let packet = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
1497///
1498/// let header = packet.header();
1499/// // TLP Type tells us what is this packet
1500/// let tlp_type = header.tlp_type().unwrap();
1501/// let tlp_format = packet.tlp_format().unwrap();
1502/// let requester_id;
1503/// match (tlp_type) {
1504///      TlpType::MemReadReq |
1505///      TlpType::MemReadLockReq |
1506///      TlpType::MemWriteReq |
1507///      TlpType::DeferrableMemWriteReq |
1508///      TlpType::IOReadReq |
1509///      TlpType::IOWriteReq |
1510///      TlpType::FetchAddAtomicOpReq |
1511///      TlpType::SwapAtomicOpReq |
1512///      TlpType::CompareSwapAtomicOpReq => requester_id = new_mem_req(packet.data(), &tlp_format).unwrap().req_id(),
1513///      TlpType::ConfType0ReadReq |
1514///      TlpType::ConfType0WriteReq |
1515///      TlpType::ConfType1ReadReq |
1516///      TlpType::ConfType1WriteReq => requester_id = new_conf_req(packet.data().to_vec()).unwrap().req_id(),
1517///      TlpType::MsgReq |
1518///      TlpType::MsgReqData => requester_id = new_msg_req(packet.data().to_vec()).unwrap().req_id(),
1519///      TlpType::Cpl |
1520///      TlpType::CplData |
1521///      TlpType::CplLocked |
1522///      TlpType::CplDataLocked => requester_id = new_cmpl_req(packet.data().to_vec()).unwrap().req_id(),
1523///      TlpType::LocalTlpPrefix |
1524///      TlpType::EndToEndTlpPrefix => println!("I need to implement TLP Type: {:?}", tlp_type),
1525/// }
1526/// ```
1527pub struct TlpPacket {
1528    header: TlpPacketHeader,
1529    /// Set when the packet was created from `TlpMode::Flit` bytes.
1530    /// `None` for non-flit packets.
1531    flit_dw0: Option<FlitDW0>,
1532    data: Vec<u8>,
1533}
1534
1535impl fmt::Debug for TlpPacket {
1536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1537        f.debug_struct("TlpPacket")
1538            .field("header", &self.header)
1539            .field("flit_dw0", &self.flit_dw0)
1540            .field("data_len", &self.data.len())
1541            .finish()
1542    }
1543}
1544
1545impl TlpPacket {
1546    /// Create a new `TlpPacket` from raw bytes and the specified framing mode.
1547    ///
1548    /// Use `TlpMode::NonFlit` for standard PCIe 1.0–5.0 TLP framing.
1549    /// Use `TlpMode::Flit` for flit-mode TLP framing (introduced in PCIe 6.0 Base Spec).
1550    ///
1551    /// # Errors
1552    ///
1553    /// - [`TlpError::InvalidLength`] if `bytes.len() < 4` or the flit header extends beyond `bytes`.
1554    /// - [`TlpError::InvalidType`] if flit mode and the DW0 type byte is unknown.
1555    /// - [`TlpError::MissingMandatoryOhc`] if flit mode and a mandatory OHC word is absent.
1556    pub fn new(bytes: Vec<u8>, mode: TlpMode) -> Result<TlpPacket, TlpError> {
1557        match mode {
1558            TlpMode::NonFlit => Self::new_non_flit(bytes),
1559            TlpMode::Flit => Self::new_flit(bytes),
1560        }
1561    }
1562
1563    fn new_non_flit(mut bytes: Vec<u8>) -> Result<TlpPacket, TlpError> {
1564        if bytes.len() < 4 {
1565            return Err(TlpError::InvalidLength);
1566        }
1567        let mut header = vec![0; 4];
1568        header.clone_from_slice(&bytes[0..4]);
1569        let data = bytes.drain(4..).collect();
1570        Ok(TlpPacket {
1571            header: TlpPacketHeader::new_non_flit(header)?,
1572            flit_dw0: None,
1573            data,
1574        })
1575    }
1576
1577    fn new_flit(bytes: Vec<u8>) -> Result<TlpPacket, TlpError> {
1578        if bytes.len() < 4 {
1579            return Err(TlpError::InvalidLength);
1580        }
1581        let dw0 = FlitDW0::from_dw0(&bytes)?;
1582        dw0.validate_mandatory_ohc()?;
1583
1584        let hdr_bytes = (dw0.tlp_type.base_header_dw() as usize + dw0.ohc_count() as usize) * 4;
1585        if bytes.len() < hdr_bytes {
1586            return Err(TlpError::InvalidLength);
1587        }
1588        let payload = bytes[hdr_bytes..].to_vec();
1589
1590        // Use a dummy non-flit header; callers should use flit_type() for type queries.
1591        let dummy = TlpPacketHeader::new_non_flit(vec![0u8; 4])?;
1592        Ok(TlpPacket {
1593            header: dummy,
1594            flit_dw0: Some(dw0),
1595            data: payload,
1596        })
1597    }
1598
1599    // -----------------------------------------------------------------------
1600    // Preferred (non-get_*) API
1601    // -----------------------------------------------------------------------
1602
1603    /// Returns a reference to the DW0 packet header.
1604    ///
1605    /// For flit-mode packets the underlying `TlpPacketHeader` holds an all-zero
1606    /// placeholder and its fields are **meaningless**.  Check [`TlpPacket::mode`]
1607    /// first and use [`TlpPacket::flit_type`] for flit-mode packets.
1608    pub fn header(&self) -> &TlpPacketHeader {
1609        &self.header
1610    }
1611
1612    /// Returns the packet payload bytes (everything after the 4-byte DW0 header).
1613    ///
1614    /// For flit-mode read requests this will be empty even when `Length > 0`.
1615    pub fn data(&self) -> &[u8] {
1616        &self.data
1617    }
1618
1619    /// Decode and return the TLP type from the DW0 header fields.
1620    ///
1621    /// For flit-mode packets, use [`TlpPacket::flit_type`] instead.
1622    ///
1623    /// # Errors
1624    ///
1625    /// - [`TlpError::NotImplemented`] if this packet was created with `TlpMode::Flit`.
1626    /// - [`TlpError::InvalidFormat`] if the 3-bit Fmt field is unknown.
1627    /// - [`TlpError::InvalidType`] if the 5-bit Type field is unknown.
1628    /// - [`TlpError::UnsupportedCombination`] if the Fmt/Type pair is not legal.
1629    pub fn tlp_type(&self) -> Result<TlpType, TlpError> {
1630        if self.flit_dw0.is_some() {
1631            return Err(TlpError::NotImplemented);
1632        }
1633        self.header.tlp_type()
1634    }
1635
1636    /// Decode and return the TLP format (3DW/4DW, with/without data) from DW0.
1637    ///
1638    /// For flit-mode packets, use [`TlpPacket::flit_type`] instead.
1639    ///
1640    /// # Errors
1641    ///
1642    /// - [`TlpError::NotImplemented`] if this packet was created with `TlpMode::Flit`.
1643    /// - [`TlpError::InvalidFormat`] if the 3-bit Fmt field is not a known value.
1644    pub fn tlp_format(&self) -> Result<TlpFmt, TlpError> {
1645        if self.flit_dw0.is_some() {
1646            return Err(TlpError::NotImplemented);
1647        }
1648        TlpFmt::try_from(self.header.get_format())
1649    }
1650
1651    /// Returns the flit-mode TLP type when the packet was created from
1652    /// `TlpMode::Flit` bytes, or `None` for non-flit packets.
1653    pub fn flit_type(&self) -> Option<FlitTlpType> {
1654        self.flit_dw0.map(|d| d.tlp_type)
1655    }
1656
1657    /// Returns the framing mode this packet was created with.
1658    ///
1659    /// Use this to dispatch cleanly between the flit-mode and non-flit-mode
1660    /// method sets.  Relying on `flit_type().is_some()` as an indirect proxy
1661    /// works but obscures intent; `mode()` makes the check self-documenting:
1662    ///
1663    /// ```
1664    /// use rtlp_lib::{TlpPacket, TlpMode};
1665    ///
1666    /// # let bytes = vec![0x00u8; 4];
1667    /// let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
1668    /// match pkt.mode() {
1669    ///     TlpMode::Flit    => { /* use pkt.flit_type() */ }
1670    ///     TlpMode::NonFlit => { /* use pkt.tlp_type(), pkt.tlp_format() */ }
1671    ///     // TlpMode is #[non_exhaustive] — wildcard required in external code
1672    ///     _ => {}
1673    /// }
1674    /// ```
1675    pub fn mode(&self) -> TlpMode {
1676        if self.flit_dw0.is_some() {
1677            TlpMode::Flit
1678        } else {
1679            TlpMode::NonFlit
1680        }
1681    }
1682
1683    // -----------------------------------------------------------------------
1684    // Deprecated aliases — kept for backward compatibility
1685    // -----------------------------------------------------------------------
1686
1687    /// Returns a reference to the DW0 packet header.
1688    ///
1689    /// # Deprecation
1690    ///
1691    /// Prefer [`TlpPacket::header`] which follows Rust naming conventions.
1692    #[deprecated(since = "0.5.0", note = "use header() instead")]
1693    pub fn get_header(&self) -> &TlpPacketHeader {
1694        self.header()
1695    }
1696
1697    /// Returns the packet payload bytes as an owned `Vec<u8>`.
1698    ///
1699    /// # Deprecation
1700    ///
1701    /// Prefer [`TlpPacket::data`] which returns `&[u8]` without allocating.
1702    #[deprecated(since = "0.5.0", note = "use data() which returns &[u8] instead")]
1703    pub fn get_data(&self) -> Vec<u8> {
1704        self.data.clone()
1705    }
1706
1707    /// Decode and return the TLP type from the DW0 header fields.
1708    ///
1709    /// # Deprecation
1710    ///
1711    /// Prefer [`TlpPacket::tlp_type`] which follows Rust naming conventions.
1712    #[deprecated(since = "0.5.0", note = "use tlp_type() instead")]
1713    pub fn get_tlp_type(&self) -> Result<TlpType, TlpError> {
1714        self.tlp_type()
1715    }
1716
1717    /// Decode and return the TLP format from DW0.
1718    ///
1719    /// # Deprecation
1720    ///
1721    /// Prefer [`TlpPacket::tlp_format`] which follows Rust naming conventions.
1722    #[deprecated(since = "0.5.0", note = "use tlp_format() instead")]
1723    pub fn get_tlp_format(&self) -> Result<TlpFmt, TlpError> {
1724        self.tlp_format()
1725    }
1726
1727    /// Returns the flit-mode TLP type.
1728    ///
1729    /// # Deprecation
1730    ///
1731    /// Prefer [`TlpPacket::flit_type`] which follows Rust naming conventions.
1732    #[deprecated(since = "0.5.0", note = "use flit_type() instead")]
1733    pub fn get_flit_type(&self) -> Option<FlitTlpType> {
1734        self.flit_type()
1735    }
1736}
1737
1738// ============================================================================
1739// Display implementations — Wireshark-style one-line summaries
1740// ============================================================================
1741
1742/// Short mnemonic for a non-flit TLP type + format combination.
1743fn non_flit_short_name(tlp_type: &TlpType, fmt: &TlpFmt) -> &'static str {
1744    match tlp_type {
1745        // Wildcard arms: tlp_type() already validates that MemReadReq only appears with
1746        // NoDataHeader3DW or NoDataHeader4DW, so the `_` arm here only matches
1747        // NoDataHeader3DW in practice. Kept as wildcard for brevity since the valid
1748        // combinations are enforced upstream.
1749        TlpType::MemReadReq => match fmt {
1750            TlpFmt::NoDataHeader4DW => "MRd64",
1751            _ => "MRd32",
1752        },
1753        TlpType::MemReadLockReq => "MRdLk",
1754        // Same as MemReadReq — tlp_type() ensures only WithDataHeader3DW/4DW reach here.
1755        TlpType::MemWriteReq => match fmt {
1756            TlpFmt::WithDataHeader4DW => "MWr64",
1757            _ => "MWr32",
1758        },
1759        TlpType::IOReadReq => "IORd",
1760        TlpType::IOWriteReq => "IOWr",
1761        TlpType::ConfType0ReadReq => "CfgRd0",
1762        TlpType::ConfType0WriteReq => "CfgWr0",
1763        TlpType::ConfType1ReadReq => "CfgRd1",
1764        TlpType::ConfType1WriteReq => "CfgWr1",
1765        TlpType::MsgReq => "Msg",
1766        TlpType::MsgReqData => "MsgD",
1767        TlpType::Cpl => "Cpl",
1768        TlpType::CplData => "CplD",
1769        TlpType::CplLocked => "CplLk",
1770        TlpType::CplDataLocked => "CplDLk",
1771        TlpType::FetchAddAtomicOpReq => "FAdd",
1772        TlpType::SwapAtomicOpReq => "Swap",
1773        TlpType::CompareSwapAtomicOpReq => "CAS",
1774        // Same pattern — tlp_type() enforces valid format combinations for DMWr.
1775        TlpType::DeferrableMemWriteReq => match fmt {
1776            TlpFmt::WithDataHeader4DW => "DMWr64",
1777            _ => "DMWr32",
1778        },
1779        TlpType::LocalTlpPrefix => "LPfx",
1780        TlpType::EndToEndTlpPrefix => "E2EPfx",
1781    }
1782}
1783
1784/// Short mnemonic for a flit-mode TLP type.
1785fn flit_short_name(flit_type: &FlitTlpType) -> &'static str {
1786    match flit_type {
1787        FlitTlpType::Nop => "NOP",
1788        FlitTlpType::MemRead32 => "MRd32",
1789        FlitTlpType::UioMemRead => "UMRd64",
1790        FlitTlpType::MsgToRc => "Msg",
1791        FlitTlpType::MemWrite32 => "MWr32",
1792        FlitTlpType::IoWrite => "IOWr",
1793        FlitTlpType::CfgWrite0 => "CfgWr0",
1794        FlitTlpType::FetchAdd32 => "FAdd32",
1795        FlitTlpType::CompareSwap32 => "CAS32",
1796        FlitTlpType::DeferrableMemWrite32 => "DMWr32",
1797        FlitTlpType::UioMemWrite => "UMWr64",
1798        FlitTlpType::MsgDToRc => "MsgD",
1799        FlitTlpType::LocalTlpPrefix => "LPfx",
1800    }
1801}
1802
1803impl fmt::Display for TlpPacketHeader {
1804    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1805        match self.tlp_type() {
1806            Ok(t) => {
1807                // `tlp_type()` has already validated the format field, so this conversion
1808                // is expected to succeed. A failure here would indicate a violated
1809                // internal invariant between `tlp_type()` and `TlpFmt::try_from`.
1810                // Use a graceful fallback instead of expect() — Display must never panic.
1811                let fm = match TlpFmt::try_from(self.get_format()) {
1812                    Ok(fm) => fm,
1813                    Err(_) => {
1814                        return write!(
1815                            f,
1816                            "??? fmt={:#05b} type={:#07b} len={}",
1817                            self.get_format(),
1818                            self.get_type(),
1819                            self.get_length()
1820                        );
1821                    }
1822                };
1823                let short = non_flit_short_name(&t, &fm);
1824                write!(
1825                    f,
1826                    "{short} len={} tc={} td={} ep={}",
1827                    self.get_length(),
1828                    self.get_tc(),
1829                    self.get_td(),
1830                    self.get_ep()
1831                )
1832            }
1833            Err(_) => write!(
1834                f,
1835                "??? fmt={:#05b} type={:#07b} len={}",
1836                self.get_format(),
1837                self.get_type(),
1838                self.get_length()
1839            ),
1840        }
1841    }
1842}
1843
1844impl fmt::Display for TlpPacket {
1845    /// Wireshark-style one-line summary.
1846    ///
1847    /// # Non-flit examples
1848    /// ```text
1849    /// MRd32 len=1 req=0400 tag=20 addr=F620000C
1850    /// MWr64 len=4 req=BEEF tag=A5 addr=100000000
1851    /// CplD len=1 cpl=2001 req=0400 tag=AB stat=0 bc=252
1852    /// CfgRd0 len=1 req=0100 tag=01 bus=02 dev=03 fn=0 reg=10
1853    /// Msg len=0 req=ABCD tag=01 code=7F
1854    /// FAdd len=1 req=DEAD tag=42 addr=C0010004
1855    /// ```
1856    ///
1857    /// # Flit examples
1858    /// ```text
1859    /// Flit:MWr32 len=4 tc=0 ohc=1
1860    /// Flit:NOP
1861    /// Flit:CfgWr0 len=1 tc=0 ohc=1
1862    /// ```
1863    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1864        match self.mode() {
1865            TlpMode::Flit => {
1866                if let Some(dw0) = &self.flit_dw0 {
1867                    let short = flit_short_name(&dw0.tlp_type);
1868                    write!(f, "Flit:{short}")?;
1869
1870                    // NOP and LocalTlpPrefix are minimal — just the type name
1871                    if matches!(dw0.tlp_type, FlitTlpType::Nop | FlitTlpType::LocalTlpPrefix) {
1872                        return Ok(());
1873                    }
1874
1875                    write!(f, " len={} tc={}", dw0.length, dw0.tc)?;
1876
1877                    let ohc = dw0.ohc_count();
1878                    if ohc > 0 {
1879                        write!(f, " ohc={}", ohc)?;
1880                    }
1881                    if dw0.attr != 0 {
1882                        write!(f, " attr={}", dw0.attr)?;
1883                    }
1884                    if dw0.ts != 0 {
1885                        write!(f, " ts={}", dw0.ts)?;
1886                    }
1887
1888                    // Note: OHC-A bytes are consumed by the header parser and not
1889                    // available in data(). OHC presence is shown via ohc= count above.
1890
1891                    Ok(())
1892                } else {
1893                    // Defensive guard: flit_dw0 is always Some for flit-mode packets
1894                    // constructed through the public API (new_flit_packet sets it).
1895                    // This branch would only trigger if internal construction is
1896                    // bypassed or a future refactor breaks the invariant.
1897                    write!(f, "Flit:???")
1898                }
1899            }
1900            TlpMode::NonFlit => {
1901                let tlp_type = match self.tlp_type() {
1902                    Ok(t) => t,
1903                    Err(_) => return write!(f, "??? data={}B", self.data.len()),
1904                };
1905                let fmt = match self.tlp_format() {
1906                    Ok(fm) => fm,
1907                    Err(_) => return write!(f, "{tlp_type:?} data={}B", self.data.len()),
1908                };
1909
1910                let short_name = non_flit_short_name(&tlp_type, &fmt);
1911                let length = self.header.get_length();
1912                let data = self.data();
1913
1914                match tlp_type {
1915                    // Memory requests — show req_id, tag, address
1916                    TlpType::MemReadReq
1917                    | TlpType::MemReadLockReq
1918                    | TlpType::MemWriteReq
1919                    | TlpType::DeferrableMemWriteReq
1920                    | TlpType::IOReadReq
1921                    | TlpType::IOWriteReq => {
1922                        let header_len = core::cmp::min(data.len(), 12);
1923                        if let Ok(mr) = new_mem_req(data[..header_len].to_vec(), &fmt) {
1924                            write!(
1925                                f,
1926                                "{short_name} len={length} req={:04X} tag={:02X} addr={:X}",
1927                                mr.req_id(),
1928                                mr.tag(),
1929                                mr.address()
1930                            )
1931                        } else {
1932                            write!(f, "{short_name} len={length}")
1933                        }
1934                    }
1935                    // Config requests — show req_id, tag, bus/dev/fn/reg
1936                    TlpType::ConfType0ReadReq
1937                    | TlpType::ConfType0WriteReq
1938                    | TlpType::ConfType1ReadReq
1939                    | TlpType::ConfType1WriteReq => {
1940                        let header_bytes = if data.len() >= 8 { &data[..8] } else { data };
1941                        if let Ok(cr) = new_conf_req(header_bytes.to_vec()) {
1942                            write!(
1943                                f,
1944                                "{short_name} len={length} req={:04X} tag={:02X} bus={:02X} dev={:02X} fn={} reg={:02X}",
1945                                cr.req_id(),
1946                                cr.tag(),
1947                                cr.bus_nr(),
1948                                cr.dev_nr(),
1949                                cr.func_nr(),
1950                                cr.reg_nr()
1951                            )
1952                        } else {
1953                            write!(f, "{short_name} len={length}")
1954                        }
1955                    }
1956                    // Completions — show completer, requester, status, byte count
1957                    TlpType::Cpl
1958                    | TlpType::CplData
1959                    | TlpType::CplLocked
1960                    | TlpType::CplDataLocked => {
1961                        let header_bytes = if data.len() >= 12 { &data[..12] } else { data };
1962                        if let Ok(cpl) = new_cmpl_req(header_bytes.to_vec()) {
1963                            write!(
1964                                f,
1965                                "{short_name} len={length} cpl={:04X} req={:04X} tag={:02X} stat={} bc={}",
1966                                cpl.cmpl_id(),
1967                                cpl.req_id(),
1968                                cpl.tag(),
1969                                cpl.cmpl_stat(),
1970                                cpl.byte_cnt()
1971                            )
1972                        } else {
1973                            write!(f, "{short_name} len={length}")
1974                        }
1975                    }
1976                    // Messages — show req_id, tag, message code
1977                    TlpType::MsgReq | TlpType::MsgReqData => {
1978                        let header_bytes = if data.len() > 12 { &data[..12] } else { data };
1979                        if let Ok(msg) = new_msg_req(header_bytes.to_vec()) {
1980                            write!(
1981                                f,
1982                                "{short_name} len={length} req={:04X} tag={:02X} code={:02X}",
1983                                msg.req_id(),
1984                                msg.tag(),
1985                                msg.msg_code()
1986                            )
1987                        } else {
1988                            write!(f, "{short_name} len={length}")
1989                        }
1990                    }
1991                    // Atomics — show req_id, tag, address
1992                    TlpType::FetchAddAtomicOpReq
1993                    | TlpType::SwapAtomicOpReq
1994                    | TlpType::CompareSwapAtomicOpReq => {
1995                        if let Ok(ar) = new_atomic_req(self) {
1996                            write!(
1997                                f,
1998                                "{short_name} len={length} req={:04X} tag={:02X} addr={:X}",
1999                                ar.req_id(),
2000                                ar.tag(),
2001                                ar.address()
2002                            )
2003                        } else {
2004                            write!(f, "{short_name} len={length}")
2005                        }
2006                    }
2007                    // Prefixes — just the type
2008                    TlpType::LocalTlpPrefix | TlpType::EndToEndTlpPrefix => {
2009                        write!(f, "{short_name} len={length}")
2010                    }
2011                }
2012            }
2013            // TlpMode is #[non_exhaustive]; future variants handled here
2014            #[allow(unreachable_patterns)]
2015            _ => write!(f, "???"),
2016        }
2017    }
2018}
2019
2020#[cfg(test)]
2021mod tests {
2022    use super::*;
2023
2024    #[test]
2025    fn tlp_header_type() {
2026        // Empty packet is still MemREAD: FMT '000' Type '0 0000' Length 0
2027        let memread = TlpHeader([0x0, 0x0, 0x0, 0x0]);
2028        assert_eq!(memread.get_tlp_type().unwrap(), TlpType::MemReadReq);
2029
2030        // MemRead32 FMT '000' Type '0 0000'
2031        let memread32 = TlpHeader([0x00, 0x00, 0x20, 0x01]);
2032        assert_eq!(memread32.get_tlp_type().unwrap(), TlpType::MemReadReq);
2033
2034        // MemWrite32 FMT '010' Type '0 0000'
2035        let memwrite32 = TlpHeader([0x40, 0x00, 0x00, 0x01]);
2036        assert_eq!(memwrite32.get_tlp_type().unwrap(), TlpType::MemWriteReq);
2037
2038        // CPL without Data: FMT '000' Type '0 1010'
2039        let cpl_no_data = TlpHeader([0x0a, 0x00, 0x10, 0x00]);
2040        assert_eq!(cpl_no_data.get_tlp_type().unwrap(), TlpType::Cpl);
2041
2042        // CPL with Data: FMT '010' Type '0 1010'
2043        let cpl_with_data = TlpHeader([0x4a, 0x00, 0x20, 0x40]);
2044        assert_eq!(cpl_with_data.get_tlp_type().unwrap(), TlpType::CplData);
2045
2046        // MemRead 4DW: FMT: '001' Type '0 0000'
2047        let memread_4dw = TlpHeader([0x20, 0x00, 0x20, 0x40]);
2048        assert_eq!(memread_4dw.get_tlp_type().unwrap(), TlpType::MemReadReq);
2049
2050        // Config Type 0 Read request: FMT: '000' Type '0 0100'
2051        let conf_t0_read = TlpHeader([0x04, 0x00, 0x00, 0x01]);
2052        assert_eq!(
2053            conf_t0_read.get_tlp_type().unwrap(),
2054            TlpType::ConfType0ReadReq
2055        );
2056
2057        // Config Type 0 Write request: FMT: '010' Type '0 0100'
2058        let conf_t0_write = TlpHeader([0x44, 0x00, 0x00, 0x01]);
2059        assert_eq!(
2060            conf_t0_write.get_tlp_type().unwrap(),
2061            TlpType::ConfType0WriteReq
2062        );
2063
2064        // Config Type 1 Read request: FMT: '000' Type '0 0101'
2065        let conf_t1_read = TlpHeader([0x05, 0x88, 0x80, 0x01]);
2066        assert_eq!(
2067            conf_t1_read.get_tlp_type().unwrap(),
2068            TlpType::ConfType1ReadReq
2069        );
2070
2071        // Config Type 1 Write request: FMT: '010' Type '0 0101'
2072        let conf_t1_write = TlpHeader([0x45, 0x88, 0x80, 0x01]);
2073        assert_eq!(
2074            conf_t1_write.get_tlp_type().unwrap(),
2075            TlpType::ConfType1WriteReq
2076        );
2077
2078        // HeaderLog: 04000001 0000220f 01070000 af36fc70
2079        // HeaderLog: 60009001 4000000f 00000280 4047605c
2080        let memwrite64 = TlpHeader([0x60, 0x00, 0x90, 0x01]);
2081        assert_eq!(memwrite64.get_tlp_type().unwrap(), TlpType::MemWriteReq);
2082    }
2083
2084    #[test]
2085    fn tlp_header_works_all_zeros() {
2086        let bits_locations = TlpHeader([0x0, 0x0, 0x0, 0x0]);
2087
2088        assert_eq!(bits_locations.get_format(), 0);
2089        assert_eq!(bits_locations.get_type(), 0);
2090        assert_eq!(bits_locations.get_t9(), 0);
2091        assert_eq!(bits_locations.get_tc(), 0);
2092        assert_eq!(bits_locations.get_t8(), 0);
2093        assert_eq!(bits_locations.get_attr_b2(), 0);
2094        assert_eq!(bits_locations.get_ln(), 0);
2095        assert_eq!(bits_locations.get_th(), 0);
2096        assert_eq!(bits_locations.get_td(), 0);
2097        assert_eq!(bits_locations.get_ep(), 0);
2098        assert_eq!(bits_locations.get_attr(), 0);
2099        assert_eq!(bits_locations.get_at(), 0);
2100        assert_eq!(bits_locations.get_length(), 0);
2101    }
2102
2103    #[test]
2104    fn tlp_header_works_all_ones() {
2105        let bits_locations = TlpHeader([0xff, 0xff, 0xff, 0xff]);
2106
2107        assert_eq!(bits_locations.get_format(), 0x7);
2108        assert_eq!(bits_locations.get_type(), 0x1f);
2109        assert_eq!(bits_locations.get_t9(), 0x1);
2110        assert_eq!(bits_locations.get_tc(), 0x7);
2111        assert_eq!(bits_locations.get_t8(), 0x1);
2112        assert_eq!(bits_locations.get_attr_b2(), 0x1);
2113        assert_eq!(bits_locations.get_ln(), 0x1);
2114        assert_eq!(bits_locations.get_th(), 0x1);
2115        assert_eq!(bits_locations.get_td(), 0x1);
2116        assert_eq!(bits_locations.get_ep(), 0x1);
2117        assert_eq!(bits_locations.get_attr(), 0x3);
2118        assert_eq!(bits_locations.get_at(), 0x3);
2119        assert_eq!(bits_locations.get_length(), 0x3ff);
2120    }
2121
2122    #[test]
2123    fn test_invalid_format_error() {
2124        // Format field with invalid value (e.g., 0b101 = 5)
2125        // byte0 layout: bits[7:5] = fmt, bits[4:0] = type
2126        // Fmt=0b101 → byte0 = 0b1010_0000 = 0xA0
2127        let invalid_fmt_101 = TlpHeader([0xa0, 0x00, 0x00, 0x01]);
2128        assert_eq!(
2129            invalid_fmt_101.get_tlp_type().unwrap_err(),
2130            TlpError::InvalidFormat
2131        );
2132
2133        // Fmt=0b110 → byte0 = 0b1100_0000 = 0xC0
2134        let invalid_fmt_110 = TlpHeader([0xc0, 0x00, 0x00, 0x01]);
2135        assert_eq!(
2136            invalid_fmt_110.get_tlp_type().unwrap_err(),
2137            TlpError::InvalidFormat
2138        );
2139
2140        // Fmt=0b111 → byte0 = 0b1110_0000 = 0xE0
2141        let invalid_fmt_111 = TlpHeader([0xe0, 0x00, 0x00, 0x01]);
2142        assert_eq!(
2143            invalid_fmt_111.get_tlp_type().unwrap_err(),
2144            TlpError::InvalidFormat
2145        );
2146    }
2147
2148    #[test]
2149    fn test_invalid_type_error() {
2150        // Type field with invalid encoding (e.g., 0b01111 = 15)
2151        let invalid_type = TlpHeader([0x0f, 0x00, 0x00, 0x01]); // FMT='000' Type='01111'
2152        let result = invalid_type.get_tlp_type();
2153        assert!(result.is_err());
2154        assert_eq!(result.unwrap_err(), TlpError::InvalidType);
2155    }
2156
2157    #[test]
2158    fn test_unsupported_combination_error() {
2159        // Valid format and type but unsupported combination
2160        // IO Request with 4DW header (not valid)
2161        let invalid_combo = TlpHeader([0x22, 0x00, 0x00, 0x01]); // FMT='001' Type='00010' (IO Request 4DW)
2162        let result = invalid_combo.get_tlp_type();
2163        assert!(result.is_err());
2164        assert_eq!(result.unwrap_err(), TlpError::UnsupportedCombination);
2165    }
2166
2167    // ── new_mem_req rejects TlpPrefix ──────────────────────────────────────
2168
2169    #[test]
2170    fn mem_req_rejects_tlp_prefix() {
2171        let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
2172        let result = new_mem_req(bytes, &TlpFmt::TlpPrefix);
2173        assert!(matches!(result, Err(TlpError::UnsupportedCombination)));
2174    }
2175
2176    // ── short packet rejection ─────────────────────────────────────────────
2177
2178    #[test]
2179    fn packet_new_rejects_empty_input() {
2180        assert!(matches!(
2181            TlpPacket::new(vec![], TlpMode::NonFlit),
2182            Err(TlpError::InvalidLength)
2183        ));
2184    }
2185
2186    #[test]
2187    fn packet_new_rejects_3_bytes() {
2188        assert!(matches!(
2189            TlpPacket::new(vec![0x00, 0x00, 0x00], TlpMode::NonFlit),
2190            Err(TlpError::InvalidLength)
2191        ));
2192    }
2193
2194    #[test]
2195    fn packet_new_accepts_4_bytes() {
2196        // Exactly 4 bytes = header only, no data — should succeed
2197        assert!(TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::NonFlit).is_ok());
2198    }
2199
2200    #[test]
2201    fn packet_header_new_rejects_short_input() {
2202        assert!(matches!(
2203            TlpPacketHeader::new(vec![0x00, 0x00], TlpMode::NonFlit),
2204            Err(TlpError::InvalidLength)
2205        ));
2206    }
2207
2208    // ── TlpMode: Flit ─────────────────────────────────────────────────────
2209
2210    #[test]
2211    fn packet_new_flit_succeeds_for_valid_nop() {
2212        // NOP flit TLP (type 0x00, 1 DW base header, no payload)
2213        let bytes = vec![0x00, 0x00, 0x00, 0x00];
2214        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
2215        assert_eq!(pkt.flit_type(), Some(FlitTlpType::Nop));
2216        assert!(pkt.data().is_empty());
2217    }
2218
2219    #[test]
2220    fn packet_new_flit_mrd32_has_no_payload() {
2221        // MRd32 flit (type 0x03, 3 DW base header, no payload despite Length=1)
2222        let bytes = vec![
2223            0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2224        ];
2225        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
2226        assert_eq!(pkt.flit_type(), Some(FlitTlpType::MemRead32));
2227        assert!(pkt.data().is_empty()); // read request, no payload
2228    }
2229
2230    #[test]
2231    fn packet_new_flit_mwr32_has_payload() {
2232        // MWr32 flit (type 0x40, 3 DW base header + 1 DW payload)
2233        let bytes = vec![
2234            0x40, 0x00, 0x00, 0x01, // DW0: MemWrite32, length=1
2235            0x00, 0x00, 0x00, 0x00, // DW1
2236            0x00, 0x00, 0x00, 0x00, // DW2
2237            0xDE, 0xAD, 0xBE, 0xEF, // payload
2238        ];
2239        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
2240        assert_eq!(pkt.flit_type(), Some(FlitTlpType::MemWrite32));
2241        assert_eq!(pkt.data(), [0xDE, 0xAD, 0xBE, 0xEF]);
2242    }
2243
2244    #[test]
2245    fn packet_new_flit_nonflit_returns_none_for_flit_type() {
2246        // Non-flit packets should return None from flit_type()
2247        let pkt = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::NonFlit).unwrap();
2248        assert_eq!(pkt.flit_type(), None);
2249    }
2250
2251    #[test]
2252    fn packet_header_new_flit_returns_not_implemented() {
2253        // TlpPacketHeader::new() with Flit still returns NotImplemented
2254        let bytes = vec![0x00, 0x00, 0x00, 0x00];
2255        assert_eq!(
2256            TlpPacketHeader::new(bytes, TlpMode::Flit).err().unwrap(),
2257            TlpError::NotImplemented
2258        );
2259    }
2260
2261    // ── TlpMode: derive traits ─────────────────────────────────────────────
2262
2263    #[test]
2264    fn tlp_mode_debug_and_partialeq() {
2265        assert_eq!(TlpMode::NonFlit, TlpMode::NonFlit);
2266        assert_ne!(TlpMode::NonFlit, TlpMode::Flit);
2267        let s = format!("{:?}", TlpMode::NonFlit);
2268        assert!(s.contains("NonFlit"));
2269        let s2 = format!("{:?}", TlpMode::Flit);
2270        assert!(s2.contains("Flit"));
2271    }
2272
2273    #[test]
2274    #[allow(clippy::clone_on_copy)]
2275    fn tlp_mode_copy_and_clone() {
2276        let m = TlpMode::NonFlit;
2277        let m2 = m; // Copy
2278        let m3 = m.clone(); // Verify Clone trait is callable on Copy types
2279        assert_eq!(m2, TlpMode::NonFlit);
2280        assert_eq!(m3, TlpMode::NonFlit);
2281    }
2282
2283    // ── TlpError::NotImplemented ──────────────────────────────────────────
2284
2285    #[test]
2286    fn not_implemented_error_is_distinct() {
2287        let e = TlpError::NotImplemented;
2288        assert_ne!(e, TlpError::InvalidFormat);
2289        assert_ne!(e, TlpError::InvalidType);
2290        assert_ne!(e, TlpError::UnsupportedCombination);
2291        assert_ne!(e, TlpError::InvalidLength);
2292        assert_eq!(e, TlpError::NotImplemented);
2293        let s = format!("{:?}", e);
2294        assert!(s.contains("NotImplemented"));
2295    }
2296
2297    // ── helpers ───────────────────────────────────────────────────────────────
2298
2299    /// Build a DW0-only TlpHeader from a 3-bit fmt and 5-bit type field.
2300    /// byte0 layout (MSB0): bits[7:5] = fmt, bits[4:0] = type
2301    fn dw0(fmt: u8, typ: u8) -> TlpHeader<[u8; 4]> {
2302        TlpHeader([(fmt << 5) | (typ & 0x1f), 0x00, 0x00, 0x00])
2303    }
2304
2305    /// Build a full TLP byte vector: DW0 header + arbitrary payload bytes.
2306    /// DW0 bytes 1-3 are left 0 (length / TC / flags irrelevant for field tests).
2307    fn mk_tlp(fmt: u8, typ: u8, rest: &[u8]) -> Vec<u8> {
2308        let mut v = Vec::with_capacity(4 + rest.len());
2309        v.push((fmt << 5) | (typ & 0x1f));
2310        v.push(0x00); // TC, T9, T8, Attr_b2, LN, TH
2311        v.push(0x00); // TD, Ep, Attr, AT
2312        v.push(0x00); // Length
2313        v.extend_from_slice(rest);
2314        v
2315    }
2316
2317    // ── happy path: every currently-supported (fmt, type) pair ────────────────
2318
2319    #[test]
2320    fn header_decode_supported_pairs() {
2321        const FMT_3DW_NO_DATA: u8 = 0b000;
2322        const FMT_4DW_NO_DATA: u8 = 0b001;
2323        const FMT_3DW_WITH_DATA: u8 = 0b010;
2324        const FMT_4DW_WITH_DATA: u8 = 0b011;
2325
2326        const TY_MEM: u8 = 0b00000;
2327        const TY_MEM_LK: u8 = 0b00001;
2328        const TY_IO: u8 = 0b00010;
2329        const TY_CFG0: u8 = 0b00100;
2330        const TY_CFG1: u8 = 0b00101;
2331        const TY_CPL: u8 = 0b01010;
2332        const TY_CPL_LK: u8 = 0b01011;
2333        const TY_ATOM_FETCH: u8 = 0b01100;
2334        const TY_ATOM_SWAP: u8 = 0b01101;
2335        const TY_ATOM_CAS: u8 = 0b01110;
2336        const TY_DMWR: u8 = 0b11011;
2337
2338        // Memory Request: NoData → Read, WithData → Write; both 3DW and 4DW
2339        assert_eq!(
2340            dw0(FMT_3DW_NO_DATA, TY_MEM).get_tlp_type().unwrap(),
2341            TlpType::MemReadReq
2342        );
2343        assert_eq!(
2344            dw0(FMT_4DW_NO_DATA, TY_MEM).get_tlp_type().unwrap(),
2345            TlpType::MemReadReq
2346        );
2347        assert_eq!(
2348            dw0(FMT_3DW_WITH_DATA, TY_MEM).get_tlp_type().unwrap(),
2349            TlpType::MemWriteReq
2350        );
2351        assert_eq!(
2352            dw0(FMT_4DW_WITH_DATA, TY_MEM).get_tlp_type().unwrap(),
2353            TlpType::MemWriteReq
2354        );
2355
2356        // Memory Lock Request: NoData only (3DW and 4DW)
2357        assert_eq!(
2358            dw0(FMT_3DW_NO_DATA, TY_MEM_LK).get_tlp_type().unwrap(),
2359            TlpType::MemReadLockReq
2360        );
2361        assert_eq!(
2362            dw0(FMT_4DW_NO_DATA, TY_MEM_LK).get_tlp_type().unwrap(),
2363            TlpType::MemReadLockReq
2364        );
2365
2366        // IO Request: 3DW only; NoData → Read, WithData → Write
2367        assert_eq!(
2368            dw0(FMT_3DW_NO_DATA, TY_IO).get_tlp_type().unwrap(),
2369            TlpType::IOReadReq
2370        );
2371        assert_eq!(
2372            dw0(FMT_3DW_WITH_DATA, TY_IO).get_tlp_type().unwrap(),
2373            TlpType::IOWriteReq
2374        );
2375
2376        // Config Type 0: 3DW only
2377        assert_eq!(
2378            dw0(FMT_3DW_NO_DATA, TY_CFG0).get_tlp_type().unwrap(),
2379            TlpType::ConfType0ReadReq
2380        );
2381        assert_eq!(
2382            dw0(FMT_3DW_WITH_DATA, TY_CFG0).get_tlp_type().unwrap(),
2383            TlpType::ConfType0WriteReq
2384        );
2385
2386        // Config Type 1: 3DW only
2387        assert_eq!(
2388            dw0(FMT_3DW_NO_DATA, TY_CFG1).get_tlp_type().unwrap(),
2389            TlpType::ConfType1ReadReq
2390        );
2391        assert_eq!(
2392            dw0(FMT_3DW_WITH_DATA, TY_CFG1).get_tlp_type().unwrap(),
2393            TlpType::ConfType1WriteReq
2394        );
2395
2396        // Completion: 3DW only; NoData → Cpl, WithData → CplData
2397        assert_eq!(
2398            dw0(FMT_3DW_NO_DATA, TY_CPL).get_tlp_type().unwrap(),
2399            TlpType::Cpl
2400        );
2401        assert_eq!(
2402            dw0(FMT_3DW_WITH_DATA, TY_CPL).get_tlp_type().unwrap(),
2403            TlpType::CplData
2404        );
2405
2406        // Completion Locked: 3DW only
2407        assert_eq!(
2408            dw0(FMT_3DW_NO_DATA, TY_CPL_LK).get_tlp_type().unwrap(),
2409            TlpType::CplLocked
2410        );
2411        assert_eq!(
2412            dw0(FMT_3DW_WITH_DATA, TY_CPL_LK).get_tlp_type().unwrap(),
2413            TlpType::CplDataLocked
2414        );
2415
2416        // Atomics: WithData only (3DW and 4DW)
2417        assert_eq!(
2418            dw0(FMT_3DW_WITH_DATA, TY_ATOM_FETCH)
2419                .get_tlp_type()
2420                .unwrap(),
2421            TlpType::FetchAddAtomicOpReq
2422        );
2423        assert_eq!(
2424            dw0(FMT_4DW_WITH_DATA, TY_ATOM_FETCH)
2425                .get_tlp_type()
2426                .unwrap(),
2427            TlpType::FetchAddAtomicOpReq
2428        );
2429
2430        assert_eq!(
2431            dw0(FMT_3DW_WITH_DATA, TY_ATOM_SWAP).get_tlp_type().unwrap(),
2432            TlpType::SwapAtomicOpReq
2433        );
2434        assert_eq!(
2435            dw0(FMT_4DW_WITH_DATA, TY_ATOM_SWAP).get_tlp_type().unwrap(),
2436            TlpType::SwapAtomicOpReq
2437        );
2438
2439        assert_eq!(
2440            dw0(FMT_3DW_WITH_DATA, TY_ATOM_CAS).get_tlp_type().unwrap(),
2441            TlpType::CompareSwapAtomicOpReq
2442        );
2443        assert_eq!(
2444            dw0(FMT_4DW_WITH_DATA, TY_ATOM_CAS).get_tlp_type().unwrap(),
2445            TlpType::CompareSwapAtomicOpReq
2446        );
2447
2448        // DMWr: WithData only (3DW and 4DW)
2449        assert_eq!(
2450            dw0(FMT_3DW_WITH_DATA, TY_DMWR).get_tlp_type().unwrap(),
2451            TlpType::DeferrableMemWriteReq
2452        );
2453        assert_eq!(
2454            dw0(FMT_4DW_WITH_DATA, TY_DMWR).get_tlp_type().unwrap(),
2455            TlpType::DeferrableMemWriteReq
2456        );
2457
2458        // Message Requests: all 6 routing sub-types, no-data → MsgReq, with-data → MsgReqData
2459        // Type[4:0]: 10000=routeRC, 10001=routeAddr, 10010=routeID,
2460        //            10011=broadcast, 10100=local, 10101=gathered
2461        for routing in 0b10000u8..=0b10101u8 {
2462            assert_eq!(
2463                dw0(FMT_3DW_NO_DATA, routing).get_tlp_type().unwrap(),
2464                TlpType::MsgReq,
2465                "Fmt=000 Type={:#07b} should be MsgReq",
2466                routing
2467            );
2468            assert_eq!(
2469                dw0(FMT_4DW_NO_DATA, routing).get_tlp_type().unwrap(),
2470                TlpType::MsgReq,
2471                "Fmt=001 Type={:#07b} should be MsgReq",
2472                routing
2473            );
2474            assert_eq!(
2475                dw0(FMT_3DW_WITH_DATA, routing).get_tlp_type().unwrap(),
2476                TlpType::MsgReqData,
2477                "Fmt=010 Type={:#07b} should be MsgReqData",
2478                routing
2479            );
2480            assert_eq!(
2481                dw0(FMT_4DW_WITH_DATA, routing).get_tlp_type().unwrap(),
2482                TlpType::MsgReqData,
2483                "Fmt=011 Type={:#07b} should be MsgReqData",
2484                routing
2485            );
2486        }
2487    }
2488
2489    // ── negative path: every illegal (fmt, type) pair → UnsupportedCombination ─
2490
2491    #[test]
2492    fn header_decode_rejects_unsupported_combinations() {
2493        const FMT_3DW_NO_DATA: u8 = 0b000;
2494        const FMT_4DW_NO_DATA: u8 = 0b001;
2495        const FMT_3DW_WITH_DATA: u8 = 0b010;
2496        const FMT_4DW_WITH_DATA: u8 = 0b011;
2497
2498        const TY_MEM_LK: u8 = 0b00001;
2499        const TY_IO: u8 = 0b00010;
2500        const TY_CFG0: u8 = 0b00100;
2501        const TY_CFG1: u8 = 0b00101;
2502        const TY_CPL: u8 = 0b01010;
2503        const TY_CPL_LK: u8 = 0b01011;
2504        const TY_ATOM_FETCH: u8 = 0b01100;
2505        const TY_ATOM_SWAP: u8 = 0b01101;
2506        const TY_ATOM_CAS: u8 = 0b01110;
2507        const TY_DMWR: u8 = 0b11011;
2508
2509        // IO: 4DW variants are illegal
2510        assert_eq!(
2511            dw0(FMT_4DW_NO_DATA, TY_IO).get_tlp_type().unwrap_err(),
2512            TlpError::UnsupportedCombination
2513        );
2514        assert_eq!(
2515            dw0(FMT_4DW_WITH_DATA, TY_IO).get_tlp_type().unwrap_err(),
2516            TlpError::UnsupportedCombination
2517        );
2518
2519        // Config: 4DW variants are illegal (configs are always 3DW)
2520        assert_eq!(
2521            dw0(FMT_4DW_NO_DATA, TY_CFG0).get_tlp_type().unwrap_err(),
2522            TlpError::UnsupportedCombination
2523        );
2524        assert_eq!(
2525            dw0(FMT_4DW_WITH_DATA, TY_CFG0).get_tlp_type().unwrap_err(),
2526            TlpError::UnsupportedCombination
2527        );
2528        assert_eq!(
2529            dw0(FMT_4DW_NO_DATA, TY_CFG1).get_tlp_type().unwrap_err(),
2530            TlpError::UnsupportedCombination
2531        );
2532        assert_eq!(
2533            dw0(FMT_4DW_WITH_DATA, TY_CFG1).get_tlp_type().unwrap_err(),
2534            TlpError::UnsupportedCombination
2535        );
2536
2537        // Completions: 4DW variants are illegal
2538        assert_eq!(
2539            dw0(FMT_4DW_NO_DATA, TY_CPL).get_tlp_type().unwrap_err(),
2540            TlpError::UnsupportedCombination
2541        );
2542        assert_eq!(
2543            dw0(FMT_4DW_WITH_DATA, TY_CPL).get_tlp_type().unwrap_err(),
2544            TlpError::UnsupportedCombination
2545        );
2546        assert_eq!(
2547            dw0(FMT_4DW_NO_DATA, TY_CPL_LK).get_tlp_type().unwrap_err(),
2548            TlpError::UnsupportedCombination
2549        );
2550        assert_eq!(
2551            dw0(FMT_4DW_WITH_DATA, TY_CPL_LK)
2552                .get_tlp_type()
2553                .unwrap_err(),
2554            TlpError::UnsupportedCombination
2555        );
2556
2557        // Atomics: NoData variants are illegal (atomics always carry data)
2558        assert_eq!(
2559            dw0(FMT_3DW_NO_DATA, TY_ATOM_FETCH)
2560                .get_tlp_type()
2561                .unwrap_err(),
2562            TlpError::UnsupportedCombination
2563        );
2564        assert_eq!(
2565            dw0(FMT_4DW_NO_DATA, TY_ATOM_FETCH)
2566                .get_tlp_type()
2567                .unwrap_err(),
2568            TlpError::UnsupportedCombination
2569        );
2570        assert_eq!(
2571            dw0(FMT_3DW_NO_DATA, TY_ATOM_SWAP)
2572                .get_tlp_type()
2573                .unwrap_err(),
2574            TlpError::UnsupportedCombination
2575        );
2576        assert_eq!(
2577            dw0(FMT_4DW_NO_DATA, TY_ATOM_SWAP)
2578                .get_tlp_type()
2579                .unwrap_err(),
2580            TlpError::UnsupportedCombination
2581        );
2582        assert_eq!(
2583            dw0(FMT_3DW_NO_DATA, TY_ATOM_CAS)
2584                .get_tlp_type()
2585                .unwrap_err(),
2586            TlpError::UnsupportedCombination
2587        );
2588        assert_eq!(
2589            dw0(FMT_4DW_NO_DATA, TY_ATOM_CAS)
2590                .get_tlp_type()
2591                .unwrap_err(),
2592            TlpError::UnsupportedCombination
2593        );
2594
2595        // MemReadLock: WithData variants are illegal (lock is a read-only operation)
2596        assert_eq!(
2597            dw0(FMT_3DW_WITH_DATA, TY_MEM_LK)
2598                .get_tlp_type()
2599                .unwrap_err(),
2600            TlpError::UnsupportedCombination
2601        );
2602        assert_eq!(
2603            dw0(FMT_4DW_WITH_DATA, TY_MEM_LK)
2604                .get_tlp_type()
2605                .unwrap_err(),
2606            TlpError::UnsupportedCombination
2607        );
2608
2609        // TlpPrefix fmt (0b100): all Type values decode to a Prefix type, never UnsupportedCombination.
2610        // These are tested in header_decode_prefix_and_message_types.
2611
2612        // DMWr: NoData variants are illegal (DMWr always carries data)
2613        assert_eq!(
2614            dw0(FMT_3DW_NO_DATA, TY_DMWR).get_tlp_type().unwrap_err(),
2615            TlpError::UnsupportedCombination
2616        );
2617        assert_eq!(
2618            dw0(FMT_4DW_NO_DATA, TY_DMWR).get_tlp_type().unwrap_err(),
2619            TlpError::UnsupportedCombination
2620        );
2621        // Note: FMT_PREFIX with TY_DMWR now decodes to EndToEndTlpPrefix (Type[4]=1)
2622    }
2623
2624    // ── DMWr: Deferrable Memory Write header decode ────────────────────────
2625
2626    #[test]
2627    fn tlp_header_dmwr32_decode() {
2628        // Fmt=010 (3DW w/ Data), Type=11011 (DMWr) → byte0 = 0x5B
2629        let dmwr32 = TlpHeader([0x5B, 0x00, 0x00, 0x00]);
2630        assert_eq!(
2631            dmwr32.get_tlp_type().unwrap(),
2632            TlpType::DeferrableMemWriteReq
2633        );
2634    }
2635
2636    #[test]
2637    fn tlp_header_dmwr64_decode() {
2638        // Fmt=011 (4DW w/ Data), Type=11011 (DMWr) → byte0 = 0x7B
2639        let dmwr64 = TlpHeader([0x7B, 0x00, 0x00, 0x00]);
2640        assert_eq!(
2641            dmwr64.get_tlp_type().unwrap(),
2642            TlpType::DeferrableMemWriteReq
2643        );
2644    }
2645
2646    #[test]
2647    fn tlp_header_dmwr_rejects_nodata_formats() {
2648        // Fmt=000, Type=11011 → byte0 = 0x1B
2649        let dmwr_bad_3dw_nodata = TlpHeader([0x1B, 0x00, 0x00, 0x00]);
2650        assert_eq!(
2651            dmwr_bad_3dw_nodata.get_tlp_type().unwrap_err(),
2652            TlpError::UnsupportedCombination
2653        );
2654
2655        // Fmt=001, Type=11011 → byte0 = 0x3B
2656        let dmwr_bad_4dw_nodata = TlpHeader([0x3B, 0x00, 0x00, 0x00]);
2657        assert_eq!(
2658            dmwr_bad_4dw_nodata.get_tlp_type().unwrap_err(),
2659            TlpError::UnsupportedCombination
2660        );
2661    }
2662
2663    #[test]
2664    fn dmwr_full_packet_3dw_fields() {
2665        // DMWr32 through TlpPacket pipeline with MemRequest3DW fields
2666        let payload = [
2667            0xAB, 0xCD, 0x42, 0x0F, // req_id=0xABCD, tag=0x42, BE=0x0F
2668            0xDE, 0xAD, 0x00, 0x00, // address32=0xDEAD0000
2669        ];
2670        let pkt = TlpPacket::new(mk_tlp(0b010, 0b11011, &payload), TlpMode::NonFlit).unwrap();
2671        assert_eq!(pkt.tlp_type().unwrap(), TlpType::DeferrableMemWriteReq);
2672        assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader3DW);
2673
2674        let mr = new_mem_req(pkt.data().to_vec(), &pkt.tlp_format().unwrap()).unwrap();
2675        assert_eq!(mr.req_id(), 0xABCD);
2676        assert_eq!(mr.tag(), 0x42);
2677        assert_eq!(mr.address(), 0xDEAD_0000);
2678    }
2679
2680    #[test]
2681    fn dmwr_full_packet_4dw_fields() {
2682        // DMWr64 through TlpPacket pipeline with MemRequest4DW fields
2683        let payload = [
2684            0xBE, 0xEF, 0xA5, 0x00, // req_id=0xBEEF, tag=0xA5
2685            0x11, 0x22, 0x33, 0x44, // address64 hi
2686            0x55, 0x66, 0x77, 0x88, // address64 lo
2687        ];
2688        let pkt = TlpPacket::new(mk_tlp(0b011, 0b11011, &payload), TlpMode::NonFlit).unwrap();
2689        assert_eq!(pkt.tlp_type().unwrap(), TlpType::DeferrableMemWriteReq);
2690        assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader4DW);
2691
2692        let mr = new_mem_req(pkt.data().to_vec(), &pkt.tlp_format().unwrap()).unwrap();
2693        assert_eq!(mr.req_id(), 0xBEEF);
2694        assert_eq!(mr.tag(), 0xA5);
2695        assert_eq!(mr.address(), 0x1122_3344_5566_7788);
2696    }
2697
2698    // ── is_non_posted() semantics ─────────────────────────────────────────────
2699
2700    #[test]
2701    fn is_non_posted_returns_true_for_non_posted_types() {
2702        assert!(TlpType::MemReadReq.is_non_posted());
2703        assert!(TlpType::MemReadLockReq.is_non_posted());
2704        assert!(TlpType::IOReadReq.is_non_posted());
2705        assert!(TlpType::IOWriteReq.is_non_posted());
2706        assert!(TlpType::ConfType0ReadReq.is_non_posted());
2707        assert!(TlpType::ConfType0WriteReq.is_non_posted());
2708        assert!(TlpType::ConfType1ReadReq.is_non_posted());
2709        assert!(TlpType::ConfType1WriteReq.is_non_posted());
2710        assert!(TlpType::FetchAddAtomicOpReq.is_non_posted());
2711        assert!(TlpType::SwapAtomicOpReq.is_non_posted());
2712        assert!(TlpType::CompareSwapAtomicOpReq.is_non_posted());
2713        assert!(TlpType::DeferrableMemWriteReq.is_non_posted());
2714    }
2715
2716    #[test]
2717    fn is_non_posted_returns_false_for_posted_types() {
2718        assert!(!TlpType::MemWriteReq.is_non_posted());
2719        assert!(!TlpType::MsgReq.is_non_posted());
2720        assert!(!TlpType::MsgReqData.is_non_posted());
2721    }
2722
2723    #[test]
2724    fn is_non_posted_returns_false_for_completions() {
2725        // Completions are responses, not requests — is_non_posted() is false
2726        assert!(!TlpType::Cpl.is_non_posted());
2727        assert!(!TlpType::CplData.is_non_posted());
2728        assert!(!TlpType::CplLocked.is_non_posted());
2729        assert!(!TlpType::CplDataLocked.is_non_posted());
2730    }
2731
2732    /// Exhaustive is_non_posted() coverage: all 21 TlpType variants explicitly listed.
2733    /// A spec change to non-posted semantics will immediately fail the relevant assertion.
2734    #[test]
2735    fn is_non_posted_exhaustive_all_21_variants() {
2736        // --- Non-posted (require a Completion) ---
2737        assert!(
2738            TlpType::MemReadReq.is_non_posted(),
2739            "MemReadReq must be non-posted"
2740        );
2741        assert!(
2742            TlpType::MemReadLockReq.is_non_posted(),
2743            "MemReadLockReq must be non-posted"
2744        );
2745        assert!(
2746            TlpType::IOReadReq.is_non_posted(),
2747            "IOReadReq must be non-posted"
2748        );
2749        assert!(
2750            TlpType::IOWriteReq.is_non_posted(),
2751            "IOWriteReq must be non-posted"
2752        );
2753        assert!(
2754            TlpType::ConfType0ReadReq.is_non_posted(),
2755            "ConfType0ReadReq must be non-posted"
2756        );
2757        assert!(
2758            TlpType::ConfType0WriteReq.is_non_posted(),
2759            "ConfType0WriteReq must be non-posted"
2760        );
2761        assert!(
2762            TlpType::ConfType1ReadReq.is_non_posted(),
2763            "ConfType1ReadReq must be non-posted"
2764        );
2765        assert!(
2766            TlpType::ConfType1WriteReq.is_non_posted(),
2767            "ConfType1WriteReq must be non-posted"
2768        );
2769        assert!(
2770            TlpType::FetchAddAtomicOpReq.is_non_posted(),
2771            "FetchAddAtomicOpReq must be non-posted"
2772        );
2773        assert!(
2774            TlpType::SwapAtomicOpReq.is_non_posted(),
2775            "SwapAtomicOpReq must be non-posted"
2776        );
2777        assert!(
2778            TlpType::CompareSwapAtomicOpReq.is_non_posted(),
2779            "CompareSwapAtomicOpReq must be non-posted"
2780        );
2781        assert!(
2782            TlpType::DeferrableMemWriteReq.is_non_posted(),
2783            "DeferrableMemWriteReq must be non-posted"
2784        );
2785
2786        // --- Posted (no Completion expected) ---
2787        assert!(
2788            !TlpType::MemWriteReq.is_non_posted(),
2789            "MemWriteReq is posted"
2790        );
2791        assert!(!TlpType::MsgReq.is_non_posted(), "MsgReq is posted");
2792        assert!(!TlpType::MsgReqData.is_non_posted(), "MsgReqData is posted");
2793
2794        // --- Completions (responses, not requests) ---
2795        assert!(
2796            !TlpType::Cpl.is_non_posted(),
2797            "Cpl is a response, not a request"
2798        );
2799        assert!(
2800            !TlpType::CplData.is_non_posted(),
2801            "CplData is a response, not a request"
2802        );
2803        assert!(
2804            !TlpType::CplLocked.is_non_posted(),
2805            "CplLocked is a response, not a request"
2806        );
2807        assert!(
2808            !TlpType::CplDataLocked.is_non_posted(),
2809            "CplDataLocked is a response, not a request"
2810        );
2811
2812        // --- Prefixes (not transactions) ---
2813        assert!(
2814            !TlpType::LocalTlpPrefix.is_non_posted(),
2815            "LocalTlpPrefix is not a transaction"
2816        );
2817        assert!(
2818            !TlpType::EndToEndTlpPrefix.is_non_posted(),
2819            "EndToEndTlpPrefix is not a transaction"
2820        );
2821    }
2822
2823    // ── atomic tier-A: real bytes through the full packet pipeline ─────────────
2824
2825    #[test]
2826    fn atomic_fetchadd_3dw_type_and_fields() {
2827        const FMT_3DW_WITH_DATA: u8 = 0b010;
2828        const TY_ATOM_FETCH: u8 = 0b01100;
2829
2830        // DW1+DW2 as MemRequest3DW sees them (MSB0):
2831        //   requester_id [15:0]  = 0x1234
2832        //   tag          [23:16] = 0x56
2833        //   last_dw_be   [27:24] = 0x0  (ignored for this test)
2834        //   first_dw_be  [31:28] = 0x0  (ignored for this test)
2835        //   address32    [63:32] = 0x89ABCDEF
2836        let payload = [
2837            0x12, 0x34, // req_id
2838            0x56, 0x00, // tag, BE nibbles
2839            0x89, 0xAB, 0xCD, 0xEF, // address32
2840        ];
2841
2842        let pkt = TlpPacket::new(
2843            mk_tlp(FMT_3DW_WITH_DATA, TY_ATOM_FETCH, &payload),
2844            TlpMode::NonFlit,
2845        )
2846        .unwrap();
2847
2848        assert_eq!(pkt.tlp_type().unwrap(), TlpType::FetchAddAtomicOpReq);
2849        assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader3DW);
2850
2851        let fmt = pkt.tlp_format().unwrap();
2852        let mr = new_mem_req(pkt.data().to_vec(), &fmt).unwrap();
2853        assert_eq!(mr.req_id(), 0x1234);
2854        assert_eq!(mr.tag(), 0x56);
2855        assert_eq!(mr.address(), 0x89AB_CDEF);
2856    }
2857
2858    #[test]
2859    fn atomic_cas_4dw_type_and_fields() {
2860        const FMT_4DW_WITH_DATA: u8 = 0b011;
2861        const TY_ATOM_CAS: u8 = 0b01110;
2862
2863        // DW1-DW3 as MemRequest4DW sees them (MSB0):
2864        //   requester_id [15:0]  = 0xBEEF
2865        //   tag          [23:16] = 0xA5
2866        //   last/first_dw_be     = 0x00
2867        //   address64    [95:32] = 0x1122_3344_5566_7788
2868        let payload = [
2869            0xBE, 0xEF, // req_id
2870            0xA5, 0x00, // tag, BE nibbles
2871            0x11, 0x22, 0x33, 0x44, // address64 high DW
2872            0x55, 0x66, 0x77, 0x88, // address64 low DW
2873        ];
2874
2875        let pkt = TlpPacket::new(
2876            mk_tlp(FMT_4DW_WITH_DATA, TY_ATOM_CAS, &payload),
2877            TlpMode::NonFlit,
2878        )
2879        .unwrap();
2880
2881        assert_eq!(pkt.tlp_type().unwrap(), TlpType::CompareSwapAtomicOpReq);
2882        assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader4DW);
2883
2884        let fmt = pkt.tlp_format().unwrap();
2885        let mr = new_mem_req(pkt.data().to_vec(), &fmt).unwrap();
2886        assert_eq!(mr.req_id(), 0xBEEF);
2887        assert_eq!(mr.tag(), 0xA5);
2888        assert_eq!(mr.address(), 0x1122_3344_5566_7788);
2889    }
2890
2891    // ── atomic tier-B: operand parsing via new_atomic_req() ───────────────────
2892
2893    #[test]
2894    fn fetchadd_3dw_operand() {
2895        // FetchAdd 3DW (W32): single 32-bit addend after the 8-byte header
2896        //   DW1: req_id=0xDEAD  tag=0x42  BE=0x00
2897        //   DW2: address32=0xC001_0004
2898        //   op0: addend=0x0000_000A
2899        let payload = [
2900            0xDE, 0xAD, 0x42, 0x00, // req_id, tag, BE
2901            0xC0, 0x01, 0x00, 0x04, // address32
2902            0x00, 0x00, 0x00, 0x0A, // addend (W32)
2903        ];
2904        let pkt = TlpPacket::new(mk_tlp(0b010, 0b01100, &payload), TlpMode::NonFlit).unwrap();
2905        let ar = new_atomic_req(&pkt).unwrap();
2906
2907        assert_eq!(ar.op(), AtomicOp::FetchAdd);
2908        assert_eq!(ar.width(), AtomicWidth::W32);
2909        assert_eq!(ar.req_id(), 0xDEAD);
2910        assert_eq!(ar.tag(), 0x42);
2911        assert_eq!(ar.address(), 0xC001_0004);
2912        assert_eq!(ar.operand0(), 0x0A);
2913        assert!(ar.operand1().is_none());
2914    }
2915
2916    #[test]
2917    fn fetchadd_4dw_operand() {
2918        // FetchAdd 4DW (W64): single 64-bit addend after the 12-byte header
2919        //   DW1: req_id=0x0042  tag=0xBB  BE=0x00
2920        //   DW2-DW3: address64=0x0000_0001_0000_0000
2921        //   op0: addend=0xFFFF_FFFF_FFFF_FFFF
2922        let payload = [
2923            0x00, 0x42, 0xBB, 0x00, // req_id, tag, BE
2924            0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // address64
2925            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // addend (W64)
2926        ];
2927        let pkt = TlpPacket::new(mk_tlp(0b011, 0b01100, &payload), TlpMode::NonFlit).unwrap();
2928        let ar = new_atomic_req(&pkt).unwrap();
2929
2930        assert_eq!(ar.op(), AtomicOp::FetchAdd);
2931        assert_eq!(ar.width(), AtomicWidth::W64);
2932        assert_eq!(ar.req_id(), 0x0042);
2933        assert_eq!(ar.tag(), 0xBB);
2934        assert_eq!(ar.address(), 0x0000_0001_0000_0000);
2935        assert_eq!(ar.operand0(), 0xFFFF_FFFF_FFFF_FFFF);
2936        assert!(ar.operand1().is_none());
2937    }
2938
2939    #[test]
2940    fn swap_3dw_operand() {
2941        // Swap 3DW (W32): single 32-bit swap value
2942        //   DW1: req_id=0x1111  tag=0x05  BE=0x00
2943        //   DW2: address32=0xF000_0008
2944        //   op0: new_value=0xABCD_EF01
2945        let payload = [
2946            0x11, 0x11, 0x05, 0x00, // req_id, tag, BE
2947            0xF0, 0x00, 0x00, 0x08, // address32
2948            0xAB, 0xCD, 0xEF, 0x01, // new_value (W32)
2949        ];
2950        let pkt = TlpPacket::new(mk_tlp(0b010, 0b01101, &payload), TlpMode::NonFlit).unwrap();
2951        let ar = new_atomic_req(&pkt).unwrap();
2952
2953        assert_eq!(ar.op(), AtomicOp::Swap);
2954        assert_eq!(ar.width(), AtomicWidth::W32);
2955        assert_eq!(ar.req_id(), 0x1111);
2956        assert_eq!(ar.tag(), 0x05);
2957        assert_eq!(ar.address(), 0xF000_0008);
2958        assert_eq!(ar.operand0(), 0xABCD_EF01);
2959        assert!(ar.operand1().is_none());
2960    }
2961
2962    #[test]
2963    fn cas_3dw_two_operands() {
2964        // CAS 3DW (W32): compare then swap — two 32-bit operands
2965        //   DW1: req_id=0xABCD  tag=0x07  BE=0x00
2966        //   DW2: address32=0x0000_4000
2967        //   op0: compare=0xCAFE_BABE
2968        //   op1: swap=0xDEAD_BEEF
2969        let payload = [
2970            0xAB, 0xCD, 0x07, 0x00, // req_id, tag, BE
2971            0x00, 0x00, 0x40, 0x00, // address32
2972            0xCA, 0xFE, 0xBA, 0xBE, // compare (W32)
2973            0xDE, 0xAD, 0xBE, 0xEF, // swap    (W32)
2974        ];
2975        let pkt = TlpPacket::new(mk_tlp(0b010, 0b01110, &payload), TlpMode::NonFlit).unwrap();
2976        let ar = new_atomic_req(&pkt).unwrap();
2977
2978        assert_eq!(ar.op(), AtomicOp::CompareSwap);
2979        assert_eq!(ar.width(), AtomicWidth::W32);
2980        assert_eq!(ar.req_id(), 0xABCD);
2981        assert_eq!(ar.tag(), 0x07);
2982        assert_eq!(ar.address(), 0x0000_4000);
2983        assert_eq!(ar.operand0(), 0xCAFE_BABE);
2984        assert_eq!(ar.operand1(), Some(0xDEAD_BEEF));
2985    }
2986
2987    #[test]
2988    fn cas_4dw_two_operands() {
2989        // CAS 4DW (W64): compare then swap — two 64-bit operands
2990        //   DW1: req_id=0x1234  tag=0xAA  BE=0x00
2991        //   DW2-DW3: address64=0xFFFF_FFFF_0000_0000
2992        //   op0: compare=0x0101_0101_0202_0202
2993        //   op1: swap=0x0303_0303_0404_0404
2994        let payload = [
2995            0x12, 0x34, 0xAA, 0x00, // req_id, tag, BE
2996            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // address64
2997            0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, // compare (W64)
2998            0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, // swap    (W64)
2999        ];
3000        let pkt = TlpPacket::new(mk_tlp(0b011, 0b01110, &payload), TlpMode::NonFlit).unwrap();
3001        let ar = new_atomic_req(&pkt).unwrap();
3002
3003        assert_eq!(ar.op(), AtomicOp::CompareSwap);
3004        assert_eq!(ar.width(), AtomicWidth::W64);
3005        assert_eq!(ar.req_id(), 0x1234);
3006        assert_eq!(ar.tag(), 0xAA);
3007        assert_eq!(ar.address(), 0xFFFF_FFFF_0000_0000);
3008        assert_eq!(ar.operand0(), 0x0101_0101_0202_0202);
3009        assert_eq!(ar.operand1(), Some(0x0303_0303_0404_0404));
3010    }
3011
3012    #[test]
3013    fn atomic_req_rejects_wrong_tlp_type() {
3014        // MemRead type is not an atomic — should get UnsupportedCombination
3015        let pkt = TlpPacket::new(mk_tlp(0b000, 0b00000, &[0u8; 16]), TlpMode::NonFlit).unwrap();
3016        assert_eq!(
3017            new_atomic_req(&pkt).err().unwrap(),
3018            TlpError::UnsupportedCombination
3019        );
3020    }
3021
3022    #[test]
3023    fn atomic_req_rejects_wrong_format() {
3024        // FetchAdd type with NoData3DW format is an invalid combo:
3025        // get_tlp_type() returns UnsupportedCombination, which propagates
3026        let pkt = TlpPacket::new(mk_tlp(0b000, 0b01100, &[0u8; 16]), TlpMode::NonFlit).unwrap();
3027        assert_eq!(
3028            new_atomic_req(&pkt).err().unwrap(),
3029            TlpError::UnsupportedCombination
3030        );
3031    }
3032
3033    #[test]
3034    fn atomic_req_rejects_short_payload() {
3035        // 3 bytes data — FetchAdd 3DW needs exactly 12 (8 hdr + 4 operand)
3036        let pkt = TlpPacket::new(mk_tlp(0b010, 0b01100, &[0u8; 3]), TlpMode::NonFlit).unwrap();
3037        assert_eq!(new_atomic_req(&pkt).err().unwrap(), TlpError::InvalidLength);
3038
3039        // 8 bytes data — header OK but operand missing (needs 12)
3040        let pkt = TlpPacket::new(mk_tlp(0b010, 0b01100, &[0u8; 8]), TlpMode::NonFlit).unwrap();
3041        assert_eq!(new_atomic_req(&pkt).err().unwrap(), TlpError::InvalidLength);
3042
3043        // 20 bytes data — CAS 4DW needs exactly 28 (12 hdr + 8 + 8)
3044        let pkt = TlpPacket::new(mk_tlp(0b011, 0b01110, &[0u8; 20]), TlpMode::NonFlit).unwrap();
3045        assert_eq!(new_atomic_req(&pkt).err().unwrap(), TlpError::InvalidLength);
3046    }
3047
3048    // ── helpers ───────────────────────────────────────────────────────────────
3049
3050    fn mk_pkt(fmt: u8, typ: u8, data: &[u8]) -> TlpPacket {
3051        TlpPacket::new(mk_tlp(fmt, typ, data), TlpMode::NonFlit).unwrap()
3052    }
3053
3054    // ── atomic tier-B (new API): real binary layout, single-argument call ─────
3055
3056    #[test]
3057    fn atomic_fetchadd_3dw_32_parses_operands() {
3058        // FetchAdd 3DW (W32): 8-byte header + 4-byte addend
3059        let data = [
3060            0x01, 0x00, 0x01, 0x00, // req_id=0x0100, tag=0x01, BE=0x00
3061            0x00, 0x00, 0x10, 0x00, // address32=0x0000_1000
3062            0x00, 0x00, 0x00, 0x07, // addend=7
3063        ];
3064        let pkt = mk_pkt(0b010, 0b01100, &data);
3065        let a = new_atomic_req(&pkt).unwrap();
3066        assert_eq!(a.op(), AtomicOp::FetchAdd);
3067        assert_eq!(a.width(), AtomicWidth::W32);
3068        assert_eq!(a.req_id(), 0x0100);
3069        assert_eq!(a.tag(), 0x01);
3070        assert_eq!(a.address(), 0x0000_1000);
3071        assert_eq!(a.operand0(), 7);
3072        assert!(a.operand1().is_none());
3073    }
3074
3075    #[test]
3076    fn atomic_swap_4dw_64_parses_operands() {
3077        // Swap 4DW (W64): 12-byte header + 8-byte new value
3078        let data = [
3079            0xBE, 0xEF, 0xA5, 0x00, // req_id=0xBEEF, tag=0xA5, BE=0x00
3080            0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // address64=0x0000_0001_0000_0000
3081            0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, // new_value
3082        ];
3083        let pkt = mk_pkt(0b011, 0b01101, &data);
3084        let a = new_atomic_req(&pkt).unwrap();
3085        assert_eq!(a.op(), AtomicOp::Swap);
3086        assert_eq!(a.width(), AtomicWidth::W64);
3087        assert_eq!(a.req_id(), 0xBEEF);
3088        assert_eq!(a.tag(), 0xA5);
3089        assert_eq!(a.address(), 0x0000_0001_0000_0000);
3090        assert_eq!(a.operand0(), 0xDEAD_BEEF_CAFE_BABE);
3091        assert!(a.operand1().is_none());
3092    }
3093
3094    #[test]
3095    fn atomic_cas_3dw_32_parses_operands() {
3096        // CAS 3DW (W32): 8-byte header + 4-byte compare + 4-byte swap
3097        let data = [
3098            0xAB, 0xCD, 0x07, 0x00, // req_id=0xABCD, tag=0x07, BE=0x00
3099            0x00, 0x00, 0x40, 0x00, // address32=0x0000_4000
3100            0xCA, 0xFE, 0xBA, 0xBE, // compare
3101            0xDE, 0xAD, 0xBE, 0xEF, // swap
3102        ];
3103        let pkt = mk_pkt(0b010, 0b01110, &data);
3104        let a = new_atomic_req(&pkt).unwrap();
3105        assert_eq!(a.op(), AtomicOp::CompareSwap);
3106        assert_eq!(a.width(), AtomicWidth::W32);
3107        assert_eq!(a.req_id(), 0xABCD);
3108        assert_eq!(a.tag(), 0x07);
3109        assert_eq!(a.address(), 0x0000_4000);
3110        assert_eq!(a.operand0(), 0xCAFE_BABE);
3111        assert_eq!(a.operand1(), Some(0xDEAD_BEEF));
3112    }
3113
3114    // ── CompletionReqDW23: Lower Address 7-bit decode ──────────────────────
3115
3116    #[test]
3117    fn completion_laddr_full_7_bits() {
3118        // Lower Address = 0x7F (127) — all 7 bits set
3119        // DW2 byte 3: R(1 bit)=0, LowerAddr(7 bits)=0x7F → byte = 0x7F
3120        let bytes = vec![
3121            0x00, 0x00, 0x00, 0x00, // completer_id, cmpl_stat, bcm, byte_cnt
3122            0x00, 0x00, 0x00, 0x7F, // req_id, tag, R=0, laddr=0x7F
3123        ];
3124        let cmpl = new_cmpl_req(bytes).unwrap();
3125        assert_eq!(cmpl.laddr(), 0x7F);
3126    }
3127
3128    #[test]
3129    fn completion_laddr_bit6_set() {
3130        // Lower Address = 64 (0x40) — bit 6 is the bit that was previously lost
3131        // DW2 byte 3: R=0, LowerAddr=0x40 → byte = 0x40
3132        let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40];
3133        let cmpl = new_cmpl_req(bytes).unwrap();
3134        assert_eq!(cmpl.laddr(), 0x40);
3135    }
3136
3137    #[test]
3138    fn completion_laddr_with_reserved_bit_set() {
3139        // R=1, LowerAddr=0x55 (85)
3140        // DW2 byte 3: 1_1010101 = 0xD5
3141        let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD5];
3142        let cmpl = new_cmpl_req(bytes).unwrap();
3143        assert_eq!(cmpl.laddr(), 0x55);
3144    }
3145
3146    #[test]
3147    fn completion_full_fields_with_laddr() {
3148        // completer_id=0x2001, cmpl_stat=0, bcm=0, byte_cnt=0x0FC,
3149        // req_id=0x1234, tag=0xAB, R=0, laddr=100 (0x64)
3150        let bytes = vec![
3151            0x20, 0x01, 0x00, 0xFC, // completer_id=0x2001, status=0, bcm=0, byte_cnt=0x0FC
3152            0x12, 0x34, 0xAB, 0x64, // req_id=0x1234, tag=0xAB, R=0, laddr=0x64
3153        ];
3154        let cmpl = new_cmpl_req(bytes).unwrap();
3155        assert_eq!(cmpl.cmpl_id(), 0x2001);
3156        assert_eq!(cmpl.byte_cnt(), 0x0FC);
3157        assert_eq!(cmpl.req_id(), 0x1234);
3158        assert_eq!(cmpl.tag(), 0xAB);
3159        assert_eq!(cmpl.laddr(), 0x64);
3160    }
3161
3162    #[test]
3163    fn atomic_fetchadd_rejects_invalid_operand_length() {
3164        // FetchAdd 3DW expects exactly 12 bytes (8 hdr + 4 operand).
3165        // A 14-byte payload (8 hdr + 6-byte "bad" operand) must be rejected.
3166        let bad = [
3167            0x01, 0x00, 0x01, 0x00, // req_id, tag, BE
3168            0x00, 0x00, 0x10, 0x00, // address32
3169            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6 bytes instead of 4
3170        ];
3171        let pkt = mk_pkt(0b010, 0b01100, &bad);
3172        assert_eq!(new_atomic_req(&pkt).unwrap_err(), TlpError::InvalidLength);
3173    }
3174
3175    // ── MessageReqDW24: DW3/DW4 full 32-bit decode ───────────────────────────
3176
3177    #[test]
3178    fn message_dw3_preserves_upper_16_bits() {
3179        // DW3 = 0xDEAD_BEEF — upper 16 bits (0xDEAD) must survive
3180        let bytes = vec![
3181            0x00, 0x00, 0x00, 0x00, // req_id, tag, msg_code
3182            0xDE, 0xAD, 0xBE, 0xEF, // DW3
3183            0x00, 0x00, 0x00, 0x00, // DW4
3184        ];
3185        let msg = new_msg_req(bytes).unwrap();
3186        assert_eq!(msg.dw3(), 0xDEAD_BEEF);
3187    }
3188
3189    #[test]
3190    fn message_dw4_preserves_upper_16_bits() {
3191        // DW4 = 0xCAFE_BABE — upper 16 bits (0xCAFE) must survive
3192        let bytes = vec![
3193            0x00, 0x00, 0x00, 0x00, // req_id, tag, msg_code
3194            0x00, 0x00, 0x00, 0x00, // DW3
3195            0xCA, 0xFE, 0xBA, 0xBE, // DW4
3196        ];
3197        let msg = new_msg_req(bytes).unwrap();
3198        assert_eq!(msg.dw4(), 0xCAFE_BABE);
3199    }
3200
3201    #[test]
3202    fn message_dw3_dw4_all_bits_set() {
3203        // Both DW3 and DW4 = 0xFFFF_FFFF
3204        let bytes = vec![
3205            0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
3206        ];
3207        let msg = new_msg_req(bytes).unwrap();
3208        assert_eq!(msg.dw3(), 0xFFFF_FFFF);
3209        assert_eq!(msg.dw4(), 0xFFFF_FFFF);
3210    }
3211
3212    #[test]
3213    fn message_request_full_fields() {
3214        // req_id=0xABCD, tag=0x42, msg_code=0x7F, DW3=0x1234_5678, DW4=0x9ABC_DEF0
3215        let bytes = vec![
3216            0xAB, 0xCD, 0x42, 0x7F, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
3217        ];
3218        let msg = new_msg_req(bytes).unwrap();
3219        assert_eq!(msg.req_id(), 0xABCD);
3220        assert_eq!(msg.tag(), 0x42);
3221        assert_eq!(msg.msg_code(), 0x7F);
3222        assert_eq!(msg.dw3(), 0x1234_5678);
3223        assert_eq!(msg.dw4(), 0x9ABC_DEF0);
3224    }
3225
3226    // ── Debug impls ───────────────────────────────────────────────────────────
3227
3228    #[test]
3229    fn tlp_packet_header_debug() {
3230        let hdr = TlpPacketHeader::new(vec![0x00, 0x00, 0x20, 0x01], TlpMode::NonFlit).unwrap();
3231        let s = format!("{:?}", hdr);
3232        assert!(s.contains("TlpPacketHeader"));
3233        assert!(s.contains("format"));
3234        assert!(s.contains("length"));
3235    }
3236
3237    #[test]
3238    fn tlp_packet_debug() {
3239        let pkt =
3240            TlpPacket::new(vec![0x40, 0x00, 0x00, 0x01, 0xDE, 0xAD], TlpMode::NonFlit).unwrap();
3241        let s = format!("{:?}", pkt);
3242        assert!(s.contains("TlpPacket"));
3243        assert!(s.contains("data_len"));
3244    }
3245
3246    #[test]
3247    fn packet_mode_returns_correct_mode() {
3248        let non_flit = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::NonFlit).unwrap();
3249        assert_eq!(non_flit.mode(), TlpMode::NonFlit);
3250
3251        let flit = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::Flit).unwrap();
3252        assert_eq!(flit.mode(), TlpMode::Flit);
3253    }
3254
3255    #[test]
3256    fn tlp_packet_debug_flit() {
3257        let pkt = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::Flit).unwrap();
3258        let s = format!("{:?}", pkt);
3259        assert!(s.contains("TlpPacket"));
3260        assert!(s.contains("flit_dw0"));
3261    }
3262
3263    // ── Display tests ────────────────────────────────────────────────────
3264
3265    #[test]
3266    fn display_memread32() {
3267        // MRd32: fmt=000 type=00000, length=1, req_id=0x0400, tag=0x00, addr=0x2001FF00
3268        let bytes = vec![
3269            0x00, 0x00, 0x00, 0x01, // DW0
3270            0x04, 0x00, 0x00, 0x0F, // DW1: req_id, tag, BE
3271            0x20, 0x01, 0xFF, 0x00, // DW2: addr32
3272        ];
3273        let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
3274        let s = format!("{pkt}");
3275        assert!(s.starts_with("MRd32"));
3276        assert!(s.contains("req=0400"));
3277        assert!(s.contains("addr=2001FF00"));
3278    }
3279
3280    #[test]
3281    fn display_memwrite64() {
3282        // MWr64: fmt=011 type=00000, length=1
3283        let bytes = vec![
3284            0x60, 0x00, 0x00, 0x01, // DW0
3285            0xBE, 0xEF, 0xA5, 0x0F, // DW1
3286            0x00, 0x00, 0x00, 0x01, // DW2: addr_hi
3287            0x00, 0x00, 0x00, 0x00, // DW3: addr_lo
3288            0xDE, 0xAD, 0xBE, 0xEF, // payload
3289        ];
3290        let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
3291        let s = format!("{pkt}");
3292        assert!(s.starts_with("MWr64"));
3293        assert!(s.contains("req=BEEF"));
3294        assert!(s.contains("tag=A5"));
3295    }
3296
3297    #[test]
3298    fn display_config_type0_read() {
3299        // CfgRd0: fmt=000 type=00100, length=1
3300        let bytes = vec![
3301            0x04, 0x00, 0x00, 0x01, // DW0
3302            0x01, 0x00, 0x01, 0x0F, // DW1: req_id=0x0100, tag=1
3303            0x02, 0x18, 0x00, 0x40, // DW2: bus=2, dev=3, fn=0, reg=0x10
3304        ];
3305        let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
3306        let s = format!("{pkt}");
3307        assert!(s.starts_with("CfgRd0"));
3308        assert!(s.contains("req=0100"));
3309        assert!(s.contains("bus=02"));
3310    }
3311
3312    #[test]
3313    fn display_completion_with_data() {
3314        // CplD: fmt=010 type=01010, length=1
3315        let bytes = vec![
3316            0x4A, 0x00, 0x00, 0x01, // DW0
3317            0x20, 0x01, 0x00, 0xFC, // DW1: cpl_id=0x2001, stat=0, bc=252
3318            0x04, 0x00, 0xAB, 0x00, // DW2: req_id=0x0400, tag=0xAB
3319            0xDE, 0xAD, 0xBE, 0xEF, // payload
3320        ];
3321        let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
3322        let s = format!("{pkt}");
3323        assert!(s.starts_with("CplD"));
3324        assert!(s.contains("cpl=2001"));
3325        assert!(s.contains("req=0400"));
3326        assert!(s.contains("tag=AB"));
3327    }
3328
3329    #[test]
3330    fn display_message() {
3331        // Msg: fmt=001 type=10000 (route to RC, no data, 4DW)
3332        let bytes = vec![
3333            0x30, 0x00, 0x00, 0x00, // DW0
3334            0xAB, 0xCD, 0x01, 0x7F, // DW1: req_id=0xABCD, tag=1, code=0x7F
3335            0x00, 0x00, 0x00, 0x00, // DW2 (dw3)
3336            0x00, 0x00, 0x00, 0x00, // DW3 (dw4)
3337        ];
3338        let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
3339        let s = format!("{pkt}");
3340        assert!(s.starts_with("Msg"));
3341        assert!(s.contains("req=ABCD"));
3342        assert!(s.contains("code=7F"));
3343    }
3344
3345    #[test]
3346    fn display_fetchadd_3dw() {
3347        // FAdd: fmt=010 type=01100, length=1
3348        let bytes = vec![
3349            0x4C, 0x00, 0x00, 0x01, // DW0
3350            0xDE, 0xAD, 0x42, 0x00, // DW1: req_id=0xDEAD, tag=0x42
3351            0xC0, 0x01, 0x00, 0x04, // DW2: addr32=0xC001_0004
3352            0x00, 0x00, 0x00, 0x0A, // operand: 10
3353        ];
3354        let pkt = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
3355        let s = format!("{pkt}");
3356        assert!(s.starts_with("FAdd"));
3357        assert!(s.contains("req=DEAD"));
3358        assert!(s.contains("tag=42"));
3359        assert!(s.contains("addr=C0010004"));
3360    }
3361
3362    // ── Flit Display tests ───────────────────────────────────────────────
3363
3364    #[test]
3365    fn display_flit_nop() {
3366        let bytes = vec![0x00, 0x00, 0x00, 0x00]; // NOP: type=0x00
3367        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
3368        assert_eq!(format!("{pkt}"), "Flit:NOP");
3369    }
3370
3371    #[test]
3372    fn display_flit_memwrite32() {
3373        // MWr32: type=0x40, tc=0, ohc=0, length=4
3374        let mut bytes = vec![
3375            0x40, 0x00, 0x00, 0x04, // DW0: MWr32, tc=0, ohc=0, len=4
3376            0x00, 0x00, 0x00, 0x00, // DW1: req_id, tag, BE
3377            0xDE, 0xAD, 0x00, 0x00, // DW2: addr32
3378        ];
3379        // 4 DW payload
3380        bytes.extend_from_slice(&[0u8; 16]);
3381        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
3382        let s = format!("{pkt}");
3383        assert!(s.starts_with("Flit:MWr32"));
3384        assert!(s.contains("len=4"));
3385    }
3386
3387    #[test]
3388    fn display_flit_memread32() {
3389        // MRd32: type=0x03, tc=2, ohc=0, length=8
3390        let bytes = vec![
3391            0x03, 0x40, 0x00, 0x08, // DW0: MRd32, tc=2 (0x40=010_00000), len=8
3392            0x00, 0x00, 0x00, 0x00, // DW1
3393            0x00, 0x00, 0x10, 0x00, // DW2: addr32
3394        ];
3395        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
3396        let s = format!("{pkt}");
3397        assert!(s.starts_with("Flit:MRd32"));
3398        assert!(s.contains("len=8"));
3399        assert!(s.contains("tc=2"));
3400    }
3401
3402    #[test]
3403    fn display_flit_cfgwrite0_with_ohc() {
3404        // CfgWr0: type=0x44, tc=0, ohc=1 (bit 0 set → 1 OHC word), length=1
3405        let bytes = vec![
3406            0x44, 0x01, 0x00, 0x01, // DW0: CfgWr0, ohc=0x01 (1 OHC word)
3407            0x00, 0x00, 0x00, 0x00, // DW1
3408            0x00, 0x00, 0x00, 0x00, // DW2
3409            0x00, 0x00, 0x00, 0x0F, // OHC-A word
3410            0xAA, 0xBB, 0xCC, 0xDD, // payload (1 DW)
3411        ];
3412        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
3413        let s = format!("{pkt}");
3414        assert!(s.starts_with("Flit:CfgWr0"));
3415        assert!(s.contains("ohc=1"));
3416    }
3417
3418    #[test]
3419    fn display_flit_local_prefix() {
3420        let bytes = vec![0x8D, 0x00, 0x00, 0x00]; // LocalTlpPrefix: type=0x8D
3421        let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
3422        assert_eq!(format!("{pkt}"), "Flit:LPfx");
3423    }
3424
3425    #[test]
3426    fn display_header_standalone() {
3427        let hdr = TlpPacketHeader::new(vec![0x00, 0x00, 0x00, 0x01], TlpMode::NonFlit).unwrap();
3428        let s = format!("{hdr}");
3429        assert!(s.starts_with("MRd32"));
3430        assert!(s.contains("len=1"));
3431    }
3432}