Expand description
§broadcast-common
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.
| Standard | Edition | Scope | Status | Notes |
|---|---|---|---|---|
| ITU-T H.222.0 / ISO/IEC 13818-1 | H.222.0 v9 (08/2023), verified against the vendored copy | Annex 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 listed | Higher layers (TS, PES, PSI) live in mpeg-ts, mpeg-pes, dvb-si. |
| ETSI EN 300 468 | V1.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 codecs | MJD range 1948-08-05 ..= 2128-01-09. Integer proleptic-Gregorian arithmetic (no closed-form Annex C formula limit). |
| ETSI TS 102 773 | V1.4.1 | Annex A CRC-32 for T2-MI packets (same crc32_mpeg2) | Full for the CRC | The packet format is in dvb-t2mi. |
| ETSI EN 300 706 | V1.2.1 | §8.1 odd-parity protected byte (parity) | Partial: parity primitive only | Also 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 only | Crypto boxes live in transmux; #EXT-X-KEY signalling in broadcast-hls. |
| RFC 1982 serial-number arithmetic | n/a | seq::SeqSpace wrap-safe sequence comparison (RTP 16-bit, SRT 31-bit) | Comparable, not spec-cited | Implementation policy, not defined by RTP/SRT. |
§Feature flags
| Feature | Default | What it enables |
|---|---|---|
std | yes | Links std: std::error::Error glue, Timestamp::from_instant, and chrono’s clock when chrono is on. Off gives #![no_std] + alloc. |
chrono | no | chrono::DateTime<Utc> conversions in time (decode_mjd_bcd_utc, encode_mjd_bcd_utc, …). Without it the plain MjdBcdDateTime and duration helpers stay available. |
serde | no | serde::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(§ion[..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 invariantRunnable examples: cargo run -p broadcast-common --example crc_and_bcd and
--example implement_parse_serialize.
§What is implemented
| Module | Public API |
|---|---|
traits | Parse<'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_mpeg2 | compute (poly 0x04C1_1DB7, init 0xFFFF_FFFF, MSB-first, no reflection, no final XOR; table built at compile time) |
bcd | from_bcd_byte, to_bcd_byte and wider helpers; None on out-of-range input, never garbage |
time | MjdBcdDateTime, 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) |
bits | BitReader, BitWriter (MSB-first, up to 64 bits per field, spans bytes), BitError |
clock33 | WRAP_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 |
len | FieldOverflow, fit_bits, fit_mask, fit_u8, fit_u16, fit_u24, fit_u32: checked width narrowing for serializers |
pusi | PusiAccumulator (size-capped, DEFAULT_MAX_UNIT_SIZE 16 MiB): the unit splitter wrapped by mpeg-ts and mpeg-pes |
ts_dup | is_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 |
parity | has_odd_parity, set_odd_parity, strip_parity |
hex | hex_encode (decoders stay with callers, whose validation policies differ) |
seq | SeqSpace (add, diff, lt/leq/gt/geq, in_closed_range), MAX_SEQ_BITS |
cenc | CencScheme (to_four_cc, from_four_cc, name), SCHEME_CENC, SCHEME_CBCS |
mux | Unpackage, Package (batch container demux/mux), Decrypt, Encrypt (in-place sample protection); inverse pairs, generic over Input/Output/Media/Config/Keys/Error |
stage | Stage (feed/poll/finish, next_deadline/on_deadline, demand), Timestamp (monotonic nanoseconds, saturating arithmetic), Demand |
| root | impl_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 (
1forreserved_future_use,0forreserved_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_*returnsFieldOverflowwhen a value does not fit its field; the& MASKidiom is banned in serializers. - One clock implementation. PCR/PTS/DTS/
pts_timewrap arithmetic lives inclock33; the 2^33 modulus must not be re-derived elsewhere. - Label convention.
impl_spec_display!keepsDisplayin lockstep with the hand-writtenname()of every public spec enum. #![forbid(unsafe_code)],#![warn(missing_docs)],no_std+allocwithoutstd.
§Related crates and license
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-t2midoes 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-sidescriptors,mpeg-ps,rmt-flute,cc-datafixed bits,rtcp-packetprofile_extensions,mp4-emsgflags). There is no raw passthrough blob. - Constructed values default to the edition value: a struct built by
hand (
new(..)/Default) carries1forreserved_future_useand H.222.0 “reserved”,0forreserved_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-pesandmpeg-pspreserve.) - 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/PackageandDecrypt/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
ParseandSerializetraits 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::Displayimpl for a spec/field enum that delegates to an inherentfn name(&self) -> &'static str.