Skip to main content

dvb_ci/objects/
host_control.rs

1//! Host Control objects — ETSI EN 50221 §8.5.1, Tables 27-30 (PDF pp. 33-34).
2//!
3//! - `tune` (`9F 84 00`, Table 27) — retune to a different service.
4//! - `replace` (`9F 84 01`, Table 28) — temporarily replace one PID with another.
5//! - `clear_replace` (`9F 84 02`, Table 29) — undo Replace operations by reference.
6//! - `ask_release` (`9F 84 03`, Table 30) — header-only release request.
7
8use crate::error::{Error, Result};
9use crate::tag::{self, ApduTag};
10use crate::traits::ApduDef;
11use broadcast_common::{Parse, Serialize};
12
13/// `tune()` object (Table 27): retune to a different service. Parameters are the
14/// EN 300 468 identifiers, each 16 bits.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17pub struct Tune {
18    /// `network_id`.
19    pub network_id: u16,
20    /// `original_network_id`.
21    pub original_network_id: u16,
22    /// `transport_stream_id`.
23    pub transport_stream_id: u16,
24    /// `service_id`.
25    pub service_id: u16,
26}
27
28// network_id(2) + original_network_id(2) + transport_stream_id(2) + service_id(2).
29const TUNE_BODY: usize = 8;
30
31impl<'a> Parse<'a> for Tune {
32    type Error = Error;
33    fn parse(bytes: &'a [u8]) -> Result<Self> {
34        let body = super::parse_apdu_header(bytes, tag::TUNE, "tune")?;
35        if body.len() < TUNE_BODY {
36            return Err(Error::BufferTooShort {
37                need: TUNE_BODY,
38                have: body.len(),
39                what: "tune",
40            });
41        }
42        Ok(Self {
43            network_id: u16::from_be_bytes([body[0], body[1]]),
44            original_network_id: u16::from_be_bytes([body[2], body[3]]),
45            transport_stream_id: u16::from_be_bytes([body[4], body[5]]),
46            service_id: u16::from_be_bytes([body[6], body[7]]),
47        })
48    }
49}
50
51impl Serialize for Tune {
52    type Error = Error;
53    fn serialized_len(&self) -> usize {
54        super::apdu_len(TUNE_BODY)
55    }
56    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
57        let mut pos = super::write_apdu_header(tag::TUNE, TUNE_BODY, buf)?;
58        buf[pos..pos + 2].copy_from_slice(&self.network_id.to_be_bytes());
59        buf[pos + 2..pos + 4].copy_from_slice(&self.original_network_id.to_be_bytes());
60        buf[pos + 4..pos + 6].copy_from_slice(&self.transport_stream_id.to_be_bytes());
61        buf[pos + 6..pos + 8].copy_from_slice(&self.service_id.to_be_bytes());
62        pos += TUNE_BODY;
63        Ok(pos)
64    }
65}
66
67impl ApduDef<'_> for Tune {
68    const TAG: ApduTag = tag::TUNE;
69    const NAME: &'static str = "TUNE";
70}
71
72/// `replace()` object (Table 28): temporarily replace one component PID with
73/// another from the same multiplex.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize))]
76pub struct Replace {
77    /// `replacement_ref` — application-allocated reference matched by Clear Replace.
78    pub replacement_ref: u8,
79    /// 13-bit `replaced_PID` — PID of the component to replace.
80    pub replaced_pid: u16,
81    /// 13-bit `replacement_PID` — PID to replace it with.
82    pub replacement_pid: u16,
83}
84
85// replacement_ref(1) + reserved/replaced_PID(2) + reserved/replacement_PID(2).
86const REPLACE_BODY: usize = 5;
87/// Maximum value the 13-bit `replaced_PID`/`replacement_PID` fields can hold.
88const MAX_PID: u16 = 0x1FFF;
89
90impl<'a> Parse<'a> for Replace {
91    type Error = Error;
92    fn parse(bytes: &'a [u8]) -> Result<Self> {
93        let body = super::parse_apdu_header(bytes, tag::REPLACE, "replace")?;
94        if body.len() < REPLACE_BODY {
95            return Err(Error::BufferTooShort {
96                need: REPLACE_BODY,
97                have: body.len(),
98                what: "replace",
99            });
100        }
101        let replacement_ref = body[0];
102        let replaced_pid = (((body[1] & 0x1F) as u16) << 8) | body[2] as u16;
103        let replacement_pid = (((body[3] & 0x1F) as u16) << 8) | body[4] as u16;
104        Ok(Self {
105            replacement_ref,
106            replaced_pid,
107            replacement_pid,
108        })
109    }
110}
111
112impl Serialize for Replace {
113    type Error = Error;
114    fn serialized_len(&self) -> usize {
115        super::apdu_len(REPLACE_BODY)
116    }
117    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
118        if self.replaced_pid > MAX_PID || self.replacement_pid > MAX_PID {
119            return Err(Error::InvalidObject {
120                what: "replace",
121                reason: "PID exceeds 13-bit range (0x1FFF)",
122            });
123        }
124        let mut pos = super::write_apdu_header(tag::REPLACE, REPLACE_BODY, buf)?;
125        buf[pos] = self.replacement_ref;
126        // reserved(3)='111', replaced_PID(13).
127        buf[pos + 1] = 0xE0 | ((self.replaced_pid >> 8) as u8 & 0x1F);
128        buf[pos + 2] = self.replaced_pid as u8;
129        // reserved(3)='111', replacement_PID(13).
130        buf[pos + 3] = 0xE0 | ((self.replacement_pid >> 8) as u8 & 0x1F);
131        buf[pos + 4] = self.replacement_pid as u8;
132        pos += REPLACE_BODY;
133        Ok(pos)
134    }
135}
136
137impl ApduDef<'_> for Replace {
138    const TAG: ApduTag = tag::REPLACE;
139    const NAME: &'static str = "REPLACE";
140}
141
142/// `clear_replace()` object (Table 29): undo all Replace operations sharing a
143/// `replacement_ref`.
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize))]
146pub struct ClearReplace {
147    /// `replacement_ref` — matches the value used in one or more Replace objects.
148    pub replacement_ref: u8,
149}
150
151impl<'a> Parse<'a> for ClearReplace {
152    type Error = Error;
153    fn parse(bytes: &'a [u8]) -> Result<Self> {
154        let body = super::parse_apdu_header(bytes, tag::CLEAR_REPLACE, "clear_replace")?;
155        let replacement_ref = *body.first().ok_or(Error::BufferTooShort {
156            need: 1,
157            have: 0,
158            what: "clear_replace replacement_ref",
159        })?;
160        Ok(Self { replacement_ref })
161    }
162}
163
164impl Serialize for ClearReplace {
165    type Error = Error;
166    fn serialized_len(&self) -> usize {
167        super::apdu_len(1)
168    }
169    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
170        let mut pos = super::write_apdu_header(tag::CLEAR_REPLACE, 1, buf)?;
171        buf[pos] = self.replacement_ref;
172        pos += 1;
173        Ok(pos)
174    }
175}
176
177impl ApduDef<'_> for ClearReplace {
178    const TAG: ApduTag = tag::CLEAR_REPLACE;
179    const NAME: &'static str = "CLEAR_REPLACE";
180}
181
182/// `ask_release()` object (Table 30): header-only release request from the host.
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize))]
185pub struct AskRelease;
186
187impl<'a> Parse<'a> for AskRelease {
188    type Error = Error;
189    fn parse(bytes: &'a [u8]) -> Result<Self> {
190        super::parse_empty_apdu(bytes, tag::ASK_RELEASE, "ask_release")?;
191        Ok(Self)
192    }
193}
194
195impl Serialize for AskRelease {
196    type Error = Error;
197    fn serialized_len(&self) -> usize {
198        super::empty_apdu_len()
199    }
200    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
201        super::serialize_empty_apdu(tag::ASK_RELEASE, buf)
202    }
203}
204
205impl ApduDef<'_> for AskRelease {
206    const TAG: ApduTag = tag::ASK_RELEASE;
207    const NAME: &'static str = "ASK_RELEASE";
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn tune_round_trips_and_bites() {
216        let t = Tune {
217            network_id: 0x1122,
218            original_network_id: 0x3344,
219            transport_stream_id: 0x5566,
220            service_id: 0x7788,
221        };
222        let bytes = t.to_bytes();
223        assert_eq!(
224            bytes,
225            [
226                0x9F, 0x84, 0x00, 0x08, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
227            ]
228        );
229        assert_eq!(Tune::parse(&bytes).unwrap(), t);
230        let mut other = t;
231        other.service_id = 0x9999;
232        assert_ne!(bytes, other.to_bytes());
233    }
234
235    #[test]
236    fn replace_round_trips_and_bites() {
237        let r = Replace {
238            replacement_ref: 0x07,
239            replaced_pid: 0x0123,    // 13-bit
240            replacement_pid: 0x01FF, // 13-bit
241        };
242        let bytes = r.to_bytes();
243        // body: 07, reserved(111)+0x0123 => 0xE1 0x23, reserved+0x01FF => 0xE1 0xFF
244        assert_eq!(
245            bytes,
246            [0x9F, 0x84, 0x01, 0x05, 0x07, 0xE1, 0x23, 0xE1, 0xFF]
247        );
248        let parsed = Replace::parse(&bytes).unwrap();
249        assert_eq!(parsed, r);
250        let mut other = r;
251        other.replacement_pid = 0x0001;
252        assert_ne!(bytes, other.to_bytes());
253    }
254
255    #[test]
256    fn replace_oversized_pid_rejected_not_truncated() {
257        let r = Replace {
258            replacement_ref: 0x01,
259            replaced_pid: 0x2010, // 14-bit value, out of 13-bit range
260            replacement_pid: 0x0001,
261        };
262        let err = r.serialize_into(&mut [0u8; 32]).unwrap_err();
263        assert!(matches!(err, Error::InvalidObject { .. }));
264
265        let r2 = Replace {
266            replacement_ref: 0x01,
267            replaced_pid: 0x0001,
268            replacement_pid: 0x2010, // 14-bit value, out of 13-bit range
269        };
270        let err2 = r2.serialize_into(&mut [0u8; 32]).unwrap_err();
271        assert!(matches!(err2, Error::InvalidObject { .. }));
272    }
273
274    #[test]
275    fn clear_replace_round_trips_and_bites() {
276        let c = ClearReplace {
277            replacement_ref: 0x42,
278        };
279        let bytes = c.to_bytes();
280        assert_eq!(bytes, [0x9F, 0x84, 0x02, 0x01, 0x42]);
281        assert_eq!(ClearReplace::parse(&bytes).unwrap(), c);
282        let other = ClearReplace {
283            replacement_ref: 0x43,
284        };
285        assert_ne!(bytes, other.to_bytes());
286    }
287
288    #[test]
289    fn ask_release_round_trips() {
290        let bytes = AskRelease.to_bytes();
291        assert_eq!(bytes, [0x9F, 0x84, 0x03, 0x00]);
292        assert_eq!(AskRelease::parse(&bytes).unwrap(), AskRelease);
293    }
294}