dvb-si 7.0.0

ETSI EN 300 468 DVB Service Information parser + builder. MPEG-2 PSI included.
Documentation

dvb-si

ETSI EN 300 468 DVB Service Information parser and builder, plus the MPEG-2 PSI tables it builds on, the DVB-allocated companion tables, and the DSM-CC data carousel.

Complete coverage: every allocated table_id in EN 300 468 V1.19.1 Table 2 (29 section types; 28 dispatched by AnyTableSection + the type-keyed MPE datagram view) and every allocated descriptor_tag in Table 12 (0x40–0x7F, 64 descriptors) is implemented, each with a symmetric Parse / Serialize pair and round-trip tests. Layouts are derived from the ETSI specs (vendored in the repo and transcribed into reviewable markdown) and validated against live broadcast captures.

MSRV: 1.81. no_std + alloc supported — disable the default std feature for embedded targets (non-Latin text charsets and wall-clock time require std).

API model

dvb-si makes the PSI/SI layering explicit:

  • Section parsers are named *Section: PatSection, NitSection, SdtSection, SitSection, EitSection, and so on. Each value is exactly one wire section and still borrows from the input bytes where possible.
  • AnyTableSection dispatches one complete section by table_id. Demux events expose this through SectionEvent::table_section().
  • collect::SectionSetCollector assembles every long-form multi-section table using the common section_number / last_section_number fields. A completed set owns the original section bytes and can be parsed generically: complete.table::<PatSection>().
  • collect::CompleteNit, CompleteBat, CompleteSdt, and CompleteEit add flattened logical-table views where that is more useful than a vector of sections. Their descriptor loops are parsed through AnyDescriptor while the raw descriptor-loop bytes remain available.
  • collect::EitCollector handles the EIT schedule rule that spans table IDs through last_table_id; ordinary section-number collection is not enough for schedule EIT. EIT schedule sub-tables version independently, and the collector exposes clear() / retain_logical() for long-running EPG pruning.
  • epg::EpgStore (feature chrono) builds an electronic programme guide over EitCollector: deduplicated, time-ordered events with decoded short and extended (§6.2.15 fragment-concatenated) text, content/ratings/CRIDs, now_and_next(key, at), SDT service-name join, and services() to enumerate.
  • resync::TsResync recovers 188-byte (and 204-byte Reed-Solomon) packet alignment from an arbitrary byte stream — junk prefixes, mid-stream loss.
  • TsPacket::adaptation_field() decodes the adaptation field: discontinuity / random-access flags, PCR & OPCR (Pcr::as_27mhz()), and splice countdown.
  • mux::SectionPacketizer / mux::SiMux are the byte-exact inverse: pack serialized section bytes back into 188-byte TS packets (ISO/IEC 13818-1 §2.4.4).
  • TableId variants use Rust CamelCase names (TableId::Pat, TableId::NetworkInformationActual, TableId::MpeFec), while section parser types carry the Section suffix.

Quickstart

use dvb_si::demux::SiDemux;
use dvb_si::descriptors::AnyDescriptor;
use dvb_si::tables::AnyTableSection;

let mut demux = SiDemux::builder().build();

// In real code, `packet` is each aligned 188-byte packet from your TS source
// (file, UDP, tuner). See `dvb-tools dump` for a complete file-reading CLI.
for event in demux.feed(&packet) {
    match event.table_section() {
        Ok(AnyTableSection::SdtSection(sdt)) => {
            for service in &sdt.services {
                for item in service.descriptors.iter().flatten() {
                    if let AnyDescriptor::Service(svc) = item {
                        // DvbText decodes EN 300 468 Annex A -> UTF-8.
                        println!("service: {}", svc.service_name.decode());
                    }
                }
            }
        }
        Ok(AnyTableSection::PatSection(pat)) => {
            println!("PAT v{} on {}", event.version().unwrap_or(0), event.pid());
        }
        _ => {}
    }
}

See dvb-tools dump for a complete file-reading CLI (cargo run -p dvb-tools -- dump file.ts [--json]).

Layer map

TS packets ──► demux::SiDemux ──► SectionEvent
                                    │ .table_section()
                                    ▼
                              tables::AnyTableSection  (PatSection, SdtSection, …)
                                    │ section.<loop field> : DescriptorLoop
                                    ▼
                          descriptors::parse_loop ──► AnyDescriptor
                                    │ field : DvbText / LangCode
                                    ▼
                              text::DvbText::decode() ──► UTF-8 String

SectionEvent.bytes() ──► collect::SectionSetCollector ──► CompleteSectionSet
                                                         │ .table::<T>()
                                                         ├ .nit() / .bat() / .sdt() / .eit()
                                                         ▼
                                                   complete logical tables

serialized sections ──► mux::SectionPacketizer ──► 188-byte TS packets

Section coverage

28 table_ids are dispatched by AnyTableSection::parse; MpeDatagramSection (0x3E) is reachable via AnyTableSection::parse_as as noted below. Every section parser has typed header fields, symmetric parse/serialize, and round-trip tests. Per the crate's zero-copy convention, descriptor loops and repeated sub-structures borrow from the input bytes. Flat descriptor loops are DescriptorLoop values that walk into typed descriptors; notes below call out only tables that go further or deliberately keep a nested structure raw.

table_id Table Spec Notes
0x00 PAT — Program Association ISO/IEC 13818-1 typed program→PID entries
0x01 CAT — Conditional Access ISO/IEC 13818-1 typed ca_descriptors() view
0x02 PMT — Program Map ISO/IEC 13818-1 typed ES loop
0x03 TSDT — TS Description ISO/IEC 13818-1
0x3A–0x3F DSM-CC sections ISO/IEC 13818-6 / EN 301 192 framing; 0x3B/0x3C payloads typed via carousel; 0x3E typed as MPE
0x3E MPE datagram_section (typed IP/MAC view) EN 301 192 §7 MAC reassembly, LLC/SNAP flag, SSI-aware trailer — accessible via AnyTableSection::parse_as
0x40/0x41 NIT actual/other EN 300 468 §5.2.1 typed TS loop
0x42/0x46 SDT actual/other EN 300 468 §5.2.3 typed service loop
0x4A BAT — Bouquet Association EN 300 468 §5.2.2 typed TS loop
0x4B UNT — Update Notification (SSU) TS 102 006 typed platform entries
0x4C INT — IP/MAC Notification EN 301 192
0x4D SAT — Satellite Access family EN 300 468 §5.2.11 typed SatBody: Position V2/V3, cell fragment, time association, beamhopping
0x4E–0x6F EIT p/f + schedule, actual/other EN 300 468 §5.2.4 typed event loop; chrono-gated MJD+BCD start_time()
0x70 TDT — Time and Date EN 300 468 §5.2.5
0x71 RST — Running Status EN 300 468 §5.2.7 typed event loop
0x72 ST — Stuffing EN 300 468 §5.2.8
0x73 TOT — Time Offset EN 300 468 §5.2.6 incl. the SSI=0-with-CRC framing exception
0x74 AIT — Application Information TS 102 809 typed application loop; validated vs live HbbTV capture
0x75 Container TS 102 323 ContainerSection
0x76 RCT — Related Content TS 102 323
0x77 CIT — Content Identifier TS 102 323
0x78 MPE-FEC EN 301 192 §9.9 typed real_time_parameters
0x79 RNT — Resolution Notification TS 102 323
0x7A MPE-IFEC TS 102 772 typed real_time_parameters
0x7B Protection message TS 102 809 §9 authentication-message + certificate-collection variants by table_id_extension
0x7C DFIS — Downloadable Font Info EN 303 560 typed font_info loop (table_id per EN 300 468 Table 2 NOTE 2)
0x7E DIT — Discontinuity Information EN 300 468
0x7F SIT — Selection Information EN 300 468

Remaining table_id values are reserved or user-defined in EN 300 468 V1.19.1 Table 2 — there is nothing standardized left to implement.

Descriptors

71 descriptor tags are dispatched by AnyDescriptor (via DescriptorLoop::iter / parse_loop): 6 MPEG-2/DSM-CC tags, 64 DVB-allocated tags (0x40–0x7F), and the de-facto private logical_channel_descriptor (0x83) — which exists as a variant but is not auto-dispatched (register it via DescriptorRegistry with PDS context). Unknown or unregistered tags pass through as AnyDescriptor::Unknown { tag, body } — the raw payload is preserved and round-trips losslessly.

MPEG-2 / DSM-CC descriptors (ISO/IEC 13818-1 / -6)

tag Descriptor
0x05 registration
0x06 data_stream_alignment
0x09 CA
0x0A ISO_639_language
0x0F private_data_indicator
0x13 carousel_identifier (ISO/IEC 13818-6)

DVB descriptors (EN 300 468, and companion specs)

All 64 allocated tags 0x40–0x7F from EN 300 468 V1.19.1 Table 12 are implemented. Each parses into a typed struct with a symmetric serializer and round-trip tests; any free-form byte fields (names, selector tails) stay borrowed &[u8] per the crate's zero-copy convention.

tag Descriptor Spec Notes
0x40 network_name EN 300 468
0x41 service_list EN 300 468
0x42 stuffing EN 300 468
0x43 satellite_delivery_system EN 300 468
0x44 cable_delivery_system EN 300 468
0x45 VBI_data EN 300 468 typed service loop; one-byte line entries raw per §6.2.47
0x46 VBI_teletext EN 300 468
0x47 bouquet_name EN 300 468
0x48 service EN 300 468
0x49 country_availability EN 300 468
0x4A linkage EN 300 468
0x4B NVOD_reference EN 300 468
0x4C time_shifted_service EN 300 468
0x4D short_event EN 300 468
0x4E extended_event EN 300 468
0x4F time_shifted_event EN 300 468
0x50 component EN 300 468
0x51 mosaic EN 300 468 typed cell + elementary-cell loops, typed cell_linkage variants
0x52 stream_identifier EN 300 468
0x53 CA_identifier EN 300 468
0x54 content EN 300 468
0x55 parental_rating EN 300 468
0x56 teletext EN 300 468
0x57 telephone EN 300 468 bit-packed length fields typed
0x58 local_time_offset EN 300 468
0x59 subtitling EN 300 468
0x5A terrestrial_delivery_system EN 300 468
0x5B multilingual_network_name EN 300 468
0x5C multilingual_bouquet_name EN 300 468
0x5D multilingual_service_name EN 300 468
0x5E multilingual_component EN 300 468
0x5F private_data_specifier EN 300 468
0x60 service_move EN 300 468
0x61 short_smoothing_buffer EN 300 468
0x62 frequency_list EN 300 468
0x63 partial_transport_stream EN 300 468 §7.2.1
0x64 data_broadcast EN 300 468 selector raw (interpretation depends on data_broadcast_id)
0x65 scrambling EN 300 468
0x66 data_broadcast_id EN 300 468 / EN 301 192 id_selector tail raw
0x67 transport_stream EN 300 468
0x68 DSNG EN 300 468
0x69 PDC EN 300 468
0x6A AC-3 EN 300 468 Annex D
0x6B ancillary_data EN 300 468
0x6C cell_list EN 300 468 both loops typed; 12+12-bit extents unpacked
0x6D cell_frequency_link EN 300 468 both loops typed
0x6E announcement_support EN 300 468
0x6F application_signalling TS 102 809
0x70 adaptation_field_data EN 300 468
0x71 service_identifier TS 102 809
0x72 service_availability EN 300 468
0x73 default_authority TS 102 323
0x74 related_content TS 102 323
0x75 TVA_id TS 102 323
0x76 content_identifier TS 102 323
0x77 time_slice_fec_identifier EN 301 192
0x78 ECM_repetition_rate EN 301 192
0x79 S2_satellite_delivery_system EN 300 468
0x7A enhanced_AC-3 EN 300 468 Annex D
0x7B DTS EN 300 468 Annex G
0x7C AAC EN 300 468 Annex H
0x7D XAIT_location TS 102 727
0x7E FTA_content_management EN 300 468
0x7F extension EN 300 468 §6.2.18.1 typed discriminant + typed bodies; see below

Private descriptors

tag Descriptor Notes
0x83 logical_channel EACEM/NorDig private; variant exists, not auto-dispatched — register via DescriptorRegistry

Extension descriptor registry (tag 0x7F)

The first payload byte (descriptor_tag_extension) selects a sub-descriptor (EN 300 468 §6.4). 21 tag_extensions are fully typed; everything else is preserved byte-exact as ExtensionBody::Raw and round-trips losslessly. Private descriptor_tag_extension values are surfaced via DescriptorLoop::iter_with_extensions(&desc_reg, &ext_reg) as ExtIterItem::CustomExtension.

tag_ext Extension Spec
0x00 image_icon Table 145, §6.4.7
0x04 T2_delivery_system Table 133, §6.4.6.3 (cell loop unfolded)
0x05 SH_delivery_system Table 119, §6.4.6.2
0x06 supplementary_audio Table 153, §6.4.11
0x07 network_change_notify Table 149, §6.4.9 (cell/change loop unfolded)
0x08 message Table 148, §6.4.9
0x09 target_region Table 156, §6.4.12 (region loop unfolded)
0x0A target_region_name Table 157, §6.4.13 (region loop unfolded)
0x0B service_relocated Table 152, §6.4.10
0x0D C2_delivery_system Table 115, §6.4.6.1
0x10 video_depth_range Table 160, §6.4.16.1 (range loop typed)
0x11 T2MI Table 158, §6.4.14
0x13 URI_linkage Table 159, §6.4.16.1
0x15 AC-4 Annex D §D.5 (first level; toc/extra raw)
0x16 C2_bundle_delivery_system Table 139, §6.4.6.4
0x17 S2X_satellite_delivery_system Table 140, §6.4.6.5.2 (channel-bond entries typed)
0x19 audio_preselection Table 110, §6.4.1 (preselection loop unfolded)
0x20 TTML_subtitling EN 303 560 Table 1, §5.2.1.1
0x22 service_prominence Table 162c, §6.4.18 (SOGI loop typed; target_region loop raw)
0x23 vvc_subpictures Table 162a, §6.4.17
0x24 S2Xv2_satellite_delivery_system Tables 144a–144c, §6.4.6.5.3

Tags whose governing spec is not vendored (0x010x03 CPCM, 0x0C XAIT_PID, 0x0E/0x0F/0x21 DTS family, 0x14 CI_ancillary, 0x18 protection_message) are preserved as ExtensionBody::Raw and round-trip byte-exact.

DSM-CC data carousel

The carousel module types the download-protocol payloads carried inside DSM-CC sections (ISO/IEC 13818-6 §7.2/§7.3 as profiled by DVB — TR 101 202, TS 102 006 SSU, TS 102 809 object carousels):

Message / Type messageId Notes
DSI — DownloadServerInitiate 0x1006 privateData raw: SSU GroupInfoIndication / OC ServiceGatewayInfo
DII — DownloadInfoIndication 0x1002 typed module loop
DDB — DownloadDataBlock 0x1003
ModuleReassembler DDB → complete modules per DII geometry: version-aware, out-of-order tolerant, per-module + aggregate memory caps

The BIOP object-carousel layer (carousel::biop) is also implemented: ServiceGatewayInfo, BiopMessage (Directory / File / ServiceGateway / Stream / StreamEvent), IOR + tagged profiles, and the CarouselFs virtual-file-tree walker (resolve / file_bytes); ISO/IEC 13818-6 §11 as profiled by TR 101 202.

Validated byte-exact against a live French-TNT (M6 HbbTV) capture in the test suite.

Text decoding

Full EN 300 468 Annex A Table A.3 selector coverage:

Selector Table Decoding
(none, first byte ≥ 0x20) default Latin, Figure A.1 glyph-for-glyph (ISO 6937 superset — € at 0xA4, full non-spacing diacritic row with precomposed forms + combining-mark fallback)
0x01–0x0B ISO/IEC 8859-5 … -15 via encoding_rs (0x08 is reserved — no ISO 8859-12)
0x10 ISO/IEC 8859-n (two-byte selector) via encoding_rs
0x11 ISO/IEC 10646 BMP UCS-2 BE
0x12 KS X 1001 (Korean) EUC-KR
0x13 GB-2312-1980 (Simplified Chinese) GBK (GB-2312 superset)
0x14 Big5 (Traditional Chinese) Big5
0x15 UTF-8 passthrough
0x1F encoding_type_id escape id byte consumed; body U+FFFD (no registered broadcast ids)
reserved (0x08, 0x0C–0x0F, 0x16–0x1E) U+FFFD per byte

Annex A.1 control codes are honored for both the single-byte (0x80–0x9F) and two-byte (U+E080–U+E09F PUA, Table A.2) tables: emphasis markers dropped, CR/LF → space, reserved controls stripped.

The non-Latin charsets (encoding_rs) require the std feature.

Typed dispatch

You rarely match table_ids or descriptor_tags by hand. AnyTableSection::parse dispatches a complete section to the right typed section parser; a section's descriptor-loop field is a DescriptorLoop whose .iter() yields AnyDescriptor values (typed where known, Unknown otherwise, never panicking — parse_loop does the same for a free byte slice). DescriptorRegistry lets you plug in private descriptors at runtime, TableRegistry does the same for private table_ids, and ExtensionRegistry handles private tag-extension sub-descriptors (tag 0x7F). All are generated from a single declarative list so the dispatcher can never drift from the implemented set.

use dvb_si::descriptors::AnyDescriptor;

for item in eit_event.descriptors.iter() {       // DescriptorLoop::iter()
    match item? {
        AnyDescriptor::ShortEvent(se) => println!("{}", se.event_name.decode()),
        AnyDescriptor::Unknown { tag, .. } => eprintln!("unknown 0x{tag:02X}"),
        _ => {}
    }
}

Multi-section collection and EPG

use dvb_si::collect::{CompletedEit, EitCollector, SectionSetCollector};
use dvb_si::demux::SiDemux;
use dvb_si::tables::eit;
use dvb_si::tables::nit::{self, NitSection};

let mut demux = SiDemux::builder().build();
let mut nit_sections = SectionSetCollector::new();
let mut eit_sections = EitCollector::new();

for event in demux.feed(&packet) {
    match event.table_id() {
        nit::TABLE_ID_ACTUAL | nit::TABLE_ID_OTHER => {
            if let Some(complete) = nit_sections
                .push_section_with_pid(Some(event.pid().value()), event.bytes())
                .ok()
                .flatten()
            {
                let nit = complete.nit()?;            // flattened logical NIT
                let raw = complete.table::<NitSection>()?; // generic section view
                let _ = (nit, raw);
            }
        }
        eit::TABLE_ID_PF_ACTUAL..=eit::TABLE_ID_SCHEDULE_OTHER_LAST => {
            if let Some(done) = eit_sections
                .push_section_with_pid(Some(event.pid().value()), event.bytes())
                .ok()
                .flatten()
            {
                match done {
                    CompletedEit::PresentFollowing(set) => {
                        let eit = set.eit()?;
                        let _ = eit;
                    }
                    CompletedEit::Schedule(schedule) => {
                        for table in schedule.tables()? {
                            let _ = table.events;
                        }
                    }
                }
            }
        }
        _ => {}
    }
}

// Long-running EPG collectors can prune old state.
eit_sections.retain_logical(|key| application_still_needs(key));

Owning parsed views across threads (feature yoke)

Zero-copy parsed types borrow the input slice via <'a>. The optional yoke feature bundles the backing bytes and the borrowing view into one 'static, cheaply-Clone, Send + Sync value via the [yoke] crate:

use std::sync::Arc;
use dvb_si::{owned::Owned, tables::pmt::PmtSection};
use dvb_common::Parse;

let bytes: Arc<[u8]> = Arc::from(section);             // own the section
let pmt: Owned<PmtSection<'static>> = Owned::try_new(bytes, |b| PmtSection::parse(b))?;
let view: &PmtSection = pmt.get();                            // no re-parse, no lifetime

Features

[dependencies]
dvb-si = { version = "7", default-features = false }  # tight build: no_std + alloc
Feature Default Description
std yes Link std; no_std + alloc when off. Non-Latin text charsets and wall-clock time require std.
chrono yes MJD+BCD wire fields → DateTime<Utc> decoded accessors. Off → raw MJD/BCD bytes returned.
ts yes MPEG-TS packet types, SectionReassembler, SiDemux, TsResync, SectionPacketizer, SiMux. Off → bring your own section bytes.
serde yes Serialize on all parsed types (Serialize-only — re-parse from wire bytes to reconstruct).
yoke no Yokeable impls + Owned<T> wrapper to retain parsed views past the input buffer's borrow.
flate2 no zlib decompression for compressed BIOP modules in object carousels. Off → compressed bytes exposed raw. Requires std.

serde is Serialize-only — for display/export (JSON via serde_json); parsing FROM JSON is deliberately unsupported — re-parse from the wire bytes.

Principles

  • Spec fidelity. Every field in a section's syntax appears in the parsed struct.
  • Parse and construct. Every parser has a symmetric serializer; round-trip is tested.
  • Zero-copy where possible. Parsed types borrow from the input via <'a> lifetimes.
  • No magic numbers. Every hex literal outside #[cfg(test)] is a named constant or enum.

Spec grounding

Every layout is cited. The repo vendors the ETSI PDFs and transcribes their syntax tables into reviewable markdown (docs/) — each spec below links both the ETSI deliverable and the in-repo transcription:

Spec ETSI deliverable Transcription
EN 300 468 V1.19.1 (2025-02) — DVB SI PDF en_300_468.md
EN 301 192 v1.7.1 — data broadcasting PDF en_301_192.md
TS 102 006 v1.7.1 — System Software Update PDF ts_102_006_ssu.md
TS 102 323 v1.4.1 — TV-Anytime carriage PDF ts_102_323_tva.md
TS 102 809 v1.3.1 — application signalling PDF ts_102_809_apps.md
TS 102 772 v1.1.1 — MPE-IFEC PDF ts_102_772_mpe_ifec.md
EN 303 560 v1.1.1 — TTML subtitling PDF en_303_560_ttml.md
TS 102 727 v1.1.1 — MHP (XAIT) PDF vendored PDF only (cites give page + table)
TR 101 202 v1.2.1 — data broadcasting guidelines PDF profile semantics for carousel (no syntax tables)
ISO/IEC 13818-6 — DSM-CC not freely redistributable iso_13818_6_carousel.md (provenance-documented hand transcription)

The crate has been through five adversarial spec-audit rounds; fixture tests run against real transponder captures.

Upgrading

For 3.x → 4.0, see MIGRATION-4.0.md: section parser renames, AnyTableSection, table_section(), CamelCase TableId, and multi-section collection. For older breaks, see MIGRATION-3.1.md and MIGRATION-2.0.md.

Family

dvb-common — traits + CRC-32
dvb-t2mi — T2-MI, all 12 packet types
dvb-bbframe — S2/S2X/T2 BBFRAME
dvb-conformance — TR 101 290 monitor
dvb-tools — CLI: dump / services / epg / pids / t2mi
For GSE see the existing dvb-gse crate.

License

Licensed under either of MIT or Apache-2.0, at your option.