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.
4.0 API model
dvb-si 4.0 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. AnyTableSectiondispatches one complete section bytable_id. Demux events expose this throughSectionEvent::table_section().collect::SectionSetCollectorassembles every long-form multi-section table using the commonsection_number/last_section_numberfields. A completed set owns the original section bytes and can be parsed generically:complete.table::<PatSection>().collect::CompleteNit,CompleteBat,CompleteSdt, andCompleteEitadd flattened logical-table views where that is more useful than a vector of sections. Their descriptor loops are parsed throughAnyDescriptorwhile the raw descriptor-loop bytes remain available.collect::EitCollectorhandles the EIT schedule rule that spans table IDs throughlast_table_id; ordinary section-number collection is not enough for schedule EIT. EIT schedule sub-tables version independently, and the collector exposesclear()/retain_logical()for long-running EPG pruning.TableIdvariants use Rust CamelCase names (TableId::Pat,TableId::NetworkInformationActual,TableId::MpeFec), while section parser types carry theSectionsuffix.
There are no 3.x compatibility aliases in 4.0. See
MIGRATION-4.0.md for before/after examples.
Section coverage
Every section parser: typed header fields, symmetric parse/serialize, round-trip
tested. 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 | Status |
|---|---|---|---|
| 0x00 | PAT — Program Association | ISO/IEC 13818-1 | ✅ full |
| 0x01 | CAT — Conditional Access | ISO/IEC 13818-1 | ✅ full + typed ca_descriptors() view |
| 0x02 | PMT — Program Map | ISO/IEC 13818-1 | ✅ full (typed ES loop) |
| 0x03 | TSDT — TS Description | ISO/IEC 13818-1 | ✅ full |
| 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 | ✅ full (MAC reassembly, LLC/SNAP flag, SSI-aware trailer) |
| 0x40/0x41 | NIT actual/other | EN 300 468 §5.2.1 | ✅ full (typed TS loop) |
| 0x42/0x46 | SDT actual/other | EN 300 468 §5.2.3 | ✅ full (typed service loop) |
| 0x4A | BAT — Bouquet Association | EN 300 468 §5.2.2 | ✅ full (typed TS loop) |
| 0x4B | UNT — Update Notification (SSU) | TS 102 006 | ✅ full |
| 0x4C | INT — IP/MAC Notification | EN 301 192 | ✅ full |
| 0x4D | SAT — Satellite Access family | EN 300 468 §5.2.11 | ✅ header + SatTableId discriminant typed; variant bodies raw (bit-packed orbital data, layout in docs) |
| 0x4E–0x6F | EIT p/f + schedule, actual/other | EN 300 468 §5.2.4 | ✅ full (typed event loop; chrono-gated MJD+BCD start_time()) |
| 0x70 | TDT — Time and Date | EN 300 468 §5.2.5 | ✅ full |
| 0x71 | RST — Running Status | EN 300 468 §5.2.7 | ✅ full (typed event loop) |
| 0x72 | ST — Stuffing | EN 300 468 §5.2.8 | ✅ full |
| 0x73 | TOT — Time Offset | EN 300 468 §5.2.6 | ✅ full (incl. the SSI=0-with-CRC framing exception) |
| 0x74 | AIT — Application Information | TS 102 809 | ✅ full (typed application loop), validated vs live HbbTV capture |
| 0x75 | Container | TS 102 323 | ✅ full (ContainerSection) |
| 0x76 | RCT — Related Content | TS 102 323 | ✅ full |
| 0x77 | CIT — Content Identifier | TS 102 323 | ✅ full |
| 0x78 | MPE-FEC | EN 301 192 §9.9 | ✅ full (typed real_time_parameters) |
| 0x79 | RNT — Resolution Notification | TS 102 323 | ✅ full |
| 0x7A | MPE-IFEC | TS 102 772 | ✅ full (typed real_time_parameters) |
| 0x7B | Protection message | TS 102 809 §9 | ✅ full — authentication-message + certificate-collection variants by table_id_extension |
| 0x7C | DFIS — Downloadable Font Info | EN 303 560 | ✅ full (typed font_info loop; table_id per EN 300 468 Table 2 NOTE 2) |
| 0x7E | DIT — Discontinuity Information | EN 300 468 | ✅ full |
| 0x7F | SIT — Selection Information | EN 300 468 | ✅ full |
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.
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 | messageId | Status |
|---|---|---|
| DSI — DownloadServerInitiate | 0x1006 | ✅ full (privateData raw: SSU GroupInfoIndication / OC ServiceGatewayInfo) |
| DII — DownloadInfoIndication | 0x1002 | ✅ full (typed module loop) |
| DDB — DownloadDataBlock | 0x1003 | ✅ full |
ModuleReassembler |
— | ✅ DDB → complete modules per DII geometry: version-aware, out-of-order tolerant, per-module + aggregate memory caps |
Validated byte-exact against a live French-TNT (M6 HbbTV) capture in the test suite. BIOP object-carousel payloads above this layer are out of scope.
Descriptors
Every allocated descriptor_tag in EN 300 468 V1.19.1 Table 12
(0x40–0x7F) is implemented — plus the MPEG-2 descriptors that matter in SI
context and the de-facto private logical_channel_descriptor. Each parses into
a typed struct with a symmetric serializer and round-trip tests; any
unallocated/unknown tag passes through as raw bytes (tag + payload preserved).
Per the crate's zero-copy convention, free-form byte fields (names, selector
tails) stay borrowed &[u8]; notes below only where a sub-structure is
deliberately kept raw.
| tag | Descriptor | Spec | Status |
|---|---|---|---|
| 0x05 | registration | ISO/IEC 13818-1 | ✅ full |
| 0x06 | data_stream_alignment | ISO/IEC 13818-1 | ✅ full |
| 0x09 | CA | ISO/IEC 13818-1 | ✅ full |
| 0x0A | ISO_639_language | ISO/IEC 13818-1 | ✅ full |
| 0x0F | private_data_indicator | ISO/IEC 13818-1 | ✅ full |
| 0x40 | network_name | EN 300 468 | ✅ full |
| 0x41 | service_list | EN 300 468 | ✅ full |
| 0x42 | stuffing | EN 300 468 | ✅ full |
| 0x43 | satellite_delivery_system | EN 300 468 | ✅ full |
| 0x44 | cable_delivery_system | EN 300 468 | ✅ full |
| 0x45 | VBI_data | EN 300 468 | ✅ full (typed service loop; one-byte line entries raw per §6.2.47) |
| 0x46 | VBI_teletext | EN 300 468 | ✅ full |
| 0x47 | bouquet_name | EN 300 468 | ✅ full |
| 0x48 | service | EN 300 468 | ✅ full |
| 0x49 | country_availability | EN 300 468 | ✅ full |
| 0x4A | linkage | EN 300 468 | ✅ full |
| 0x4B | NVOD_reference | EN 300 468 | ✅ full |
| 0x4C | time_shifted_service | EN 300 468 | ✅ full |
| 0x4D | short_event | EN 300 468 | ✅ full |
| 0x4E | extended_event | EN 300 468 | ✅ full |
| 0x4F | time_shifted_event | EN 300 468 | ✅ full |
| 0x50 | component | EN 300 468 | ✅ full |
| 0x51 | mosaic | EN 300 468 | ✅ full (typed cell + elementary-cell loops, typed cell_linkage variants) |
| 0x52 | stream_identifier | EN 300 468 | ✅ full |
| 0x53 | CA_identifier | EN 300 468 | ✅ full |
| 0x54 | content | EN 300 468 | ✅ full |
| 0x55 | parental_rating | EN 300 468 | ✅ full |
| 0x56 | teletext | EN 300 468 | ✅ full |
| 0x57 | telephone | EN 300 468 | ✅ full (bit-packed length fields typed) |
| 0x58 | local_time_offset | EN 300 468 | ✅ full |
| 0x59 | subtitling | EN 300 468 | ✅ full |
| 0x5A | terrestrial_delivery_system | EN 300 468 | ✅ full |
| 0x5B | multilingual_network_name | EN 300 468 | ✅ full |
| 0x5C | multilingual_bouquet_name | EN 300 468 | ✅ full |
| 0x5D | multilingual_service_name | EN 300 468 | ✅ full |
| 0x5E | multilingual_component | EN 300 468 | ✅ full |
| 0x5F | private_data_specifier | EN 300 468 | ✅ full |
| 0x60 | service_move | EN 300 468 | ✅ full |
| 0x61 | short_smoothing_buffer | EN 300 468 | ✅ full |
| 0x62 | frequency_list | EN 300 468 | ✅ full |
| 0x63 | partial_transport_stream | EN 300 468 §7.2.1 | ✅ full |
| 0x64 | data_broadcast | EN 300 468 | ✅ full (selector raw — interpretation depends on data_broadcast_id) |
| 0x65 | scrambling | EN 300 468 | ✅ full |
| 0x66 | data_broadcast_id | EN 300 468 / EN 301 192 | ✅ full (id_selector tail raw) |
| 0x67 | transport_stream | EN 300 468 | ✅ full |
| 0x68 | DSNG | EN 300 468 | ✅ full |
| 0x69 | PDC | EN 300 468 | ✅ full |
| 0x6A | AC-3 | EN 300 468 Annex D | ✅ full |
| 0x6B | ancillary_data | EN 300 468 | ✅ full |
| 0x6C | cell_list | EN 300 468 | ✅ full (both loops typed, 12+12-bit extents unpacked) |
| 0x6D | cell_frequency_link | EN 300 468 | ✅ full (both loops typed) |
| 0x6E | announcement_support | EN 300 468 | ✅ full |
| 0x6F | application_signalling | TS 102 809 | ✅ full |
| 0x70 | adaptation_field_data | EN 300 468 | ✅ full |
| 0x71 | service_identifier | TS 102 809 | ✅ full |
| 0x72 | service_availability | EN 300 468 | ✅ full |
| 0x73 | default_authority | TS 102 323 | ✅ full |
| 0x74 | related_content | TS 102 323 | ✅ full |
| 0x75 | TVA_id | TS 102 323 | ✅ full |
| 0x76 | content_identifier | TS 102 323 | ✅ full |
| 0x77 | time_slice_fec_identifier | EN 301 192 | ✅ full |
| 0x78 | ECM_repetition_rate | EN 301 192 | ✅ full |
| 0x79 | S2_satellite_delivery_system | EN 300 468 | ✅ full |
| 0x7A | enhanced_AC-3 | EN 300 468 Annex D | ✅ full |
| 0x7B | DTS | EN 300 468 Annex G | ✅ full |
| 0x7C | AAC | EN 300 468 Annex H | ✅ full |
| 0x7D | XAIT_location | TS 102 727 | ✅ full |
| 0x7E | FTA_content_management | EN 300 468 | ✅ full |
| 0x7F | extension | EN 300 468 §6.2.18.1 | ✅ typed discriminant + typed bodies below; unknown tag_extensions round-trip raw |
| 0x83 | logical_channel | EACEM/NorDig private | ✅ full |
Extension descriptor registry (tag 0x7F)
The first payload byte (descriptor_tag_extension) selects a sub-descriptor
(EN 300 468 §6.4). A body is typed only when its syntax table is vendored
under docs/; everything else is preserved byte-exact as Raw and
round-trips losslessly.
| tag_ext | Extension | Status |
|---|---|---|
| 0x04 | T2_delivery_system | ✅ typed (first level; cell loop raw) |
| 0x06 | supplementary_audio | ✅ typed |
| 0x07 | network_change_notify | ✅ typed (cell loop raw) |
| 0x08 | message | ✅ typed |
| 0x09 | target_region | ✅ typed (region loop raw) |
| 0x0A | target_region_name | ✅ typed (region loop raw) |
| 0x0B | service_relocated | ✅ typed |
| 0x0D | C2_delivery_system | ✅ typed |
| 0x13 | URI_linkage | ✅ typed (uri/private split) |
| 0x15 | AC-4 | ✅ typed (first level; toc/extra raw) |
| 0x16 | C2_bundle_delivery_system | ✅ typed (full fixed loop) |
| 0x17 | S2X_satellite_delivery_system | ✅ typed (primary channel; bonding tail raw) |
| 0x19 | audio_preselection | ✅ typed (preselection loop raw) |
| 0x20 | TTML_subtitling | ✅ typed (EN 303 560) |
| 0x00 image_icon · 0x05 SH_delivery_system · 0x10 video_depth_range · 0x11 T2MI · 0x22–0x24 | niche; deferred | raw-preserved |
| 0x01–0x03 CPCM (TS 102 825) · 0x0C XAIT_PID (TS 102 727) · 0x0E/0x0F/0x21 DTS family · 0x14 CI_ancillary (TS 103 205) · 0x18 protection_message (TS 102 809) | spec not vendored | raw-preserved |
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, every position pinned by tests) |
| 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.
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 | en_300_468.md | |
| EN 301 192 v1.7.1 — data broadcasting | en_301_192.md | |
| TS 102 006 v1.7.1 — System Software Update | ts_102_006_ssu.md | |
| TS 102 323 v1.4.1 — TV-Anytime carriage | ts_102_323_tva.md | |
| TS 102 809 v1.3.1 — application signalling | ts_102_809_apps.md | |
| TS 102 772 v1.1.1 — MPE-IFEC | ts_102_772_mpe_ifec.md | |
| EN 303 560 v1.1.1 — TTML subtitling | en_303_560_ttml.md | |
| TS 102 727 v1.1.1 — MHP (XAIT) | vendored PDF only (cites give page + table) | |
| TR 101 202 v1.2.1 — data broadcasting guidelines | 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.
Demux in 10 lines
Feed 188-byte TS packets to [SiDemux]; it filters by PID, reassembles
sections, validates CRCs, follows the PAT to PMT PIDs, and version-gates so a
steady carousel emits each changed section only once. You get a SectionEvent per
changed section:
use SiDemux;
use AnyTableSection;
let mut demux = builder.build;
for packet in ts_packets
See examples/si_dump.rs for a complete file-reading CLI
(cargo run -p dvb-si --example si_dump -- file.ts [--json]).
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); and DescriptorRegistry lets you plug in private
descriptors at runtime. All are generated from a single declarative list so the
dispatcher can never drift from the implemented set.
use AnyDescriptor;
for item in eit_event.descriptors.iter
Upgrading? For 3.x → 4.0, see MIGRATION-4.0.md: section parser renames,
AnyTableSection,table_section(), CamelCaseTableId, and multi-section collection. For older breaks, see MIGRATION-3.1.md and MIGRATION-2.0.md.
Usage
use Parse;
use SdtSection;
// `section_bytes`: one complete SDT section, e.g. from `SectionReassembler`.
let sdt = parse?;
for service in &sdt.services
Collect all sections for a logical table before using table-wide fields. In a live stream, collector errors are section-scoped: log/drop that input section and keep feeding later sections unless your application chooses strict failure.
use ;
use SiDemux;
use eit;
use ;
let mut demux = builder.build;
let mut nit_sections = new;
let mut eit_sections = new;
for event in demux.feed
// Long-running EPG collectors can prune application-defined old state.
eit_sections.retain_logical;
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.
Features
Default: chrono (MJD+BCD → DateTime<Utc>), ts (TS packet +
SectionReassembler), serde.
serde is Serialize-only — for display/export (JSON via serde_json);
parsing FROM JSON is deliberately unsupported, re-parse from the wire bytes.
Optional (off by default):
yoke — yoke::Yokeable on every zero-copy view type plus an Owned<T>
wrapper, so you can own a parsed view past the input buffer's borrow (store it
in a struct field, a cache, a watch/broadcast channel, or send it across a
thread) without re-parsing or a hand-written mirror type:
use Arc;
use ;
use Parse;
let bytes: = from; // own the section
let pmt: = try_new?;
let view: &PmtSection = pmt.get; // no re-parse, no lifetime
= { = "4.0", = false } # tight build
Family
dvb-common — traits + CRC-32
dvb-t2mi — T2-MI, all 12 packet types
dvb-bbframe — S2/S2X/T2 BBFRAME
For GSE see the existing dvb-gse crate.
License
Licensed under either of MIT or Apache-2.0, at your option.