Skip to main content

Crate dvb_ci

Crate dvb_ci 

Source
Expand description

DVB Common Interface (EN 50221) — the host↔CICAM wire protocol.

Parses + builds the EN 50221 protocol objects: resource APDUs (Resource Manager, Application Information, CA support incl. ca_pmt/ca_pmt_reply, Date-Time, Host Control, the full MMI set — low-level/display/keypad/subtitle/ download and high-level menu/list/enq — and Low-Speed Communications), the session-layer SPDUs and transport-layer TPDUs, plus a CA_PMT builder that turns a dvb-si PMT into the object handed to a CICAM.

Every wire structure implements dvb_common::Parse / dvb_common::Serialize symmetrically (parse → serialize is byte-identical), with all length fields computed from content. Spec citations live in each module doc; the render-verified transcription is in docs/en_50221/.

Scope: the wire/protocol layer only. The physical PC-Card transport and CI+ crypto (the CC resource) are out of scope. #![no_std] (+ alloc).

§Layers

  • tag / length — the 3-byte apdu_tag and the ASN.1-style length_field shared by all PDUs.
  • resource — the 4-octet resource_identifier().
  • objects — application-layer APDU objects, dispatched by AnyApdu.
  • spdu — session-layer SPDUs (open/create/close session, session number).
  • tpdu — transport-layer framing (C_TPDU/R_TPDU + connection mgmt).
  • builder — the CA_PMT projection from a dvb-si PMT.

All Table 58 apdu_tags are now typed; an unallocated/private tag is produced by AnyApdu::parse as AnyApdu::Unknown (lossless round-trip). The CI+ crypto (CC resource) and the PC-Card hardware transport remain out of scope.

§Examples

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

§build_ca_pmt

//! Build a `ca_pmt` object from a `dvb-si` PMT and print the wire bytes.
//!
//! Run with: `cargo run -p dvb-ci --example build_ca_pmt`
//!
//! Shows the headline workflow: a host takes a parsed MPEG-2 PMT, strips every
//! non-CA descriptor, and hands the CA-only `ca_pmt` object to a CICAM.

use dvb_ci::builder::build_ca_pmt;
use dvb_ci::objects::ca_pmt::{CaPmt, CaPmtCmdId, CaPmtListManagement};
use dvb_common::Parse;
use dvb_si::tables::pmt::PmtSection;

fn main() {
    // A small PMT (program 1): a programme-level CA_descriptor (tag 0x09) plus a
    // non-CA registration descriptor, one scrambled video ES with an ES-level
    // CA_descriptor, and one clear audio ES with only a language descriptor.
    let pmt_bytes = sample_pmt();
    let pmt = PmtSection::parse(&pmt_bytes).expect("valid PMT");

    let built = build_ca_pmt(&pmt, CaPmtListManagement::Only, CaPmtCmdId::OkDescrambling);
    let wire = built.to_bytes();

    println!("ca_pmt APDU ({} bytes): {:02X?}", wire.len(), wire);

    // Re-parse to confirm the projection round-trips.
    let parsed = CaPmt::parse(&wire).expect("valid ca_pmt");
    println!("list_management : {}", parsed.list_management);
    println!("program_number : {}", parsed.program_number);
    println!(
        "program CA desc : {} byte(s) (non-CA descriptors stripped)",
        parsed.program_ca_descriptors.len()
    );
    for (i, s) in parsed.streams.iter().enumerate() {
        println!(
            "  ES[{i}] type=0x{:02X} pid=0x{:04X} ca_descriptors={} cmd_id={:?}",
            s.stream_type,
            s.elementary_pid,
            s.ca_descriptors.len(),
            s.cmd_id.map(|c| c.name())
        );
    }
}

fn sample_pmt() -> Vec<u8> {
    let prog_ca = [0x09, 0x04, 0x05, 0x00, 0xE1, 0x00]; // CA_system_id 0x0500, PID 0x0100
    let reg = [0x05, 0x04, b'H', b'D', b'M', b'V']; // non-CA, will be stripped
    let mut program_info = Vec::new();
    program_info.extend_from_slice(&prog_ca);
    program_info.extend_from_slice(&reg);

    let es0_ca = [0x09, 0x04, 0x05, 0x00, 0xE1, 0x01];
    let lang = [0x0A, 0x04, b'e', b'n', b'g', 0x00];

    let mut body = vec![0x02, 0x00, 0x00, 0x00, 0x01, 0xC3, 0x00, 0x00, 0xE2, 0x00];
    let pil = program_info.len();
    body.push(0xF0 | ((pil >> 8) as u8 & 0x0F));
    body.push(pil as u8);
    body.extend_from_slice(&program_info);

    body.extend_from_slice(&[
        0x02,
        0xE2,
        0x00,
        0xF0 | ((es0_ca.len() >> 8) as u8),
        es0_ca.len() as u8,
    ]);
    body.extend_from_slice(&es0_ca);
    body.extend_from_slice(&[
        0x03,
        0xE2,
        0x01,
        0xF0 | ((lang.len() >> 8) as u8),
        lang.len() as u8,
    ]);
    body.extend_from_slice(&lang);

    let section_length = body.len() - 3 + 4;
    body[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
    body[2] = section_length as u8;
    let crc = dvb_common::crc32_mpeg2::compute(&body);
    body.extend_from_slice(&crc.to_be_bytes());
    body
}

§parse_apdu

//! Parse an APDU with the unified [`AnyApdu`] dispatcher and round-trip it.
//!
//! Run with: `cargo run -p dvb-ci --example parse_apdu`

use dvb_ci::objects::ca_info::CaInfo;
use dvb_ci::AnyApdu;
use dvb_common::Serialize;

fn main() {
    // A ca_info() APDU (9F 80 31) advertising two CA_system_ids: 0x0500, 0x0B00.
    let info = CaInfo {
        ca_system_ids: vec![0x0500, 0x0B00],
    };
    let wire = info.to_bytes();
    println!("ca_info APDU   : {:02X?}", wire);

    // Route it through the tag dispatcher without knowing the type in advance.
    let any = AnyApdu::parse(&wire).expect("valid APDU");
    println!("dispatched as  : {} (tag {})", any.name(), any.tag());

    if let AnyApdu::CaInfo(ci) = &any {
        println!("CA_system_ids  : {:04X?}", ci.ca_system_ids);
    }

    // Symmetric serialize: AnyApdu round-trips byte-for-byte.
    let back = any.to_bytes();
    assert_eq!(back, wire);
    println!("round-trip     : OK ({} bytes)", back.len());

    // An unrecognised tag (Tenq 9F 88 07, an MMI high-level object not yet
    // implemented) is preserved losslessly as AnyApdu::Unknown.
    let unknown_wire = [0x9F, 0x88, 0x07, 0x02, 0xAA, 0xBB];
    let unknown = AnyApdu::parse(&unknown_wire).expect("valid APDU framing");
    println!(
        "unknown tag    : {} -> preserved, round-trips: {}",
        unknown.name(),
        unknown.to_bytes() == unknown_wire
    );
}

Re-exports§

pub use any::AnyApdu;
pub use error::Error;
pub use error::Result;
pub use resource::ResourceId;
pub use tag::ApduTag;
pub use traits::ApduDef;

Modules§

any
Unified APDU dispatch: AnyApdu.
builder
CA_PMT builder — project a dvb-si PMT into the ca_pmt object handed to a CICAM — ETSI EN 50221 §8.4.3.4 (Table 25), per docs/en_50221/ca-pmt.md.
ci_ext
DVB CI Extensions (ETSI TS 101 699) — the resource-scoped APDU layer.
ci_plus
CI Plus extensions (ETSI TS 103 205) — the resource-scoped APDU layer.
error
Error type for EN 50221 Common Interface parsing/serialization.
length
The EN 50221 length_field — the ASN.1-style length used by every PDU at the Transport, Session and Application layers — ETSI EN 50221 §7, Table 1 (PDF p. 11).
objects
Application-layer APDU objects (resource APDUs) — ETSI EN 50221 §8.4-§8.6.
resource
resource_identifier() — the 4-octet resource identity — ETSI EN 50221 §8.2.2, Table 15 (PDF p. 24) + §8.8.1, Table 57 (PDF p. 54).
spdu
Session Protocol Data Unit (SPDU) framing — ETSI EN 50221 §7.2.4-7.2.7, Tables 4-14 (PDF pp. 19-23).
tag
apdu_tag — the 3-byte ASN.1 application-object tag — ETSI EN 50221 §8.8.2, Table 58 + Figure 16 (PDF pp. 56-57).
tpdu
Transport Protocol Data Unit (TPDU) framing — ETSI EN 50221 Annex A §A.4.1, Tables A.1-A.16 (PDF pp. 63-70).
traits
dvb-ci-specific dispatch trait. Parse / Serialize come from dvb_common and are imported at the call sites.