Skip to main content

Crate rmt_flute

Crate rmt_flute 

Source
Expand description

Multicast object-delivery wire formats: ALC / LCT / FLUTE / NORM.

This crate parses and serializes the binary headers used to deliver files and streams over IP multicast. Every format here is IETF RMT (Reliable Multicast Transport) — RFC 5651, RFC 5775, RFC 6726, RFC 5740. No broadcast-specific standard is implemented by this crate.

Several delivery systems are layered on top of these formats and are consumers of this crate rather than owners of it: DVB (DVB-IPTV and DVB-MABR / ETSI TS 103 769 file delivery), 3GPP (MBMS/eMBMS download delivery), and ATSC 3.0 (ROUTE, A/331 Annex A — written as a profile-and-delta on RFC 5651/5775/6726).

Renamed from dvb-flute at 0.4.0: the old name named one consumer of an IETF standard rather than the standard itself, and read as a layering error once non-DVB consumers needed to depend on it. All dvb-flute versions are yanked; there is no shim.

Implements:

  • LctHeader — the Layered Coding Transport header (RFC 5651 §5). The fixed first word carries V/C/PSI/S/O/H/A/B, HDR_LEN and the Codepoint; the C, S, O and H flags then drive the byte-widths of the CCI, TSI and TOI fields (4*(C+1), 4*S+2*H, 4*O+2*H bytes). The shared H half-word feeds both TSI and TOI. Flag bits and HDR_LEN are recomputed on serialize from the typed field lengths — there is no raw passthrough.
  • HeaderExtension — the LCT/NORM header-extension chain (RFC 5651 §5.2): variable-length (HET 0..=127, carries HEL) and fixed-length (HET 128..=255, one word) forms; with ExtTime (EXT_TIME) and the LctExtType registry (EXT_NOP/EXT_AUTH/EXT_TIME).
  • AlcPacket — an Asynchronous Layered Coding packet (RFC 5775): LCT header + an opaque FEC Payload ID + the encoding-symbol payload, plus EXT_FTI (HET 64) and the Small-Block-Systematic FecPayloadId128.
  • ExtFdt / ExtCenc — the FLUTE (RFC 6726) fixed-length LCT extensions EXT_FDT (HET 192) and EXT_CENC (HET 193), plus the TOI = 0 FDT-Instance convention. The FDT Instance body is XML and is out of scope of this binary crate — it rides as the packet payload.
  • NormCommonHeader + NormData / NormInfo / NormCmd / NormFeedback — the NORM (RFC 5740) common header and message types (NORM_INFO / NORM_DATA / NORM_CMD / NORM_NACK / NORM_ACK / NORM_REPORT).
  • SourceBlockPartition — the FEC Building Block’s (RFC 5052 §9.1) scheme-agnostic Block Partitioning Algorithm: given a transport object’s Transfer-Length, Encoding-Symbol-Length and Maximum-Source-Block-Length, derive the number of source blocks and each block’s length in symbols. The common substrate dvb-mabr and atsc3-route both need (issue #944) without hardcoding any FEC scheme’s FEC Payload ID or Scheme-specific OTI layout.

FEC Payload ID bit layouts are FEC-scheme dependent (RFC 5052 / the FEC Scheme document) and are not defined by ALC/NORM themselves; this crate exposes them as opaque byte slices (the caller supplies the length), with FecPayloadId128 provided as one concrete illustrative layout.

All integer fields are big-endian. #![no_std] + alloc; depends only on broadcast-common.

§Examples

Build an LCT header from typed fields (flag-driven CCI/TSI/TOI widths) and round-trip it:

use rmt_flute::{LctHeader, LCT_VERSION};

let cci = [0u8; 4]; // C = 0
let tsi = [0u8; 4]; // S = 1, H = 0
let hdr = LctHeader {
    version: LCT_VERSION,
    psi: 0,
    close_session: false,
    close_object: false,
    codepoint: 0,
    cci: &cci,
    tsi: &tsi,
    toi: &[],
    extensions: vec![],
};
let mut buf = vec![0u8; hdr.serialized_len()];
hdr.serialize_into(&mut buf).unwrap();
let (re, used) = LctHeader::parse(&buf).unwrap();
assert_eq!(used, buf.len());
assert_eq!(re, hdr);

§Runnable examples

Run with cargo run -p rmt-flute --example <name>.

§build_lct

/// Build a FLUTE/ALC packet from typed fields — an LCT header (TOI = 0) with an
/// EXT_FDT extension, a Small-Block-Systematic FEC Payload ID, and an XML FDT
/// Instance payload — then serialize it (recomputing all flags + HDR_LEN) and
/// dump the wire bytes.
///
/// ```sh
/// cargo run -p rmt-flute --example build_lct
/// ```
use rmt_flute::{
    AlcPacket, ExtFdt, FLUTE_VERSION, FecPayloadId128, HET_EXT_FDT, LCT_VERSION, LctHeader,
};

fn main() {
    // TOI = 0 (FDT Instance), so the TOI field is present and zero (O=0, H=1
    // gives a 2-byte TOI; we use O=1 here for a clean 4-byte zero TOI).
    let cci = [0u8, 0, 0, 1];
    let tsi = [0u8, 0, 0, 0x2A]; // S=1, H=0 — non-zero TSI (required by ALC)
    let toi = [0u8, 0, 0, 0]; // O=1, H=0 — TOI = 0 (FDT Instance)

    // EXT_FDT (HET 192, fixed): FLUTE v2, FDT Instance ID 5.
    let ext_fdt = ExtFdt {
        version: FLUTE_VERSION,
        instance_id: 5,
    };
    let mut fdt_scratch = [0u8; 3];
    let ext = ext_fdt.to_extension(&mut fdt_scratch).unwrap();

    let lct = LctHeader {
        version: LCT_VERSION,
        psi: 0,
        close_session: false,
        close_object: false,
        codepoint: 0, // Compact No-Code FEC (Encoding ID 0)
        cci: &cci,
        tsi: &tsi,
        toi: &toi,
        extensions: vec![ext],
    };

    // FEC Payload ID (Small Block Systematic, fec_id 128/129).
    let fpid = FecPayloadId128 {
        source_block_number: 0,
        source_block_length: 1,
        encoding_symbol_id: 0,
    };
    let mut fpid_bytes = [0u8; 8];
    fpid.serialize_into(&mut fpid_bytes).unwrap();

    // A tiny (truncated) FDT Instance XML body as the packet payload. The XML
    // itself is out of scope of this crate — it rides as opaque payload.
    let xml = br#"<?xml version="1.0"?><FDT-Instance Expires="0"/>"#;

    let pkt = AlcPacket::new(lct, &fpid_bytes, xml);

    let mut bytes = vec![0u8; pkt.serialized_len()];
    let n = pkt.serialize_into(&mut bytes).unwrap();

    println!("ALC/FLUTE packet: {n} bytes");
    println!("LCT version: {}", pkt.lct.version);
    println!(
        "flags  C={} S={} O={} H={}",
        pkt.lct.c_flag(),
        pkt.lct.s_flag(),
        pkt.lct.o_flag(),
        pkt.lct.h_flag()
    );
    println!("HDR_LEN: {} words", pkt.lct.hdr_len());
    println!("TOI bytes: {:02X?} (0 => FDT Instance)", pkt.lct.toi);
    println!("EXT_FDT instance_id: {}", ext_fdt.instance_id);
    println!("FEC Payload ID: {:02X?}", pkt.fec_payload_id);
    println!("payload (XML, opaque): {} bytes", pkt.payload.len());
    print!("wire bytes:");
    for b in &bytes {
        print!(" {b:02X}");
    }
    println!();

    // Round-trip sanity (FEC Payload ID len = 8 for fec_id 128/129).
    let re = AlcPacket::parse(&bytes, rmt_flute::FEC_PAYLOAD_ID_128_LEN).unwrap();
    assert_eq!(re, pkt);
    // The EXT_FDT extension decodes back.
    let re_fdt = ExtFdt::parse(re.lct.extensions[0].content).unwrap();
    assert_eq!(re.lct.extensions[0].het, HET_EXT_FDT);
    assert_eq!(re_fdt, ext_fdt);
    println!("round-trip: OK");
}

§parse_flute

/// Read the committed FLUTE FDT-packet fixture, parse the ALC/LCT framing +
/// EXT_FDT extension, and report the decoded fields (the XML FDT Instance body
/// is left opaque — out of scope of this binary crate).
///
/// ```sh
/// cargo run -p rmt-flute --example parse_flute
/// ```
use std::fs;

use rmt_flute::{AlcPacket, ExtFdt, FEC_PAYLOAD_ID_128_LEN, FecPayloadId128, HET_EXT_FDT};

fn main() {
    // Fixtures live in the workspace-shared `fixtures/rmt-flute/` directory,
    // not under the crate. This example previously pointed at a
    // `tests/fixtures/` path that has never existed, and its "nothing to do"
    // fallback silently swallowed the failure — so it never actually ran.
    // Missing fixture is now a hard error: an example that quietly does
    // nothing is worse than one that fails.
    let path = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/../fixtures/rmt-flute/flute_fdt.bin"
    );
    let data = fs::read(path)
        .unwrap_or_else(|e| panic!("committed fixture {path} could not be read: {e}"));

    // FLUTE default FEC is Compact No-Code (Encoding ID 0): a 16-bit Source
    // Block Number + 16-bit Encoding Symbol ID = 4 bytes. But this fixture uses
    // the Small-Block-Systematic 8-byte FEC Payload ID for a richer example.
    let pkt = AlcPacket::parse(&data, FEC_PAYLOAD_ID_128_LEN).unwrap();

    println!("ALC/FLUTE packet: {} bytes", data.len());
    println!("LCT version: {}", pkt.lct.version);
    println!(
        "flags  C={} S={} O={} H={}  close_session={} close_object={}",
        pkt.lct.c_flag(),
        pkt.lct.s_flag(),
        pkt.lct.o_flag(),
        pkt.lct.h_flag(),
        pkt.lct.close_session,
        pkt.lct.close_object,
    );
    println!("HDR_LEN: {} words", pkt.lct.hdr_len());
    println!("CCI: {:02X?}", pkt.lct.cci);
    println!("TSI: {:02X?}", pkt.lct.tsi);
    println!("TOI: {:02X?}  (0 => FDT Instance)", pkt.lct.toi);

    for ext in &pkt.lct.extensions {
        if ext.het == HET_EXT_FDT {
            let fdt = ExtFdt::parse(ext.content).unwrap();
            println!(
                "EXT_FDT: FLUTE v{} FDT Instance ID {}",
                fdt.version, fdt.instance_id
            );
        } else {
            println!("extension HET {} ({} bytes)", ext.het, ext.serialized_len());
        }
    }

    let fpid = FecPayloadId128::parse(pkt.fec_payload_id).unwrap();
    println!(
        "FEC Payload ID: sbn={} sbl={} esi={}",
        fpid.source_block_number, fpid.source_block_length, fpid.encoding_symbol_id
    );
    println!(
        "FDT Instance XML payload ({} bytes, opaque): {}",
        pkt.payload.len(),
        core::str::from_utf8(pkt.payload).unwrap_or("<non-utf8>")
    );

    // Byte-exact round-trip.
    let mut out = vec![0u8; pkt.serialized_len()];
    let n = pkt.serialize_into(&mut out).unwrap();
    assert_eq!(n, data.len());
    assert_eq!(out, data, "serialize must be byte-identical to the fixture");
    println!("byte-exact round-trip: OK");
}

Structs§

AlcPacket
A parsed ALC packet (RFC 5775 §4.1): an LctHeader followed by an opaque FEC Payload ID and the encoding-symbol payload.
ExtCenc
EXT_CENC — FDT Instance Content Encoding Header (RFC 6726 §3.4.3, HET = 193, fixed-length).
ExtFdt
EXT_FDT — FDT Instance Header (RFC 6726 §3.4.1, HET = 192, fixed-length).
ExtTime
A decoded EXT_TIME header extension (RFC 5651 §5.2.2, HET = 2).
FecPayloadId128
FEC Payload ID for Small Block Systematic codes (fec_id = 128/129), reproduced from RFC 5445 as an illustrative layout (RFC 5775 itself defines no FEC Payload ID format). 8 bytes: a 32-bit source_block_number, a 16-bit source_block_length, and a 16-bit encoding_symbol_id.
HeaderExtension
One LCT/NORM header extension (RFC 5651 §5.2 / RFC 5740 §4.1).
LctHeader
A decoded LCT header (RFC 5651 §5.1).
NormCmd
A NORM_CMD message (RFC 5740 §4.2.3): common header + sender word + an 8-bit sub-type selecting the body, then the sub-type-specific content (kept opaque, plus the extension chain).
NormCommonHeader
The NORM common message header (RFC 5740 §4.1, Figure 1): 8 bytes carrying version | type | hdr_len | sequence | source_id.
NormData
NORM_DATA fixed header beyond the common header + sender word (RFC 5740 §4.2.1, Figure 4): flags | fec_id | object_transport_id | fec_payload_id.
NormFeedback
A NORM feedback message — NORM_NACK (type 4) or NORM_ACK (type 5) (RFC 5740 §4.3): common header, server_id, instance_id, a 16-bit field that is reserved for NACK / ack_type|ack_id for ACK, then grtt_response_sec/grtt_response_usec, extensions, and opaque payload.
NormInfo
NORM_INFO fixed header beyond the common header + sender word (RFC 5740 §4.2.2, Figure 8): flags | fec_id | object_transport_id.
SenderWord
The shared sender word carried by NORM_DATA / NORM_INFO / NORM_CMD (RFC 5740 §4.2): instance_id(16) | grtt(8) | backoff(4) | gsize(4).
SourceBlockPartition
The source-block structure of a transport object, per RFC 5052 §9.1’s Block Partitioning Algorithm.

Enums§

CencAlgorithm
Content-encoding algorithm of an FDT Instance payload (RFC 6726 §3.4.3).
Error
A parse / serialize error.
LctExtType
A known LCT Header Extension Type (RFC 5651 §5.2.1 / §9.2).
NormAckType
NORM ack_type (RFC 5740 §4.2.3, shared by NORM_CMD(ACK_REQ) and NORM_ACK).
NormCmdType
NORM_CMD sub-type (RFC 5740 §4.2.3).
NormMessageType
NORM message type (RFC 5740 §4.1).

Constants§

ALC_HET_EXT_FTI
HET for ALC’s EXT_FTI (FEC Object Transmission Information) — RFC 5775 §4.2. Variable-length form (HET 0..=127). The HEC body is FEC-scheme dependent.
COMMON_HEADER_LEN
Size of the NORM common message header in bytes.
FDT_INSTANCE_ID_MAX
Maximum FDT Instance ID (20-bit field).
FEC_PAYLOAD_ID_128_LEN
Wire size in bytes of a FecPayloadId128.
FEEDBACK_FIXED_LEN
Fixed-header byte size of a NORM feedback message before extensions: common(8) + server_id(4) + instance_id+ack/reserved(4) + 2×grtt(8) = 24.
FIXED_HEADER_LEN
Size in bytes of the fixed first word (V/C/PSI/S/O/H/Res/A/B + HDR_LEN + CP).
FIXED_HET_MIN
The HET boundary: values < FIXED_HET_MIN are variable-length (carry an HEL); values >= FIXED_HET_MIN are fixed-length (one 32-bit word, no HEL).
FLUTE_VERSION
FLUTE version carried in EXT_FDT’s V field (RFC 6726 = 2).
HET_EXT_CC
HET for NORM EXT_CC (variable-length, hel = 3) — RFC 5740 §4.2.3.
HET_EXT_CENC
HET for EXT_CENC (FDT Instance Content Encoding) — RFC 6726 §3.4.3. Fixed.
HET_EXT_FDT
HET for EXT_FDT (FDT Instance Header) — RFC 6726 §3.4.1. Fixed-length.
HET_EXT_NOP
HET for EXT_NOP (No-Operation) — RFC 5651 §5.2.1.
HET_EXT_RATE
HET for NORM EXT_RATE (fixed-length) — RFC 5740 §4.2.3.
HET_EXT_TIME
HET for EXT_TIME (Timing information) — RFC 5651 §5.2.2.
LCT_HET_EXT_AUTH
HET for EXT_AUTH (Packet Authentication) — RFC 5651 §5.2.1.
LCT_VERSION
LCT version number for RFC 5651.
NORM_FLAG_EXPLICIT
Repair segment meeting a specific erasure.
NORM_FLAG_FILE
Object is file-based.
NORM_FLAG_INFO
NORM_INFO is available for this object.
NORM_FLAG_REPAIR
Message is a repair transmission.
NORM_FLAG_STREAM
Object is a NORM_OBJECT_STREAM (enables the payload_* fields).
NORM_FLAG_UNRELIABLE
No repair will be supplied (one-shot best-effort).
NORM_HET_EXT_AUTH
HET for NORM EXT_AUTH (variable-length) — RFC 5740 §8.5.
NORM_HET_EXT_FTI
HET for NORM EXT_FTI (variable-length) — RFC 5740 §4.2.1.
NORM_INFO_FIXED_LEN
Fixed header size of NORM_INFO before header extensions: common(8) + sender(4) + flags-word(4) = 16 bytes.
NORM_NODE_ANY
Reserved NormNodeId: wildcard / any.
NORM_NODE_NONE
Reserved NormNodeId: invalid / none.
NORM_VERSION
NORM protocol version (RFC 5740 = 1).
PSI_SPI
ALC PSI bit: SPI (Source Packet Indicator) — RFC 5775 §2.1, the high PSI bit. SPI = 1 ⇒ source-data FEC Payload ID format; 0 ⇒ repair-data format.
SENDER_WORD_LEN
Size of the shared sender word (instance_id/grtt/backoff/gsize) in bytes.
TOI_FDT
The reserved TOI value for FDT Instances (RFC 6726 §3.3). FDT Instances are carried in ALC packets with TOI = 0.
USE_ERT
Use-field bit: Expected Residual Time present.
USE_SCT_HIGH
Use-field bit: Sender Current Time, high 32 bits present.
USE_SCT_LOW
Use-field bit: Sender Current Time, low 32 bits present.
USE_SLC
Use-field bit: Session Last Changed time present.
WORD
Bytes in one 32-bit word.

Functions§

chain_len
Total serialized length (bytes) of a header-extension chain.
parse_chain
Parse a chain of header extensions occupying exactly data. Every byte of data must be consumed; a trailing partial extension is an error.
serialize_chain
Serialize a header-extension chain into out. Returns bytes written.

Type Aliases§

Result
Result alias for this crate’s parsing / serialization.