Skip to main content

Crate dvb_simulcrypt

Crate dvb_simulcrypt 

Source
Expand description

DVB SimulCrypt — head-end CA message framing (ETSI TS 103 197 V1.5.1).

The DVB SimulCrypt head-end carries control/response messages between its conditional-access components over TCP. This crate is a codec for those messages — it does not open sockets. It implements the generic message structure and the two CA-bearing interfaces:

  • SimulcryptMessage — the generic generic_message (TS 103 197 §4.4.1, Table 1b): a 5-byte header (protocol_version + message_type + message_length, big-endian) followed by an ordered list of TLV Parameters (parameter_type + parameter_length + value). message_length and every parameter_length are recomputed on serialize from the typed fields — there is no raw passthrough.
  • ECMG ⇔ SCS (clause 5): EcmgScsMessageType (channel/stream setup, test, status, close, error, plus CW_provision 0x0201 and ECM_response 0x0202) and the Table 5 EcmgScsParameterType registry (Super_CAS_id 0x0001ECM_id 0x0019, error_status 0x7000, error_information 0x7001) + EcmgErrorStatus (Table 6).
  • EMMG/PDG ⇔ MUX (clause 6): EmmgMuxMessageType (channel/stream messages, stream_BW_request/allocation, data_provision 0x0211) and the Table 7 EmmgMuxParameterType registry + EmmgErrorStatus (Table 8), plus the DataType (§6.2.3) and SectionTspktFlag value tables.

§Interface scoping

The 16-bit message_type/parameter_type spaces are interface-scoped: the same value means different things on different interfaces, and the interface is not on the wire — it is fixed by which TCP connection the message arrived on (analogous to a resource scope). So SimulcryptMessage::parse_on takes an Interface hint and decodes the raw values into the matching interface-tagged MessageType / ParameterType enums.

§Signalling only — no crypto

The control words in CP_CW_combination/CW_encryption, the ECMs in ECM_datagram, and the EMM/private data in datagram are carried as opaque borrowed bytes. This crate frames and parses them; it never decrypts or interprets them. The non-implemented interfaces (C(P)SIG⇔(P)SIG, EIS⇔SCS, (P)SIG⇔MUX, ACG⇔EIS, SIMCOMP⇔MUXCONFIG) share the same framing but are not modelled.

#![no_std] + alloc; depends only on dvb-common.

§Examples

Build an ECMG⇔SCS channel_setup from typed fields and round-trip it:

use dvb_simulcrypt::{
    EcmgScsMessageType, EcmgScsParameterType, Interface, MessageType, Parameter,
    ParameterType, SimulcryptMessage,
};
use dvb_common::traits::{Parse, Serialize};

let ecm_channel_id = [0x00, 0x2A]; // 0x002A
let super_cas_id = [0x00, 0x01, 0x00, 0x02]; // CA_system_id | subsystem_id
let msg = SimulcryptMessage::new(
    Interface::EcmgScs.protocol_version(),
    MessageType::EcmgScs(EcmgScsMessageType::ChannelSetup),
    vec![
        Parameter::new(
            ParameterType::EcmgScs(EcmgScsParameterType::EcmChannelId),
            &ecm_channel_id,
        ),
        Parameter::new(
            ParameterType::EcmgScs(EcmgScsParameterType::SuperCasId),
            &super_cas_id,
        ),
    ],
);

let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
assert_eq!(SimulcryptMessage::parse_on(Interface::EcmgScs, &buf).unwrap(), msg);

§Runnable examples

Run with cargo run -p dvb-simulcrypt --example <name>.

§build_channel_setup

/// Build an ECMG⇔SCS `channel_setup` message from typed fields, serialize it
/// (recomputing message_length + each parameter_length), and dump the wire
/// bytes.
///
/// ```sh
/// cargo run -p dvb-simulcrypt --example build_channel_setup
/// ```
use dvb_common::traits::{Parse, Serialize};
use dvb_simulcrypt::{
    EcmgScsMessageType, EcmgScsParameterType, Interface, MessageType, Parameter, ParameterType,
    SimulcryptMessage,
};

fn main() {
    // channel_setup (Table, §5.4.1): ECM_channel_id (1) + Super_CAS_id (1).
    let ecm_channel_id = [0x00u8, 0x2A]; // ECM_channel_id = 0x002A
    let super_cas_id = [0x00u8, 0x01, 0x00, 0x02]; // CA_system_id | CA_subsystem_id

    let msg = SimulcryptMessage::new(
        Interface::EcmgScs.protocol_version(),
        MessageType::EcmgScs(EcmgScsMessageType::ChannelSetup),
        vec![
            Parameter::new(
                ParameterType::EcmgScs(EcmgScsParameterType::EcmChannelId),
                &ecm_channel_id,
            ),
            Parameter::new(
                ParameterType::EcmgScs(EcmgScsParameterType::SuperCasId),
                &super_cas_id,
            ),
        ],
    );

    let mut bytes = vec![0u8; msg.serialized_len()];
    let n = msg.serialize_into(&mut bytes).unwrap();

    println!("interface: {}", msg.interface());
    println!(
        "message_type: {} (0x{:04X})",
        msg.message_type,
        msg.message_type.to_u16()
    );
    println!("protocol_version: 0x{:02X}", msg.protocol_version);
    println!("message_length (body): {}", msg.body_len());
    println!("parameters: {}", msg.parameters.len());
    for p in &msg.parameters {
        print!("  {} (0x{:04X}) =", p.ptype, p.ptype.to_u16());
        for b in p.value {
            print!(" {b:02X}");
        }
        println!();
    }
    print!("wire bytes ({n}):");
    for b in &bytes {
        print!(" {b:02X}");
    }
    println!();

    // Round-trip: parse against the same interface, expect equality.
    assert_eq!(
        SimulcryptMessage::parse_on(Interface::EcmgScs, &bytes).unwrap(),
        msg
    );
    // The default `Parse` impl also targets ECMG⇔SCS.
    assert_eq!(SimulcryptMessage::parse(&bytes).unwrap(), msg);
    println!("round-trip: OK");
}

§parse_cw_provision

/// Read the committed `cw_provision.bin` fixture (an ECMG⇔SCS `CW_provision`
/// message), parse it against the ECMG⇔SCS interface, walk its parameters
/// (treating the CW inside `CP_CW_combination` as opaque), and byte-exact
/// round-trip it.
///
/// ```sh
/// cargo run -p dvb-simulcrypt --example parse_cw_provision
/// ```
use std::fs;

use dvb_common::traits::Serialize;
use dvb_simulcrypt::{
    EcmgScsParameterType, Interface, MessageType, ParameterType, SimulcryptMessage,
};

fn main() {
    let path = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/fixtures/cw_provision.bin"
    );
    let bytes = match fs::read(path) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("fixture not available ({e}); nothing to do");
            return;
        }
    };

    let msg = SimulcryptMessage::parse_on(Interface::EcmgScs, &bytes)
        .expect("CW_provision fixture must parse");

    println!("interface: {}", msg.interface());
    println!(
        "message_type: {} (0x{:04X})",
        msg.message_type,
        msg.message_type.to_u16()
    );
    assert!(matches!(
        msg.message_type,
        MessageType::EcmgScs(dvb_simulcrypt::EcmgScsMessageType::CwProvision)
    ));

    for p in &msg.parameters {
        print!(
            "  {} (0x{:04X}), {} bytes:",
            p.ptype,
            p.ptype.to_u16(),
            p.value.len()
        );
        for b in p.value {
            print!(" {b:02X}");
        }
        println!();
    }

    // The CP_CW_combination's CW is opaque — we only locate the parameter.
    // Guard: the parameter must carry at least 2 bytes for the CP number.
    if let Some(cpcw) = msg.find(ParameterType::EcmgScs(
        EcmgScsParameterType::CpCwCombination,
    )) {
        if let Some(cp_bytes) = cpcw.value.get(0..2) {
            let cp = u16::from_be_bytes([cp_bytes[0], cp_bytes[1]]);
            println!(
                "CP_CW_combination: CP={cp}, CW={} opaque bytes",
                cpcw.value.len() - 2
            );
        } else {
            println!(
                "CP_CW_combination: value too short ({} bytes, need ≥2)",
                cpcw.value.len()
            );
        }
    }

    // Byte-exact round-trip.
    let mut out = vec![0u8; msg.serialized_len()];
    let n = msg.serialize_into(&mut out).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(
        out, bytes,
        "serialize must reproduce the fixture byte-for-byte"
    );
    println!("byte-exact round-trip: OK");
}

Structs§

Parameter
One TLV parameter of a SimulcryptMessage (Table 1b): a typed parameter_type plus a borrowed, opaque parameter_value.
SimulcryptMessage
A generic SimulCrypt message (TS 103 197 Table 1b): the 5-byte header plus an ordered list of TLV Parameters.

Enums§

DataType
data_type values (TS 103 197 §6.2.3 p. 42) — what a datagram carries.
EcmgErrorStatus
ECMG⇔SCS error_status values (TS 103 197 Table 6, §5.6 p. 39).
EcmgScsMessageType
ECMG⇔SCS message_type values (TS 103 197 Table 3, §4.4.1 pp. 27-28).
EcmgScsParameterType
ECMG⇔SCS parameter_type values (TS 103 197 Table 5, §5.2 p. 31).
EmmgErrorStatus
EMMG/PDG⇔MUX error_status values (TS 103 197 Table 8, §6.2.6 p. 47).
EmmgMuxMessageType
EMMG/PDG⇔MUX message_type values (TS 103 197 Table 3, §4.4.1 pp. 27-28).
EmmgMuxParameterType
EMMG/PDG⇔MUX parameter_type values (TS 103 197 Table 7, §6.2.2 p. 42).
Error
A SimulCrypt parse / serialize error.
Interface
The SimulCrypt connection-oriented interface a message belongs to.
MessageType
Interface-tagged message_type: decode a raw value once the Interface is known.
ParameterType
Interface-tagged parameter_type: decode a raw value once the Interface is known.
SectionTspktFlag
section_TSpkt_flag values (TS 103 197 §6.2.3 p. 43) — the datagram framing in datagram parameters. (The same flag, with the same meaning, is carried on the ECMG⇔SCS interface for ECM_datagram.)

Constants§

HEADER_LEN
Bytes of the fixed generic_message header: protocol_version (1) + message_type (2) + message_length (2).
PARAMETER_HEADER_LEN
Bytes of a parameter TLV’s fixed prefix: parameter_type (2) + parameter_length (2).

Type Aliases§

Result
Result alias for SimulCrypt parsing.