Skip to main content

Crate broadcast_common

Crate broadcast_common 

Source
Expand description

§broadcast-common

crates.io docs.rs License

The foundation crate of the rust-broadcast workspace: the symmetric Parse/Serialize traits, CRC-32/MPEG-2, BCD and MJD time codecs, bit-field I/O, the 33-bit/PCR clock arithmetic, checked field-width helpers, and the container-mux and streaming-stage traits every sibling crate builds on.

§What it does / what it does not

Nearly every crate in the workspace depends on broadcast-common, so it holds only the code that must be defined exactly once: the traits that make every wire type parse/serialize symmetric, the small codecs the DVB/MPEG family shares (CRC, BCD, MJD, odd parity, hex), the clock arithmetic that PTS/DTS, PCR, SCTE-35 and SRT all need, the range-checking helpers serializers use instead of silent masking, and the vocabulary traits of the muxing hub (Unpackage/Package/Encrypt/Decrypt) and the incremental Stage driver. Use it directly when you implement a new wire type in the workspace’s style or need one of those primitives.

It deliberately has no shared error enum (each crate owns a domain Error, reached through type Error on the traits), no CRC-8 (only dvb-bbframe needs it), no concrete media or codec types, and no heavy dependencies (chrono, serde are optional; hex is the only unconditional one).

§Standards supported

This crate implements primitives rather than a wire format, so the table lists the specification each primitive follows.

StandardEditionScopeStatusNotes
ITU-T H.222.0 / ISO/IEC 13818-1H.222.0 v9 (08/2023), verified against the vendored copyAnnex A CRC decoder model, the CRC-32 (crc32_mpeg2); §2.4.3.2 PUSI unit delimiting (pusi); §2.4.3.3 legal duplicate packets (ts_dup); §2.4.3.4/§2.4.3.7 PCR (27 MHz) and 33-bit PTS/DTS clocks and the 5-byte marker-bit timestamp field (clock33)Full for the primitives listedHigher layers (TS, PES, PSI) live in mpeg-ts, mpeg-pes, dvb-si.
ETSI EN 300 468V1.20.1 (vendored; the 2038 MJD wrap rule is from this edition)Annex A CRC-32 (same polynomial as above); Annex C MJD + BCD UTC time and BCD durations (time, bcd)Full for the codecsMJD range 1948-08-05 ..= 2128-01-09. Integer proleptic-Gregorian arithmetic (no closed-form Annex C formula limit).
ETSI TS 102 773V1.4.1Annex A CRC-32 for T2-MI packets (same crc32_mpeg2)Full for the CRCThe packet format is in dvb-t2mi.
ETSI EN 300 706V1.2.1§8.1 odd-parity protected byte (parity)Partial: parity primitive onlyAlso used for CEA-608 line-21 bytes. Detect-only by design.
ISO/IEC 23001-7 (CENC)not pinned (not vendored; unverified)§4 schm.scheme_type identity: cenc, cbcs (cenc::CencScheme)Scheme identity onlyCrypto boxes live in transmux; #EXT-X-KEY signalling in broadcast-hls.
RFC 1982 serial-number arithmeticn/aseq::SeqSpace wrap-safe sequence comparison (RTP 16-bit, SRT 31-bit)Comparable, not spec-citedImplementation policy, not defined by RTP/SRT.

§Feature flags

FeatureDefaultWhat it enables
stdyesLinks std: std::error::Error glue, Timestamp::from_instant, and chrono’s clock when chrono is on. Off gives #![no_std] + alloc.
chrononochrono::DateTime<Utc> conversions in time (decode_mjd_bcd_utc, encode_mjd_bcd_utc, …). Without it the plain MjdBcdDateTime and duration helpers stay available.
serdenoserde::Serialize on the shared spec types (currently cenc::CencScheme).
[dependencies]
broadcast-common = "10"
# embedded (no_std + alloc):
# broadcast-common = { version = "9", default-features = false }

§Quick start

use broadcast_common::{bcd, clock33, crc32_mpeg2, Parse, Serialize};

// CRC-32/MPEG-2 over a real PAT section (from the committed m6-single.ts fixture):
// the CRC of the body equals the trailing CRC_32 field.
let section = [
    0x00, 0xB0, 0x0D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0x04, 0x01, 0xE0, 0x64,
    0xF9, 0xB4, 0x63, 0xEF,
];
let crc = crc32_mpeg2::compute(&section[..section.len() - 4]);
assert_eq!(crc, 0xF9B4_63EF);

// BCD and 33-bit clock arithmetic.
assert_eq!(bcd::from_bcd_byte(0x42), Some(42));
assert_eq!(clock33::add(clock33::WRAP_33BIT - 1, 2), 1);
assert_eq!(clock33::signed_distance(clock33::WRAP_33BIT - 10, 5), 15);

// Implement the symmetric contract for your own wire type.
#[derive(Debug, PartialEq)]
struct Toy { tag: u8, value: u16 }

impl<'a> Parse<'a> for Toy {
    type Error = &'static str;
    fn parse(b: &'a [u8]) -> Result<Self, Self::Error> {
        if b.len() < 3 { return Err("short"); }
        Ok(Toy { tag: b[0], value: u16::from_be_bytes([b[1], b[2]]) })
    }
}
impl Serialize for Toy {
    type Error = &'static str;
    fn serialized_len(&self) -> usize { 3 }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, Self::Error> {
        if buf.len() < 3 { return Err("short"); }
        buf[0] = self.tag;
        buf[1..3].copy_from_slice(&self.value.to_be_bytes());
        Ok(3)
    }
}
let wire = [0x42, 0x12, 0x34];
let toy = Toy::parse(&wire)?;
assert_eq!(toy.try_to_bytes()?, wire); // the workspace round-trip invariant

Runnable examples: cargo run -p broadcast-common --example crc_and_bcd and --example implement_parse_serialize.

§What is implemented

ModulePublic API
traitsParse<'a> (parse), Serialize (serialized_len, serialize_into, try_to_bytes). to_bytes() no longer exists: it panicked on failure; try_to_bytes is the only allocate-and-serialize entry point.
crc32_mpeg2compute (poly 0x04C1_1DB7, init 0xFFFF_FFFF, MSB-first, no reflection, no final XOR; table built at compile time)
bcdfrom_bcd_byte, to_bcd_byte and wider helpers; None on out-of-range input, never garbage
timeMjdBcdDateTime, decode_mjd_bcd/encode_mjd_bcd, decode_bcd_duration/encode_bcd_duration, mjd_to_ymd/ymd_to_mjd, decode_seconds_since_2000_utc/encode_seconds_since_2000_utc; with chrono: decode_mjd_bcd_utc/encode_mjd_bcd_utc. The encoder rejects impossible days (31 Feb)
bitsBitReader, BitWriter (MSB-first, up to 64 bits per field, spans bytes), BitError
clock33WRAP_33BIT, unwrap_delta, wrapping_forward_distance, add, add_signed, signed_distance; PCR: PTS_HZ, PCR_HZ, PCR_EXT_MODULUS, PCR_WRAP_27MHZ, pcr_from_parts, pcr_split, pcr_forward_distance, pcr_signed_distance; marker-bit timestamp field: Marked, MARKERS_ALL_SET, unpack_marked, pack_marked, pack_marked_with
lenFieldOverflow, fit_bits, fit_mask, fit_u8, fit_u16, fit_u24, fit_u32: checked width narrowing for serializers
pusiPusiAccumulator (size-capped, DEFAULT_MAX_UNIT_SIZE 16 MiB): the unit splitter wrapped by mpeg-ts and mpeg-pes
ts_dupis_legal_duplicate_pair, check_duplicate, DuplicateVerdict (NotDuplicate, Legal, IllegalThirdRepeat): one reading of H.222.0 §2.4.3.3 shared by dvb-conformance, media-doctor and ts-fix
parityhas_odd_parity, set_odd_parity, strip_parity
hexhex_encode (decoders stay with callers, whose validation policies differ)
seqSeqSpace (add, diff, lt/leq/gt/geq, in_closed_range), MAX_SEQ_BITS
cencCencScheme (to_four_cc, from_four_cc, name), SCHEME_CENC, SCHEME_CBCS
muxUnpackage, Package (batch container demux/mux), Decrypt, Encrypt (in-place sample protection); inverse pairs, generic over Input/Output/Media/Config/Keys/Error
stageStage (feed/poll/finish, next_deadline/on_deadline, demand), Timestamp (monotonic nanoseconds, saturating arithmetic), Demand
rootimpl_spec_display! macro (generates Display from an inherent name())

§Design notes

  • Reserved-bit and trailing-byte policy (workspace-wide, issue #1139): parse is tolerant; serialize preserves what was parsed in typed, spec-named fields (no raw passthrough blobs); hand-built values default to the edition value (1 for reserved_future_use, 0 for reserved_zero_future_use); reserved bytes are always written explicitly; trailing bytes are never dropped without a documented decision.
  • Round-trip invariant. Every wire type parses to a borrowed struct and serializes back byte-identically; this crate supplies the traits, the helpers and the rule, and each crate carries the round-trip tests.
  • Checked widths, not masks. len::fit_* returns FieldOverflow when a value does not fit its field; the & MASK idiom is banned in serializers.
  • One clock implementation. PCR/PTS/DTS/pts_time wrap arithmetic lives in clock33; the 2^33 modulus must not be re-derived elsewhere.
  • Label convention. impl_spec_display! keeps Display in lockstep with the hand-written name() of every public spec enum.
  • #![forbid(unsafe_code)], #![warn(missing_docs)], no_std + alloc without std.

Versioned independently of the DVB lockstep crates (it ships from its own broadcast-common-v* tag). Every protocol crate depends on it; it depends on none of them. See the workspace README for the crate map.

MSRV 1.95.0. Licensed under MIT OR Apache-2.0, at your option.

§Reserved-bit and trailing-byte policy

One workspace-wide rule for every wire crate (issue #1139):

  • Parse is tolerant. A non-conformant reserved bit, or trailing data that real senders emit, is accepted rather than rejected (a crate may choose stricter validation, as dvb-t2mi does for RFU bits, but must document it).
  • Serialize preserves what was parsed. Reserved bits are captured on parse in typed, spec-named fields and re-emitted verbatim, so a stream from a sender that deviates from the governing edition re-serializes byte-identically (dvb-si descriptors, mpeg-ps, rmt-flute, cc-data fixed bits, rtcp-packet profile_extensions, mp4-emsg flags). There is no raw passthrough blob.
  • Constructed values default to the edition value: a struct built by hand (new(..) / Default) carries 1 for reserved_future_use and H.222.0 “reserved”, 0 for reserved_zero_future_use, with any explicit per-field exception documented on the field. (A crate that has not adopted preserve-on-parse documents which fields it normalizes to the spec value on serialize; mpeg-ts, mpeg-pes and mpeg-ps preserve.)
  • A caller that needs byte-for-byte preservation of reserved bits in a not-yet-migrated crate keeps the original bytes rather than re-serializing.
  • Trailing bytes inside a descriptor or section body are never silently dropped without a documented decision: hold them in a typed field, or document the field as “ignored”.
  • Reserved bytes are always written explicitly by serialize_into; never rely on a pre-zeroed output buffer.

The round-trip gate is byte-exact against the original fixture slice (see docs/CRATE-ACCEPTANCE.md).

§Examples

Two runnable examples ship with this crate (cargo run -p broadcast-common --example <name>).

§crc_and_bcd

ⓘ
//! Basic: the shared building blocks — MPEG-2 CRC-32 and BCD coding.
//!
//! Run with: `cargo run -p broadcast-common --example crc_and_bcd`

use broadcast_common::{bcd, crc32_mpeg2};

fn main() {
    // Every CRC-bearing PSI/SI section is protected by this CRC-32/MPEG-2.
    let payload = [0xDE, 0xAD, 0xBE, 0xEF];
    let crc = crc32_mpeg2::compute(&payload);
    println!("CRC-32/MPEG-2 of {payload:02X?} = {crc:#010X}");

    // Appending the CRC and re-running over the whole buffer yields 0 — the
    // standard "is this section intact?" check.
    let mut framed = payload.to_vec();
    framed.extend_from_slice(&crc.to_be_bytes());
    assert_eq!(
        crc32_mpeg2::compute(&framed),
        0,
        "CRC over data+CRC is zero"
    );
    println!(
        "verify (data+CRC) = {:#010X} (0 ⇒ intact)",
        crc32_mpeg2::compute(&framed)
    );

    // BCD is how SI encodes things like service IDs and dates.
    let byte = bcd::to_bcd_byte(42).expect("0..=99 fits one BCD byte");
    println!("42 as BCD       = {byte:#04X}");
    assert_eq!(bcd::from_bcd_byte(byte), Some(42));
}

§implement_parse_serialize

ⓘ
//! Advanced: implement the project's symmetric `Parse` / `Serialize` contract
//! for your own wire type, then round-trip it.
//!
//! Every wire structure in every `dvb-*` crate implements this same pair, and
//! is round-trip tested (parse → serialize → byte-identical). This shows the
//! shape you follow when adding a new type.
//!
//! Run with: `cargo run -p broadcast-common --example implement_parse_serialize`

use broadcast_common::{Parse, Serialize};

/// A toy 3-byte header: a 1-byte tag and a big-endian 16-bit value.
#[derive(Debug, PartialEq, Eq)]
struct Toy {
    tag: u8,
    value: u16,
}

const LEN: usize = 3;

#[derive(Debug)]
#[allow(dead_code)] // fields are surfaced via Debug in the error path
enum ToyError {
    TooShort { need: usize, have: usize },
}

impl<'a> Parse<'a> for Toy {
    type Error = ToyError;
    fn parse(bytes: &'a [u8]) -> Result<Self, Self::Error> {
        if bytes.len() < LEN {
            return Err(ToyError::TooShort {
                need: LEN,
                have: bytes.len(),
            });
        }
        Ok(Toy {
            tag: bytes[0],
            value: u16::from_be_bytes([bytes[1], bytes[2]]),
        })
    }
}

impl Serialize for Toy {
    type Error = ToyError;
    fn serialized_len(&self) -> usize {
        LEN
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, Self::Error> {
        if buf.len() < LEN {
            return Err(ToyError::TooShort {
                need: LEN,
                have: buf.len(),
            });
        }
        buf[0] = self.tag;
        buf[1..3].copy_from_slice(&self.value.to_be_bytes());
        Ok(LEN)
    }
}

fn main() {
    let wire = [0x42, 0x12, 0x34];
    let toy = Toy::parse(&wire).expect("parse");
    println!("parsed: {toy:?}");

    let back = toy.try_to_bytes().unwrap();
    println!("re-serialized: {back:02X?}");

    // The hard project invariant: parse → serialize is byte-identical.
    assert_eq!(back, wire);
    assert_eq!(Toy::parse(&back).unwrap(), toy);
    println!("round-trip holds ✔");
}

Re-exports§

pub use cenc::CencScheme;
pub use mux::Decrypt;
pub use mux::Encrypt;
pub use mux::Package;
pub use mux::Unpackage;
pub use stage::Demand;
pub use stage::Stage;
pub use stage::Timestamp;
pub use traits::Parse;
pub use traits::Serialize;

Modules§

bcd
Binary-coded decimal (BCD) codec for DVB wire fields.
bits
Big-endian, MSB-first bit reader/writer for dense sub-byte wire fields.
cenc
Common Encryption (CENC) scheme identity — ISO/IEC 23001-7 §4.
clock33
Generic 33-bit wrapping-clock helpers.
crc32_mpeg2
CRC-32 MPEG-2 — Annex C of ETSI EN 300 468, Annex A of ETSI TS 102 773.
hex
Hexadecimal encoding of raw byte fields.
len
Range-checked narrowing for wire length/count fields (see #1129).
mux
Container-mux vocabulary traits: Unpackage / Package and Decrypt / Encrypt.
parity
Odd-parity protected bytes.
pusi
PUSI-delimited unit accumulation — ISO/IEC 13818-1 (H.222.0) §2.4.3.2.
seq
Wrap-safe modular sequence-number arithmetic (comparable to RFC 1982 serial-number arithmetic) over a bits-wide sequence space.
stage
Stage — the incremental-drive contract every streaming stage in the workspace will adopt (media-plane migration step 1).
time
UTC time and duration codecs for DVB wire fields.
traits
Canonical Parse and Serialize traits for the DVB crate family.
ts_dup
Legal-duplicate transport-stream-packet detection — ITU-T H.222.0 (08/2023) / ISO/IEC 13818-1 §2.4.3.3.

Macros§

impl_spec_display
Generate a core::fmt::Display impl for a spec/field enum that delegates to an inherent fn name(&self) -> &'static str.