Skip to main content

dvb_csa/
ts.rs

1//! TS packet helpers — scramble/descramble 188-byte MPEG-2 TS packets.
2//!
3//! Handles adaptation-field offset and transport_scrambling_control bits
4//! per ISO/IEC 13818-1 §2.4.3.3/§2.4.3.4 (ETSI TS 100 289 scrambles the
5//! payload located this way, but does not itself redefine TS framing).
6use crate::csa;
7use crate::error::Error;
8use crate::key::ControlWord;
9use mpeg_ts::ts::{SCRAMBLING_MASK, TS_PACKET_SIZE, TsHeader};
10
11/// `transport_scrambling_control` value for "scrambled with the even control
12/// word" (ISO/IEC 13818-1 §2.4.3.3 Table 2-4: bits `[7:6]` of byte 3 = `10`).
13const TSC_EVEN_KEY: u8 = 0x80;
14
15/// One byte: the `adaptation_field_length` field itself, immediately after
16/// the 4-byte TS header when `adaptation_field_control` signals a present
17/// adaptation field (ISO/IEC 13818-1 §2.4.3.4).
18const ADAPTATION_FIELD_LENGTH_SIZE: usize = 1;
19
20/// Scramble the payload of a 188-byte TS packet in-place.
21///
22/// Skips the 4-byte header and any adaptation field bytes.
23/// Sets the transport_scrambling_control bits to `10` (even key).
24pub fn scramble_ts_packet(
25    cw: &ControlWord,
26    packet: &mut [u8; TS_PACKET_SIZE],
27) -> Result<(), Error> {
28    let payload = ts_payload_mut(packet)?;
29    csa::scramble(cw, payload);
30    packet[3] = (packet[3] & !SCRAMBLING_MASK) | TSC_EVEN_KEY;
31    Ok(())
32}
33
34/// Descramble the payload of a 188-byte TS packet in-place.
35///
36/// Skips the 4-byte header and any adaptation field bytes.
37/// Clears the transport_scrambling_control bits to `00`.
38pub fn descramble_ts_packet(
39    cw: &ControlWord,
40    packet: &mut [u8; TS_PACKET_SIZE],
41) -> Result<(), Error> {
42    let payload = ts_payload_mut(packet)?;
43    csa::descramble(cw, payload);
44    packet[3] &= !SCRAMBLING_MASK;
45    Ok(())
46}
47
48/// Get a mutable slice to the TS packet payload, skipping the header and
49/// adaptation field.
50///
51/// The adaptation_field_control decode reuses `mpeg_ts::ts::TsHeader::parse`
52/// (ISO/IEC 13818-1 §2.4.3.3) rather than re-deriving the same bit masks a
53/// second time — an overrun/bit-order fix in that parser now reaches this
54/// crate too. `TsHeader::parse` does not itself locate the payload byte
55/// offset (that also needs the `adaptation_field_length` byte, §2.4.3.4,
56/// which is not a header field), and `mpeg_ts::ts::TsPacket` — which does
57/// compute that offset — only exposes an **immutable** `payload: &[u8]`
58/// borrowed from its input, with no in-place-mutable equivalent. CSA
59/// (de)scrambling must write the descrambled bytes back into the caller's
60/// own buffer, so this function still computes the offset and takes the
61/// `&mut` slice itself rather than depending on a mutable view `mpeg-ts`
62/// does not provide.
63fn ts_payload_mut(packet: &mut [u8; TS_PACKET_SIZE]) -> Result<&mut [u8], Error> {
64    let header = TsHeader::parse(&packet[..TsHeader::serialized_len()])
65        .expect("packet[..TsHeader::serialized_len()] is always exactly 4 bytes");
66
67    if !header.has_payload {
68        return Err(Error::BufferTooShort { need: 1, have: 0 });
69    }
70
71    let mut payload_start = TsHeader::serialized_len();
72
73    if header.has_adaptation {
74        let af_len = packet[payload_start] as usize; // adaptation_field_length, §2.4.3.4
75        payload_start += ADAPTATION_FIELD_LENGTH_SIZE + af_len;
76    }
77
78    if payload_start >= TS_PACKET_SIZE {
79        return Err(Error::BufferTooShort { need: 1, have: 0 });
80    }
81
82    Ok(&mut packet[payload_start..TS_PACKET_SIZE])
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn roundtrip_ts_packet() {
91        let cw = ControlWord::from_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
92        // Build a minimal TS packet: header 0x47, PID 0x0100, no adaptation, payload
93        let mut packet = [0u8; 188];
94        packet[0] = 0x47; // sync byte
95        packet[1] = 0x41; // TEI=0, PUSI=1, priority=0, PID high=0x0100>>8
96        packet[2] = 0x00; // PID low=0x00
97        packet[3] = 0x10; // no scrambling, adaptation_field_control=01 (payload only), CC=0
98        // Fill payload with non-zero data (need at least 8 bytes)
99        for i in 0..184 {
100            packet[4 + i] = (i % 256) as u8;
101        }
102
103        let original = packet;
104        scramble_ts_packet(&cw, &mut packet).unwrap();
105        assert_ne!(packet[4..], original[4..]);
106        assert_eq!(packet[3] & 0xc0, 0x80); // scrambling bits set
107
108        descramble_ts_packet(&cw, &mut packet).unwrap();
109        // Compare payload only (skip header)
110        assert_eq!(packet[4..], original[4..]);
111        assert_eq!(packet[3] & 0xc0, 0x00); // scrambling bits cleared
112    }
113}