Skip to main content

Crate ule

Crate ule 

Source
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): the D bit + 15-bit Length + 16-bit Type, an optional 6-byte Destination NPA address (present iff D = 0), the PDU, and the 4-byte CRC-32 trailer. Length and the CRC are recomputed on serialize from the typed fields — there is no raw passthrough.
  • TypeField — the §4.4 split at 0x0600: 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, total 2·H-LEN bytes) 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§

PayloadChain
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.
ExtensionHeader
A single ULE extension header in a chain (RFC 4326 §5).
MandatoryHType
Typed H-Type for a Mandatory extension header (H-LEN = 0, RFC 4326 §5).
OptionalHType
Typed H-Type for an Optional extension header (H-LEN = 1..=5, RFC 4326 §5).
TypeField
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 Length value 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 0x100H-Type byte 0x00, H-LEN 1..=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-Type byte 0x01 with H-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
true if the two bytes at the start of data are 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.