Expand description
ULE — Unidirectional Lightweight Encapsulation (RFC 4326) + Extension Headers (RFC 5163): IP over MPEG-2 Transport Streams.
ULE encapsulates a network-layer PDU (an IP datagram, Ethernet frame, etc.)
into a SubNetwork Data Unit (Sndu) and maps it into the payload of
MPEG-2 TS packets on a single PID (RFC 4326 §3, §4).
This crate implements:
Sndu— the SNDU wire structure (RFC 4326 §4, Figure 1): theDbit + 15-bitLength+ 16-bitType, an optional 6-byte Destination NPA address (present iffD = 0), the PDU, and the 4-byte CRC-32 trailer.Lengthand the CRC are recomputed on serialize from the typed fields — there is no raw passthrough.TypeField— the §4.4 split at0x0600: a Next-Header (H-LEN/H-Type) below, an EtherType at or above.ExtensionHeader/PayloadChain— the chained extension-header model (RFC 4326 §5, RFC 5163 §3): Optional headers (H-LEN = 1..=5, total2·H-LENbytes) and a terminating EtherType or Mandatory header (Test-SNDU 0x00, Bridged-Frame 0x01, TS-Concat 0x02, PDU-Concat 0x03).UleReceiver— TS-packet de-fragmentation/reassembly (RFC 4326 §6, §7): PUSI + 1-byte Payload Pointer handling, fragmentation across packets, packing of multiple SNDUs per packet, and End-Indicator / 0xFF padding.
The CRC-32 is the MPEG-2 / DSM-CC CRC (poly 0x04C11DB7, init
0xFFFFFFFF, MSB-first, no reflection, no final XOR — RFC 4326 §4.6),
reused from dvb_common::crc32_mpeg2; this is verified byte-exact against
RFC 4326 Appendix B’s worked example in the crate’s fixture test.
#![no_std] + alloc; depends only on dvb-common.
§Examples
Build an IPv4 SNDU (L2 filtering, D = 0) from typed fields and round-trip
it:
use ule::{Sndu, TypeField};
let pdu = [0x45u8, 0x00, 0x00, 0x14]; // start of an IPv4 header
let sndu = Sndu::new(
TypeField::EtherType(0x0800),
Some([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
&pdu,
);
let mut buf = vec![0u8; sndu.serialized_len()];
sndu.serialize_into(&mut buf).unwrap();
assert_eq!(Sndu::parse(&buf).unwrap(), sndu);§Runnable examples
Run with cargo run -p ule --example <name>.
§build_sndu
/// Build a ULE SNDU from typed fields, serialize it (recomputing Length +
/// CRC-32), and dump the wire bytes.
///
/// ```sh
/// cargo run -p ule --example build_sndu
/// ```
use ule::{Sndu, TypeField};
fn main() {
// An IPv6 SNDU with an NPA destination address (D=0), carrying a short
// opaque PDU. Mirrors the RFC 4326 Appendix B shape (Type 0x86DD, D=0).
let pdu = [
0x60, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3a, 0x40, 0xDE, 0xAD, 0xBE, 0xEF,
];
let sndu = Sndu::new(
TypeField::EtherType(0x86DD),
Some([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
&pdu,
);
let mut bytes = vec![0u8; sndu.serialized_len()];
let n = sndu.serialize_into(&mut bytes).unwrap();
println!("SNDU: {n} bytes");
println!("D-bit: {}", u8::from(sndu.d_bit()));
println!(
"Type: {} (0x{:04X})",
sndu.type_field(),
sndu.type_field().to_u16()
);
println!("Length field: {}", sndu.length_field());
if let Some(npa) = sndu.dest_address {
print!("NPA dest:");
for b in &npa {
print!(" {b:02X}");
}
println!();
}
print!("wire bytes:");
for b in &bytes {
print!(" {b:02X}");
}
println!();
// Round-trip sanity (parse re-validates the CRC).
assert_eq!(Sndu::parse(&bytes).unwrap(), sndu);
println!("round-trip (incl. CRC check): OK");
}§receive_sndu
/// Read the committed RFC 4326 Appendix B SNDU fixture, fragment it across two
/// MPEG-2 TS packet payloads, and reassemble it with `UleReceiver` — proving
/// PUSI + Payload-Pointer de-fragmentation.
///
/// ```sh
/// cargo run -p ule --example receive_sndu
/// ```
use std::fs;
use ule::{Sndu, UleReceiver};
fn main() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/appendix_b.bin");
let sndu_bytes = match fs::read(path) {
Ok(d) => d,
Err(e) => {
eprintln!("fixture not available ({e}); nothing to do");
return;
}
};
// Parse the whole SNDU once for reference.
let sndu = Sndu::parse(&sndu_bytes).unwrap();
println!("Appendix B SNDU: {} bytes", sndu_bytes.len());
println!(
"Type: {} (0x{:04X})",
sndu.type_field(),
sndu.type_field().to_u16()
);
println!("PDU: {} bytes", sndu.pdu().len());
// Fragment across two TS packet payloads at byte 20.
let split = 20;
let mut rx = UleReceiver::new();
// Packet 1: PUSI=1, Payload Pointer = 0 (SNDU starts immediately).
let mut p1 = vec![0x00u8]; // payload pointer
p1.extend_from_slice(&sndu_bytes[..split]);
let done = rx.push(&p1, true);
println!(
"after packet 1 (PUSI=1, PP=0): {} SNDU(s) complete",
done.len()
);
assert!(done.is_empty(), "SNDU should still be in reassembly");
// Packet 2: PUSI=0 continuation with the rest + 0xFF padding.
let mut p2 = sndu_bytes[split..].to_vec();
p2.extend_from_slice(&[0xFF, 0xFF, 0xFF]); // End Indicator + padding
let done = rx.push(&p2, false);
println!(
"after packet 2 (PUSI=0 continuation): {} SNDU(s) complete",
done.len()
);
assert_eq!(done.len(), 1, "exactly one reassembled SNDU");
assert_eq!(done[0], sndu_bytes, "reassembled bytes match the fixture");
let re = Sndu::parse(&done[0]).unwrap();
assert_eq!(re, sndu, "reassembled SNDU parses identically");
println!("reassembly byte-exact + CRC valid: OK");
}Structs§
- Payload
Chain - The decoded payload area of an SNDU (RFC 4326 §5): a chain of extension
headers terminated by a final
TypeField(an EtherType, or the introducing Type of a trailing Mandatory header) and the opaque PDU bytes. - Sndu
- A parsed/owned SubNetwork Data Unit (RFC 4326 §4).
- UleReceiver
- A de-fragmenting ULE receiver (RFC 4326 §7).
Enums§
- Error
- A ULE parse / serialize error.
- Extension
Header - A single ULE extension header in a chain (RFC 4326 §5).
- MandatoryH
Type - Typed H-Type for a Mandatory extension header (
H-LEN = 0, RFC 4326 §5). - OptionalH
Type - Typed H-Type for an Optional extension header (
H-LEN = 1..=5, RFC 4326 §5). - Type
Field - A decoded ULE Type field (RFC 4326 §4.4).
Constants§
- BASE_
HEADER_ LEN - Size in bytes of the SNDU base header (
D+Length+Type). - CRC_LEN
- Size in bytes of the CRC-32 trailer.
- END_
INDICATOR - The two-byte value (
D = 1,Length = 0x7FFF) that marks an End Indicator. - END_
INDICATOR_ LENGTH - The 15-bit
Lengthvalue of an End Indicator — all length bits set. - ETHERTYPE_
BOUNDARY - The boundary between Next-Header codes and EtherTypes (RFC 4326 §4.4): decimal 1536. Values below are Next-Headers; values at or above are EtherTypes.
- ETHERTYPE_
IPV4 - IPv4 EtherType (RFC 4326 §4.7.2).
- ETHERTYPE_
IPV6 - IPv6 EtherType (RFC 4326 §4.7.3).
- H_
TYPE_ BRIDGED_ FRAME - H-Type of the Bridged-Frame mandatory extension header (RFC 4326 §5.2).
- H_
TYPE_ EXT_ PADDING - H-Type of the Extension-Padding optional extension header (RFC 4326 §5.3),
IANA value
0x100→H-Typebyte0x00,H-LEN1..=5. - H_
TYPE_ PDU_ CONCAT - H-Type of the PDU-Concat mandatory extension header (RFC 5163 §3.2).
- H_
TYPE_ TEST_ SNDU - H-Type of the Test-SNDU mandatory extension header (RFC 4326 §5.1).
- H_
TYPE_ TIMESTAMP - H-Type of the TimeStamp optional extension header (RFC 5163 §3.3),
decimal 257 →
H-Typebyte0x01withH-LEN = 3. - H_
TYPE_ TS_ CONCAT - H-Type of the MPEG-2 TS-Concat mandatory extension header (RFC 5163 §3.1).
- NPA_LEN
- Size in bytes of the Destination NPA address (present when
D = 0). - PADDING_
BYTE - The 0xFF byte used for TS-payload padding / stuffing (§4.3, §6).
- TS_
PAYLOAD_ LEN - The number of TS-packet payload bytes when AFC =
01(payload only): the 188-byte packet minus its 4-byte header (RFC 4326 §3).
Functions§
- is_
end_ indicator trueif the two bytes at the start ofdataare a ULE End Indicator (0xFFFF:D = 1,Length = 0x7FFF) — no further SNDUs in this TS packet (RFC 4326 §4.3).
Type Aliases§
- Result
- Result alias for ULE parsing.