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