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 ETSI TS 100 289.
5use crate::csa;
6use crate::error::Error;
7use crate::key::ControlWord;
8
9/// Scramble the payload of a 188-byte TS packet in-place.
10///
11/// Skips the 4-byte header and any adaptation field bytes.
12/// Sets the transport_scrambling_control bits to `10` (even key).
13pub fn scramble_ts_packet(cw: &ControlWord, packet: &mut [u8; 188]) -> Result<(), Error> {
14    let payload = ts_payload_mut(packet)?;
15    csa::scramble(cw, payload);
16    // Set transport_scrambling_control to 10 (scrambled, even key)
17    packet[3] = (packet[3] & 0x3f) | 0x80;
18    Ok(())
19}
20
21/// Descramble the payload of a 188-byte TS packet in-place.
22///
23/// Skips the 4-byte header and any adaptation field bytes.
24/// Clears the transport_scrambling_control bits to `00`.
25pub fn descramble_ts_packet(cw: &ControlWord, packet: &mut [u8; 188]) -> Result<(), Error> {
26    let payload = ts_payload_mut(packet)?;
27    csa::descramble(cw, payload);
28    // Clear transport_scrambling_control
29    packet[3] &= 0x3f;
30    Ok(())
31}
32
33/// Get a mutable slice to the TS packet payload, skipping the header and
34/// adaptation field.
35fn ts_payload_mut(packet: &mut [u8; 188]) -> Result<&mut [u8], Error> {
36    let adaptation_field_control = (packet[3] >> 4) & 0x03;
37    let has_adaptation = adaptation_field_control & 0x02 != 0;
38    let has_payload = adaptation_field_control & 0x01 != 0;
39
40    if !has_payload {
41        return Err(Error::BufferTooShort { need: 1, have: 0 });
42    }
43
44    let mut payload_start = 4; // after TS header
45
46    if has_adaptation {
47        let af_len = packet[4] as usize;
48        payload_start += 1 + af_len; // adaptation_field_length byte + field
49    }
50
51    if payload_start >= 188 {
52        return Err(Error::BufferTooShort { need: 1, have: 0 });
53    }
54
55    Ok(&mut packet[payload_start..188])
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn roundtrip_ts_packet() {
64        let cw = ControlWord::from_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
65        // Build a minimal TS packet: header 0x47, PID 0x0100, no adaptation, payload
66        let mut packet = [0u8; 188];
67        packet[0] = 0x47; // sync byte
68        packet[1] = 0x41; // TEI=0, PUSI=1, priority=0, PID high=0x0100>>8
69        packet[2] = 0x00; // PID low=0x00
70        packet[3] = 0x10; // no scrambling, adaptation_field_control=01 (payload only), CC=0
71        // Fill payload with non-zero data (need at least 8 bytes)
72        for i in 0..184 {
73            packet[4 + i] = (i % 256) as u8;
74        }
75
76        let original = packet;
77        scramble_ts_packet(&cw, &mut packet).unwrap();
78        assert_ne!(packet[4..], original[4..]);
79        assert_eq!(packet[3] & 0xc0, 0x80); // scrambling bits set
80
81        descramble_ts_packet(&cw, &mut packet).unwrap();
82        // Compare payload only (skip header)
83        assert_eq!(packet[4..], original[4..]);
84        assert_eq!(packet[3] & 0xc0, 0x00); // scrambling bits cleared
85    }
86}