A Rust crate for parsing PCI Express Transaction Layer Packets (TLPs).
Decode raw TLP byte streams into strongly-typed structs and trait objects. The library handles DW0 header decoding (format + type), per-type field extraction (requester ID, tag, address, operands, …), and validates format/type combinations according to the PCIe specification.
| Category | TLP Types | Trait / Constructor |
|---|---|---|
| Memory Requests | MemRead (32/64), MemWrite (32/64), MemReadLock | MemRequest / new_mem_req() |
| IO Requests | IORead, IOWrite | MemRequest / new_mem_req() |
| Config Requests | Type 0 Read/Write, Type 1 Read/Write | ConfigurationRequest / new_conf_req() |
| Completions | Cpl, CplData, CplLocked, CplDataLocked | CompletionRequest / new_cmpl_req() |
| Messages | MsgReq, MsgReqData | MessageRequest / new_msg_req() |
| Atomic Ops | FetchAdd, Swap, CompareSwap (32/64-bit operands) | AtomicRequest / new_atomic_req() |
| DMWr | Deferrable Memory Write (32/64) | MemRequest / new_mem_req() |
For a detailed breakdown of every TLP encoding, header layout, parsed fields, and byte-level examples taken from the test suite, see tlp.md.
Add to your Cargo.toml:
[dependencies]
rtlp-lib = "0.5"use rtlp_lib::{
TlpPacket, TlpFmt, TlpType, TlpMode,
new_mem_req, new_conf_req, new_cmpl_req, new_msg_req, new_atomic_req,
};
// Raw bytes captured from a PCIe trace (DW0 .. DWn)
let bytes = vec![
0x00, 0x00, 0x20, 0x01, // DW0: MemRead 3DW, length=1
0x00, 0x00, 0x20, 0x0F, // DW1: req_id=0x0000 tag=0x20 BE=0x0F
0xF6, 0x20, 0x00, 0x0C, // DW2: address32=0xF620000C
];
let packet = TlpPacket::new(bytes, TlpMode::NonFlit).unwrap();
// mode() tells you which parsing surface to use
match packet.mode() {
TlpMode::NonFlit => {
let tlp_type = packet.tlp_type().unwrap();
let tlp_format = packet.tlp_format().unwrap();
match tlp_type {
TlpType::MemReadReq | TlpType::MemWriteReq |
TlpType::MemReadLockReq |
TlpType::DeferrableMemWriteReq |
TlpType::IOReadReq | TlpType::IOWriteReq => {
// data() returns &[u8] — no extra allocation at this call site;
// new_mem_req takes impl Into<Vec<u8>> and may allocate internally.
let mr = new_mem_req(packet.data(), &tlp_format).unwrap();
println!("req_id=0x{:04X} tag=0x{:02X} addr=0x{:X}",
mr.req_id(), mr.tag(), mr.address());
}
TlpType::FetchAddAtomicOpReq |
TlpType::SwapAtomicOpReq |
TlpType::CompareSwapAtomicOpReq => {
let ar = new_atomic_req(&packet).unwrap();
println!("atomic {:?} operand0=0x{:X}", ar.op(), ar.operand0());
}
TlpType::ConfType0ReadReq | TlpType::ConfType0WriteReq |
TlpType::ConfType1ReadReq | TlpType::ConfType1WriteReq => {
let cr = new_conf_req(packet.data()).unwrap(); // accepts &[u8] directly
println!("config bus={} dev={} func={}",
cr.bus_nr(), cr.dev_nr(), cr.func_nr());
}
TlpType::Cpl | TlpType::CplData |
TlpType::CplLocked | TlpType::CplDataLocked => {
let cpl = new_cmpl_req(packet.data()).unwrap();
println!("completion status={}", cpl.cmpl_stat());
}
TlpType::MsgReq | TlpType::MsgReqData => {
let msg = new_msg_req(packet.data()).unwrap();
println!("message code=0x{:02X}", msg.msg_code());
}
_ => println!("TLP type: {:?}", tlp_type),
}
}
TlpMode::Flit => {
// Use flit_type() for flit-mode packets
println!("flit type: {:?}", packet.flit_type());
}
_ => {}
}The library exposes TlpType::is_non_posted() to distinguish requests that
require a completion from posted writes:
use rtlp_lib::TlpType;
assert!( TlpType::MemReadReq.is_non_posted());
assert!( TlpType::DeferrableMemWriteReq.is_non_posted());
assert!(!TlpType::MemWriteReq.is_non_posted()); // posted| Type | Description |
|---|---|
TlpPacket |
Full packet: DW0 header + remaining data bytes |
TlpPacketHeader |
DW0-only wrapper exposing selected header fields via public accessors |
TlpMode |
Framing mode: NonFlit (PCIe 1–5) or Flit (PCIe 6.x) |
Key TlpPacket methods:
| Method | Returns | Notes |
|---|---|---|
mode() |
TlpMode |
Explicit framing mode; use to dispatch between API surfaces |
tlp_type() |
Result<TlpType, TlpError> |
Non-flit only; returns Err(NotImplemented) for flit |
tlp_format() |
Result<TlpFmt, TlpError> |
Non-flit only |
flit_type() |
Option<FlitTlpType> |
Flit only; None for non-flit |
header() |
&TlpPacketHeader |
Non-flit only (returns dummy zeros for flit) |
data() |
&[u8] |
Payload bytes after DW0; borrows from the packet |
Other core types:
| Type | Description |
|---|---|
TlpFmt |
Format enum: NoDataHeader3DW, NoDataHeader4DW, WithDataHeader3DW, WithDataHeader4DW, TlpPrefix |
TlpType |
21-variant enum covering all decoded non-flit TLP types |
TlpError |
InvalidFormat, InvalidType, UnsupportedCombination, InvalidLength, NotImplemented, MissingMandatoryOhc |
| Type | Description |
|---|---|
FlitTlpType |
13-variant enum for flit-mode type codes (TryFrom<u8>, base_header_dw(), is_read_request()) |
FlitDW0 |
Parsed flit-mode DW0: tlp_type, tc, ohc, ts, attr, length; from_dw0(), total_bytes() |
FlitOhcA |
Parsed OHC-A word: pasid, fdwbe, ldwbe; from_bytes() |
FlitStreamWalker |
Iterator over a packed flit TLP byte stream; yields (offset, FlitTlpType, size) per TLP |
use rtlp_lib::{TlpPacket, TlpMode, FlitStreamWalker, FlitTlpType};
// Parse a single flit TLP
let nop_bytes = vec![0x00u8, 0x00, 0x00, 0x00];
let pkt = TlpPacket::new(nop_bytes, TlpMode::Flit).unwrap();
assert_eq!(pkt.flit_type(), Some(FlitTlpType::Nop));
assert_eq!(pkt.mode(), TlpMode::Flit); // explicit mode check
// Walk a packed stream of back-to-back flit TLPs
let stream: &[u8] = &[/* packed bytes */];
for result in FlitStreamWalker::new(stream) {
let (offset, typ, size) = result.unwrap();
println!("TLP at offset {}: {:?} ({} bytes)", offset, typ, size);
}| Trait | Fields | Constructor |
|---|---|---|
MemRequest |
address(), req_id(), tag(), ldwbe(), fdwbe() |
new_mem_req(bytes, &fmt) |
ConfigurationRequest |
req_id(), tag(), bus_nr(), dev_nr(), func_nr(), ext_reg_nr(), reg_nr() |
new_conf_req(bytes) |
CompletionRequest |
cmpl_id(), cmpl_stat(), bcm(), byte_cnt(), req_id(), tag(), laddr() |
new_cmpl_req(bytes) |
MessageRequest |
req_id(), tag(), msg_code(), dw3(), dw4() |
new_msg_req(bytes) |
AtomicRequest |
op(), width(), req_id(), tag(), address(), operand0(), operand1() |
new_atomic_req(&pkt) |
Note: All
bytesparameters acceptimpl Into<Vec<u8>>— you can passpkt.data()(&[u8]) directly without calling.to_vec(). Onlynew_mem_reqadditionally requires&TlpFmt.
| Type | Variants |
|---|---|
AtomicOp |
FetchAdd, Swap, CompareSwap |
AtomicWidth |
W32, W64 |
Every decoding step returns Result<_, TlpError>:
| Error | Meaning |
|---|---|
InvalidFormat |
The 3-bit Fmt field does not match any known format |
InvalidType |
The 5-bit Type field does not match any known encoding |
UnsupportedCombination |
Valid Fmt + Type individually, but not a legal pair (e.g. DMWr with NoData) |
InvalidLength |
Byte slice is too short for the expected header + payload |
NotImplemented |
Feature exists in the API but is not yet implemented (e.g. TlpPacketHeader::new with Flit) |
MissingMandatoryOhc |
Flit TLP type requires an OHC word that was absent (IOWr/CfgWr) |
The crate has 212 passing tests (0 ignored):
| Category | File | Passes | Ignored |
|---|---|---|---|
| Unit tests | src/lib.rs |
56 | 0 |
| API contract tests | tests/api_tests.rs |
77 | 0 |
| Non-flit integration tests | tests/non_flit_tests.rs |
25 | 0 |
| Flit mode tests | tests/flit_mode_tests.rs |
45 | 0 |
| Doc tests | src/lib.rs |
9 | 0 |
cargo test # run all 212 tests
cargo test --lib # unit tests only
cargo test --test non_flit_tests # non-flit integration tests only
cargo test --test flit_mode_tests # flit mode tests (all tiers)
cargo test --doc # doc examples onlySee TESTS.md for the full test structure and flit mode tier descriptions.
Enable the serde feature to derive Serialize/Deserialize on public value types:
[dependencies]
rtlp-lib = { version = "0.5", features = ["serde"] }Supported types: TlpMode, TlpError, TlpFmt, TlpType, AtomicOp, FlitTlpType, FlitDW0, FlitOhcA
Not supported: TlpPacket and TlpPacketHeader — these contain TlpHeader, which uses a bitfield macro that does not support serde derives. If you need to serialize parsed packet data, extract the relevant fields into your own serde-compatible struct.
Forward compatibility note:
TlpModeandFlitTlpTypeare#[non_exhaustive]. New variants added in future versions will serialize correctly on the new version, but deserializing a new variant on an older version will fail. If you persist serialized data to a schema, pin yourrtlp-libversion accordingly.
- docs/api_guide.md — user-facing guide: parsing flow, mode dispatch, ownership/lifetimes, error handling, deprecated-API migration, and how to test your own code against the library.
- TESTS.md — test structure, tier descriptions, FM_* byte-vector constants reference, and running individual test suites.
- docs/tlp_reference.md — byte-level TLP examples for both non-flit (PCIe 1–5) and flit mode (PCIe 6.0 Base Spec), DW0 layout diagrams, and test inventory.
- tlp.md — supplementary PCIe TLP encoding reference.
- docs.rs — published rustdoc for the released version.
cargo doc --open— rustdoc for the current local build.
Licensed under the 3-Clause BSD License — see LICENSE.