dvb-ci 0.12.0

DVB Common Interface (EN 50221) — APDU/resource objects, session/transport PDUs, and a CA_PMT builder from a dvb-si PMT.
Documentation
//! Host Control objects — ETSI EN 50221 §8.5.1, Tables 27-30 (PDF pp. 33-34).
//!
//! - `tune` (`9F 84 00`, Table 27) — retune to a different service.
//! - `replace` (`9F 84 01`, Table 28) — temporarily replace one PID with another.
//! - `clear_replace` (`9F 84 02`, Table 29) — undo Replace operations by reference.
//! - `ask_release` (`9F 84 03`, Table 30) — header-only release request.

use crate::error::{Error, Result};
use crate::reserved::ReservedOnes;
use crate::tag::{self, ApduTag};
use crate::traits::ApduDef;
use broadcast_common::len::need_in;
use broadcast_common::{Parse, Serialize};
use mpeg_ts::pid::Pid;

/// `tune()` object (Table 27): retune to a different service. Parameters are the
/// EN 300 468 identifiers, each 16 bits.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Tune {
    /// How the `length_field` was coded on the wire: a non-minimal long form
    /// (e.g. `82 00 09`) is preserved so the object re-serializes
    /// byte-for-byte; [`LengthForm::Minimal`](crate::length::LengthForm::Minimal) for a hand-built value.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub length_form: crate::length::LengthForm,

    /// `network_id`.
    pub network_id: u16,
    /// `original_network_id`.
    pub original_network_id: u16,
    /// `transport_stream_id`.
    pub transport_stream_id: u16,
    /// `service_id`.
    pub service_id: u16,
}

// network_id(2) + original_network_id(2) + transport_stream_id(2) + service_id(2).
const TUNE_BODY: usize = 8;

impl<'a> Parse<'a> for Tune {
    type Error = Error;
    fn parse(bytes: &'a [u8]) -> Result<Self> {
        let (length_form, body) = super::parse_apdu_header(bytes, tag::TUNE, "tune")?;
        need_in(body, TUNE_BODY, "tune")?;
        super::reject_trailing_body(body, TUNE_BODY, "tune")?;
        Ok(Self {
            length_form,
            network_id: u16::from_be_bytes([body[0], body[1]]),
            original_network_id: u16::from_be_bytes([body[2], body[3]]),
            transport_stream_id: u16::from_be_bytes([body[4], body[5]]),
            service_id: u16::from_be_bytes([body[6], body[7]]),
        })
    }
}

impl Serialize for Tune {
    type Error = Error;
    fn serialized_len(&self) -> usize {
        super::apdu_len(TUNE_BODY, self.length_form)
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
        let mut pos = super::write_apdu_header(tag::TUNE, TUNE_BODY, self.length_form, buf)?;
        buf[pos..pos + 2].copy_from_slice(&self.network_id.to_be_bytes());
        buf[pos + 2..pos + 4].copy_from_slice(&self.original_network_id.to_be_bytes());
        buf[pos + 4..pos + 6].copy_from_slice(&self.transport_stream_id.to_be_bytes());
        buf[pos + 6..pos + 8].copy_from_slice(&self.service_id.to_be_bytes());
        pos += TUNE_BODY;
        Ok(pos)
    }
}

impl ApduDef<'_> for Tune {
    const TAG: ApduTag = tag::TUNE;
    const NAME: &'static str = "TUNE";
}

/// `replace()` object (Table 28): temporarily replace one component PID with
/// another from the same multiplex.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Replace {
    /// How the `length_field` was coded on the wire: a non-minimal long form
    /// (e.g. `82 00 09`) is preserved so the object re-serializes
    /// byte-for-byte; [`LengthForm::Minimal`](crate::length::LengthForm::Minimal) for a hand-built value.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub length_form: crate::length::LengthForm,

    /// `replacement_ref` — application-allocated reference matched by Clear Replace.
    pub replacement_ref: u8,
    /// 13-bit `replaced_PID` — PID of the component to replace.
    pub replaced_pid: Pid,
    /// 13-bit `replacement_PID` — PID to replace it with.
    pub replacement_pid: Pid,
    /// The 3-bit `reserved` field in front of `replaced_PID` as received,
    /// preserved so the object re-serializes byte-for-byte; `0b111` (the
    /// all-ones fill) when hand-built.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub replaced_reserved: ReservedOnes<3>,
    /// The 3-bit `reserved` field in front of `replacement_PID` as received
    /// (see [`Replace::replaced_reserved`]).
    #[cfg_attr(feature = "serde", serde(skip))]
    pub replacement_reserved: ReservedOnes<3>,
}

// replacement_ref(1) + reserved/replaced_PID(2) + reserved/replacement_PID(2).
const REPLACE_BODY: usize = 5;

impl<'a> Parse<'a> for Replace {
    type Error = Error;
    fn parse(bytes: &'a [u8]) -> Result<Self> {
        let (length_form, body) = super::parse_apdu_header(bytes, tag::REPLACE, "replace")?;
        need_in(body, REPLACE_BODY, "replace")?;
        super::reject_trailing_body(body, REPLACE_BODY, "replace")?;
        let replacement_ref = body[0];
        let replaced_pid = Pid::new((((body[1] & 0x1F) as u16) << 8) | body[2] as u16);
        let replacement_pid = Pid::new((((body[3] & 0x1F) as u16) << 8) | body[4] as u16);
        Ok(Self {
            length_form,
            replacement_ref,
            replaced_pid,
            replacement_pid,
            replaced_reserved: ReservedOnes::from_bits(body[1] >> 5),
            replacement_reserved: ReservedOnes::from_bits(body[3] >> 5),
        })
    }
}

impl Serialize for Replace {
    type Error = Error;
    fn serialized_len(&self) -> usize {
        super::apdu_len(REPLACE_BODY, self.length_form)
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
        let mut pos = super::write_apdu_header(tag::REPLACE, REPLACE_BODY, self.length_form, buf)?;
        buf[pos] = self.replacement_ref;
        // reserved(3), replaced_PID(13).
        buf[pos + 1] =
            (self.replaced_reserved.get() << 5) | ((self.replaced_pid.value() >> 8) as u8 & 0x1F);
        buf[pos + 2] = self.replaced_pid.value() as u8;
        // reserved(3), replacement_PID(13).
        buf[pos + 3] = (self.replacement_reserved.get() << 5)
            | ((self.replacement_pid.value() >> 8) as u8 & 0x1F);
        buf[pos + 4] = self.replacement_pid.value() as u8;
        pos += REPLACE_BODY;
        Ok(pos)
    }
}

impl ApduDef<'_> for Replace {
    const TAG: ApduTag = tag::REPLACE;
    const NAME: &'static str = "REPLACE";
}

/// `clear_replace()` object (Table 29): undo all Replace operations sharing a
/// `replacement_ref`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ClearReplace {
    /// How the `length_field` was coded on the wire: a non-minimal long form
    /// (e.g. `82 00 09`) is preserved so the object re-serializes
    /// byte-for-byte; [`LengthForm::Minimal`](crate::length::LengthForm::Minimal) for a hand-built value.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub length_form: crate::length::LengthForm,

    /// `replacement_ref` — matches the value used in one or more Replace objects.
    pub replacement_ref: u8,
}

impl<'a> Parse<'a> for ClearReplace {
    type Error = Error;
    fn parse(bytes: &'a [u8]) -> Result<Self> {
        let (length_form, body) =
            super::parse_apdu_header(bytes, tag::CLEAR_REPLACE, "clear_replace")?;
        let replacement_ref = *body.first().ok_or(Error::BufferTooShort(
            broadcast_common::error::BufferTooShort {
                need: 1,
                have: 0,
                what: "clear_replace replacement_ref",
            },
        ))?;
        super::reject_trailing_body(body, 1, "clear_replace")?;
        Ok(Self {
            length_form,
            replacement_ref,
        })
    }
}

impl Serialize for ClearReplace {
    type Error = Error;
    fn serialized_len(&self) -> usize {
        super::apdu_len(1, self.length_form)
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
        let mut pos = super::write_apdu_header(tag::CLEAR_REPLACE, 1, self.length_form, buf)?;
        buf[pos] = self.replacement_ref;
        pos += 1;
        Ok(pos)
    }
}

impl ApduDef<'_> for ClearReplace {
    const TAG: ApduTag = tag::CLEAR_REPLACE;
    const NAME: &'static str = "CLEAR_REPLACE";
}

/// `ask_release()` object (Table 30): header-only release request from the host.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AskRelease {
    /// How the `length_field` was coded on the wire: a non-minimal long form
    /// (e.g. `82 00 09`) is preserved so the object re-serializes
    /// byte-for-byte; [`LengthForm::Minimal`](crate::length::LengthForm::Minimal) for a hand-built value.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub length_form: crate::length::LengthForm,
}

impl<'a> Parse<'a> for AskRelease {
    type Error = Error;
    fn parse(bytes: &'a [u8]) -> Result<Self> {
        let length_form = super::parse_empty_apdu(bytes, tag::ASK_RELEASE, "ask_release")?;
        Ok(Self { length_form })
    }
}

impl Serialize for AskRelease {
    type Error = Error;
    fn serialized_len(&self) -> usize {
        super::empty_apdu_len(self.length_form)
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
        super::serialize_empty_apdu(tag::ASK_RELEASE, self.length_form, buf)
    }
}

impl ApduDef<'_> for AskRelease {
    const TAG: ApduTag = tag::ASK_RELEASE;
    const NAME: &'static str = "ASK_RELEASE";
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn tune_round_trips_and_bites() {
        let t = Tune {
            length_form: crate::length::LengthForm::Minimal,
            network_id: 0x1122,
            original_network_id: 0x3344,
            transport_stream_id: 0x5566,
            service_id: 0x7788,
        };
        let bytes = t.try_to_bytes().unwrap();
        assert_eq!(
            bytes,
            [
                0x9F, 0x84, 0x00, 0x08, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
            ]
        );
        assert_eq!(Tune::parse(&bytes).unwrap(), t);
        let mut other = t;
        other.service_id = 0x9999;
        assert_ne!(bytes, other.try_to_bytes().unwrap());
    }

    #[test]
    fn replace_round_trips_and_bites() {
        let r = Replace {
            length_form: crate::length::LengthForm::Minimal,
            replacement_ref: 0x07,
            replaced_pid: Pid::new(0x0123),    // 13-bit
            replacement_pid: Pid::new(0x01FF), // 13-bit
            replaced_reserved: Default::default(),
            replacement_reserved: Default::default(),
        };
        let bytes = r.try_to_bytes().unwrap();
        // body: 07, reserved(111)+0x0123 => 0xE1 0x23, reserved+0x01FF => 0xE1 0xFF
        assert_eq!(
            bytes,
            [0x9F, 0x84, 0x01, 0x05, 0x07, 0xE1, 0x23, 0xE1, 0xFF]
        );
        let parsed = Replace::parse(&bytes).unwrap();
        assert_eq!(parsed, r);
        let mut other = r;
        other.replacement_pid = Pid::new(0x0001);
        assert_ne!(bytes, other.try_to_bytes().unwrap());
    }

    #[test]
    fn replace_oversized_pid_is_unrepresentable() {
        // Both PID fields are typed 13-bit `Pid`s: 14-bit values are refused
        // at construction instead of at serialize time.
        assert_eq!(Pid::try_new(0x2010).unwrap_err().value, 0x2010);
        assert!(Pid::try_new(0x1FFF).is_ok());
    }

    #[test]
    fn clear_replace_round_trips_and_bites() {
        let c = ClearReplace {
            length_form: crate::length::LengthForm::Minimal,
            replacement_ref: 0x42,
        };
        let bytes = c.try_to_bytes().unwrap();
        assert_eq!(bytes, [0x9F, 0x84, 0x02, 0x01, 0x42]);
        assert_eq!(ClearReplace::parse(&bytes).unwrap(), c);
        let other = ClearReplace {
            length_form: crate::length::LengthForm::Minimal,
            replacement_ref: 0x43,
        };
        assert_ne!(bytes, other.try_to_bytes().unwrap());
    }

    #[test]
    fn ask_release_round_trips() {
        let bytes = AskRelease::default().try_to_bytes().unwrap();
        assert_eq!(bytes, [0x9F, 0x84, 0x03, 0x00]);
        assert_eq!(AskRelease::parse(&bytes).unwrap(), AskRelease::default());
    }
}