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
88impl<'a> Parse<'a> for Replace {
89    type Error = Error;
90    fn parse(bytes: &'a [u8]) -> Result<Self> {
91        let body = super::parse_apdu_header(bytes, tag::REPLACE, "replace")?;
92        if body.len() < REPLACE_BODY {
93            return Err(Error::BufferTooShort {
94                need: REPLACE_BODY,
95                have: body.len(),
96                what: "replace",
97            });
98        }
99        let replacement_ref = body[0];
100        let replaced_pid = (((body[1] & 0x1F) as u16) << 8) | body[2] as u16;
101        let replacement_pid = (((body[3] & 0x1F) as u16) << 8) | body[4] as u16;
102        Ok(Self {
103            replacement_ref,
104            replaced_pid,
105            replacement_pid,
106        })
107    }
108}
109
110impl Serialize for Replace {
111    type Error = Error;
112    fn serialized_len(&self) -> usize {
113        super::apdu_len(REPLACE_BODY)
114    }
115    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
116        let mut pos = super::write_apdu_header(tag::REPLACE, REPLACE_BODY, buf)?;
117        buf[pos] = self.replacement_ref;
118        // reserved(3)='111', replaced_PID(13).
119        buf[pos + 1] = 0xE0 | ((self.replaced_pid >> 8) as u8 & 0x1F);
120        buf[pos + 2] = self.replaced_pid as u8;
121        // reserved(3)='111', replacement_PID(13).
122        buf[pos + 3] = 0xE0 | ((self.replacement_pid >> 8) as u8 & 0x1F);
123        buf[pos + 4] = self.replacement_pid as u8;
124        pos += REPLACE_BODY;
125        Ok(pos)
126    }
127}
128
129impl ApduDef<'_> for Replace {
130    const TAG: ApduTag = tag::REPLACE;
131    const NAME: &'static str = "REPLACE";
132}
133
134/// `clear_replace()` object (Table 29): undo all Replace operations sharing a
135/// `replacement_ref`.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize))]
138pub struct ClearReplace {
139    /// `replacement_ref` — matches the value used in one or more Replace objects.
140    pub replacement_ref: u8,
141}
142
143impl<'a> Parse<'a> for ClearReplace {
144    type Error = Error;
145    fn parse(bytes: &'a [u8]) -> Result<Self> {
146        let body = super::parse_apdu_header(bytes, tag::CLEAR_REPLACE, "clear_replace")?;
147        let replacement_ref = *body.first().ok_or(Error::BufferTooShort {
148            need: 1,
149            have: 0,
150            what: "clear_replace replacement_ref",
151        })?;
152        Ok(Self { replacement_ref })
153    }
154}
155
156impl Serialize for ClearReplace {
157    type Error = Error;
158    fn serialized_len(&self) -> usize {
159        super::apdu_len(1)
160    }
161    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
162        let mut pos = super::write_apdu_header(tag::CLEAR_REPLACE, 1, buf)?;
163        buf[pos] = self.replacement_ref;
164        pos += 1;
165        Ok(pos)
166    }
167}
168
169impl ApduDef<'_> for ClearReplace {
170    const TAG: ApduTag = tag::CLEAR_REPLACE;
171    const NAME: &'static str = "CLEAR_REPLACE";
172}
173
174/// `ask_release()` object (Table 30): header-only release request from the host.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
176#[cfg_attr(feature = "serde", derive(serde::Serialize))]
177pub struct AskRelease;
178
179impl<'a> Parse<'a> for AskRelease {
180    type Error = Error;
181    fn parse(bytes: &'a [u8]) -> Result<Self> {
182        super::parse_empty_apdu(bytes, tag::ASK_RELEASE, "ask_release")?;
183        Ok(Self)
184    }
185}
186
187impl Serialize for AskRelease {
188    type Error = Error;
189    fn serialized_len(&self) -> usize {
190        super::empty_apdu_len()
191    }
192    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
193        super::serialize_empty_apdu(tag::ASK_RELEASE, buf)
194    }
195}
196
197impl ApduDef<'_> for AskRelease {
198    const TAG: ApduTag = tag::ASK_RELEASE;
199    const NAME: &'static str = "ASK_RELEASE";
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn tune_round_trips_and_bites() {
208        let t = Tune {
209            network_id: 0x1122,
210            original_network_id: 0x3344,
211            transport_stream_id: 0x5566,
212            service_id: 0x7788,
213        };
214        let bytes = t.to_bytes();
215        assert_eq!(
216            bytes,
217            [
218                0x9F, 0x84, 0x00, 0x08, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
219            ]
220        );
221        assert_eq!(Tune::parse(&bytes).unwrap(), t);
222        let mut other = t;
223        other.service_id = 0x9999;
224        assert_ne!(bytes, other.to_bytes());
225    }
226
227    #[test]
228    fn replace_round_trips_and_bites() {
229        let r = Replace {
230            replacement_ref: 0x07,
231            replaced_pid: 0x0123,    // 13-bit
232            replacement_pid: 0x01FF, // 13-bit
233        };
234        let bytes = r.to_bytes();
235        // body: 07, reserved(111)+0x0123 => 0xE1 0x23, reserved+0x01FF => 0xE1 0xFF
236        assert_eq!(
237            bytes,
238            [0x9F, 0x84, 0x01, 0x05, 0x07, 0xE1, 0x23, 0xE1, 0xFF]
239        );
240        let parsed = Replace::parse(&bytes).unwrap();
241        assert_eq!(parsed, r);
242        let mut other = r;
243        other.replacement_pid = 0x0001;
244        assert_ne!(bytes, other.to_bytes());
245    }
246
247    #[test]
248    fn clear_replace_round_trips_and_bites() {
249        let c = ClearReplace {
250            replacement_ref: 0x42,
251        };
252        let bytes = c.to_bytes();
253        assert_eq!(bytes, [0x9F, 0x84, 0x02, 0x01, 0x42]);
254        assert_eq!(ClearReplace::parse(&bytes).unwrap(), c);
255        let other = ClearReplace {
256            replacement_ref: 0x43,
257        };
258        assert_ne!(bytes, other.to_bytes());
259    }
260
261    #[test]
262    fn ask_release_round_trips() {
263        let bytes = AskRelease.to_bytes();
264        assert_eq!(bytes, [0x9F, 0x84, 0x03, 0x00]);
265        assert_eq!(AskRelease::parse(&bytes).unwrap(), AskRelease);
266    }
267}