Skip to main content

dvb_ci/ci_ext/
status_query.rs

1//! Status Query objects + audience-metering item structures — ETSI TS 101 699
2//! V1.1.1 §6.2, Tables 35-51 (PDF pp. 42-50). See `docs/ci_plus/status-query.md`.
3//!
4//! Resource ID `0x00211ii1` (`ii` = Module ID, `type = 1*`). The module
5//! interrogates the host's status items (Table 36): Selection Information (1),
6//! Port Profile (2), Viewed Service (3), Activation Status (4).
7//!
8//! APDU objects (Tables 37-41):
9//! - `StatusQueryReq` (`9F 80 00`) — module → host: a 32-bit `StatusItem`.
10//! - `TrapReq` (`9F 80 01`) — module → host: a 32-bit `StatusItem` to monitor.
11//! - `GetNextItemReq` (`9F 80 02`) — module → host: a 32-bit `StartStatusItem`.
12//! - `GetNextItemAck` (`9F 80 03`) — host → module: a 32-bit `NextStatusItem`.
13//! - `StatusAck` (`9F 80 04`) — host → module: the `StatusItem` + its
14//!   `StatusBytes` (format depends on the item — Tables 43/48/49/50).
15//!
16//! The `StatusBytes` of a [`StatusAck`] are carried as an opaque `&[u8]`; the
17//! audience-metering content structures ([`SelectionInformation`],
18//! [`PortProfile`], [`ViewedService`], [`ActivationStatus`]) implement
19//! `Parse`/`Serialize` over **just those bytes** (no `apdu_tag`) so a caller can
20//! decode them once the `StatusItem` identifies the format.
21
22use crate::error::{Error, Result};
23use crate::objects;
24use crate::tag::ApduTag;
25use alloc::vec::Vec;
26use broadcast_common::{Parse, Serialize};
27
28/// Resource-scoped `apdu_tag`s for Status Query (Tables 37-41).
29pub mod tag {
30    use crate::tag::ApduTag;
31    /// `StatusQueryReqTag` = `9F 80 00`.
32    pub const STATUS_QUERY_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
33    /// `TrapReqTag` = `9F 80 01`.
34    pub const TRAP_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
35    /// `GetNextItemReqTag` = `9F 80 02`.
36    pub const GET_NEXT_ITEM_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
37    /// `GetNextItemAckTag` = `9F 80 03`.
38    pub const GET_NEXT_ITEM_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
39    /// `StatusAckTag` = `9F 80 04`.
40    pub const STATUS_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
41}
42
43/// Width of the `StatusItem` / `StartStatusItem` / `NextStatusItem` field (32 bits).
44const STATUS_ITEM_LEN: usize = 4;
45
46fn read_u32(b: &[u8]) -> u32 {
47    u32::from_be_bytes([b[0], b[1], b[2], b[3]])
48}
49
50/// `StatusItem` (Table 36) — the host status item a query refers to.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize))]
53#[non_exhaustive]
54pub enum StatusItem {
55    /// `1` — Selection Information (Audience Metering; Table 43).
56    SelectionInformation,
57    /// `2` — Port Profile (Audience Metering; Table 48).
58    PortProfile,
59    /// `3` — Viewed Service (Table 49).
60    ViewedService,
61    /// `4` — Activation Status (Table 50).
62    ActivationStatus,
63    /// `0` and `> 4` — reserved.
64    Reserved(u32),
65}
66
67impl StatusItem {
68    /// Decode a `StatusItem` value.
69    #[must_use]
70    pub fn from_u32(v: u32) -> Self {
71        match v {
72            1 => Self::SelectionInformation,
73            2 => Self::PortProfile,
74            3 => Self::ViewedService,
75            4 => Self::ActivationStatus,
76            other => Self::Reserved(other),
77        }
78    }
79    /// Wire value.
80    #[must_use]
81    pub const fn to_u32(self) -> u32 {
82        match self {
83            Self::SelectionInformation => 1,
84            Self::PortProfile => 2,
85            Self::ViewedService => 3,
86            Self::ActivationStatus => 4,
87            Self::Reserved(v) => v,
88        }
89    }
90    /// Spec token, or `"reserved"`.
91    #[must_use]
92    pub fn name(&self) -> &'static str {
93        match self {
94            Self::SelectionInformation => "Selection Information",
95            Self::PortProfile => "Port Profile",
96            Self::ViewedService => "Viewed Service",
97            Self::ActivationStatus => "Activation Status",
98            Self::Reserved(_) => "reserved",
99        }
100    }
101}
102broadcast_common::impl_spec_display!(StatusItem, Reserved);
103
104// =================== APDU objects ===================
105
106/// `StatusQueryReq()` (Table 37): module → host.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
109pub struct StatusQueryReq {
110    /// `StatusItem` queried.
111    pub status_item: StatusItem,
112}
113
114/// `TrapReq()` (Table 38): module → host.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize))]
117pub struct TrapReq {
118    /// `StatusItem` to monitor for changes.
119    pub status_item: StatusItem,
120}
121
122/// `GetNextItemReq()` (Table 39): module → host.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124#[cfg_attr(feature = "serde", derive(serde::Serialize))]
125pub struct GetNextItemReq {
126    /// `StartStatusItem` — search start point (raw 32-bit; not necessarily a
127    /// supported item).
128    pub start_status_item: u32,
129}
130
131/// `GetNextItemAck()` (Table 40): host → module.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize))]
134pub struct GetNextItemAck {
135    /// `NextStatusItem` — first supported item greater than `StartStatusItem`
136    /// (raw 32-bit; `0` = no higher item).
137    pub next_status_item: u32,
138}
139
140/// `StatusAck()` (Table 41): host → module. The `StatusBytes` are an opaque
141/// byte string whose format depends on `status_item` (Tables 43/48/49/50); an
142/// empty `status_bytes` means the host does not support the item.
143#[derive(Debug, Clone, PartialEq, Eq)]
144#[cfg_attr(feature = "serde", derive(serde::Serialize))]
145pub struct StatusAck<'a> {
146    /// `StatusItem` this reply answers.
147    pub status_item: StatusItem,
148    /// `StatusBytes` — item-dependent payload.
149    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
150    pub status_bytes: &'a [u8],
151}
152
153macro_rules! status_item_object {
154    ($ty:ident, $tag:expr, $what:literal, $field:ident, $decode:expr, $encode:expr) => {
155        impl<'a> Parse<'a> for $ty {
156            type Error = Error;
157            fn parse(bytes: &'a [u8]) -> Result<Self> {
158                let body = objects::parse_apdu_header(bytes, $tag, $what)?;
159                if body.len() < STATUS_ITEM_LEN {
160                    return Err(Error::BufferTooShort {
161                        need: STATUS_ITEM_LEN,
162                        have: body.len(),
163                        what: $what,
164                    });
165                }
166                let raw = read_u32(body);
167                Ok(Self {
168                    $field: $decode(raw),
169                })
170            }
171        }
172        impl Serialize for $ty {
173            type Error = Error;
174            fn serialized_len(&self) -> usize {
175                objects::apdu_len(STATUS_ITEM_LEN)
176            }
177            fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
178                let pos = objects::write_apdu_header($tag, STATUS_ITEM_LEN, buf)?;
179                let raw: u32 = $encode(self.$field);
180                buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&raw.to_be_bytes());
181                Ok(pos + STATUS_ITEM_LEN)
182            }
183        }
184    };
185}
186
187status_item_object!(
188    StatusQueryReq,
189    tag::STATUS_QUERY_REQ,
190    "StatusQueryReq",
191    status_item,
192    StatusItem::from_u32,
193    StatusItem::to_u32
194);
195status_item_object!(
196    TrapReq,
197    tag::TRAP_REQ,
198    "TrapReq",
199    status_item,
200    StatusItem::from_u32,
201    StatusItem::to_u32
202);
203status_item_object!(
204    GetNextItemReq,
205    tag::GET_NEXT_ITEM_REQ,
206    "GetNextItemReq",
207    start_status_item,
208    core::convert::identity,
209    core::convert::identity
210);
211status_item_object!(
212    GetNextItemAck,
213    tag::GET_NEXT_ITEM_ACK,
214    "GetNextItemAck",
215    next_status_item,
216    core::convert::identity,
217    core::convert::identity
218);
219
220impl<'a> Parse<'a> for StatusAck<'a> {
221    type Error = Error;
222    fn parse(bytes: &'a [u8]) -> Result<Self> {
223        let body = objects::parse_apdu_header(bytes, tag::STATUS_ACK, "StatusAck")?;
224        if body.len() < STATUS_ITEM_LEN {
225            return Err(Error::BufferTooShort {
226                need: STATUS_ITEM_LEN,
227                have: body.len(),
228                what: "StatusAck",
229            });
230        }
231        Ok(Self {
232            status_item: StatusItem::from_u32(read_u32(body)),
233            status_bytes: &body[STATUS_ITEM_LEN..],
234        })
235    }
236}
237impl Serialize for StatusAck<'_> {
238    type Error = Error;
239    fn serialized_len(&self) -> usize {
240        objects::apdu_len(STATUS_ITEM_LEN + self.status_bytes.len())
241    }
242    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
243        let body_len = STATUS_ITEM_LEN + self.status_bytes.len();
244        let mut pos = objects::write_apdu_header(tag::STATUS_ACK, body_len, buf)?;
245        buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&self.status_item.to_u32().to_be_bytes());
246        pos += STATUS_ITEM_LEN;
247        buf[pos..pos + self.status_bytes.len()].copy_from_slice(self.status_bytes);
248        Ok(pos + self.status_bytes.len())
249    }
250}
251
252// =================== Audience-metering item content structures ===================
253//
254// These decode the `StatusBytes` of a StatusAck for items 1-4. They have no
255// apdu_tag/length_field of their own (the StatusAck supplies length); their
256// Parse consumes the whole input slice and Serialize emits exactly the wire
257// bytes, so a round-trip is byte-exact.
258
259/// Width of the `time` field of Selection Information (40 bits = 5 bytes, UTC
260/// time coded as the DVB SI Time and Date Table).
261pub const SELECTION_TIME_LEN: usize = 5;
262
263/// An output entry within a Selection Information [`InPortDescription`] (Table 43).
264#[derive(Debug, Clone, PartialEq, Eq, Default)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize))]
266pub struct OutputSignal<'a> {
267    /// `out_port_id` (Table 46).
268    pub out_port_id: u8,
269    /// `out_signal_desc` — block describing the output signal (opaque; format
270    /// depends on the output port — Table 47).
271    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
272    pub out_signal_desc: &'a [u8],
273}
274
275/// One `in_port_id` block of Selection Information (Table 43): an input signal
276/// source and the list of outputs it is routed to.
277#[derive(Debug, Clone, PartialEq, Eq, Default)]
278#[cfg_attr(feature = "serde", derive(serde::Serialize))]
279pub struct InPortDescription<'a> {
280    /// `in_port_id` (Table 44).
281    pub in_port_id: u8,
282    /// `in_signal_desc` — block describing the input signal (opaque; format
283    /// depends on the input port — Table 45).
284    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
285    pub in_signal_desc: &'a [u8],
286    /// The output signals this input is routed to (`length_outputs` bytes total).
287    pub outputs: Vec<OutputSignal<'a>>,
288}
289
290/// `Selection information status data` (Table 43) — status item 1: a timestamp
291/// and a list of input-port → output-port routing descriptions.
292#[derive(Debug, Clone, PartialEq, Eq, Default)]
293#[cfg_attr(feature = "serde", derive(serde::Serialize))]
294pub struct SelectionInformation<'a> {
295    /// `time` — 5-byte UTC time (DVB SI TDT coding).
296    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
297    pub time: &'a [u8],
298    /// One entry per input port present in the object.
299    pub ports: Vec<InPortDescription<'a>>,
300}
301
302impl<'a> Parse<'a> for SelectionInformation<'a> {
303    type Error = Error;
304    fn parse(body: &'a [u8]) -> Result<Self> {
305        if body.len() < SELECTION_TIME_LEN {
306            return Err(Error::BufferTooShort {
307                need: SELECTION_TIME_LEN,
308                have: body.len(),
309                what: "SelectionInformation.time",
310            });
311        }
312        let time = &body[..SELECTION_TIME_LEN];
313        let mut rest = &body[SELECTION_TIME_LEN..];
314        let mut ports = Vec::new();
315        while !rest.is_empty() {
316            // in_port_id(1) + length_in_signal_desc(1).
317            if rest.len() < 2 {
318                return Err(Error::BufferTooShort {
319                    need: 2,
320                    have: rest.len(),
321                    what: "SelectionInformation.in_port",
322                });
323            }
324            let in_port_id = rest[0];
325            let in_len = rest[1] as usize;
326            let after_in = 2 + in_len;
327            if rest.len() < after_in + 2 {
328                return Err(Error::BufferTooShort {
329                    need: after_in + 2,
330                    have: rest.len(),
331                    what: "SelectionInformation.in_signal_desc",
332                });
333            }
334            let in_signal_desc = &rest[2..after_in];
335            // reserved(4) + length_outputs(12) packed into 2 bytes.
336            let length_outputs =
337                (((rest[after_in] & 0x0F) as usize) << 8) | rest[after_in + 1] as usize;
338            let outputs_start = after_in + 2;
339            let outputs_end = outputs_start + length_outputs;
340            if rest.len() < outputs_end {
341                return Err(Error::BufferTooShort {
342                    need: outputs_end,
343                    have: rest.len(),
344                    what: "SelectionInformation.outputs",
345                });
346            }
347            let mut out_rest = &rest[outputs_start..outputs_end];
348            let mut outputs = Vec::new();
349            while !out_rest.is_empty() {
350                // out_port_id(1) + length_out_signal_desc(1).
351                if out_rest.len() < 2 {
352                    return Err(Error::BufferTooShort {
353                        need: 2,
354                        have: out_rest.len(),
355                        what: "SelectionInformation.out_port",
356                    });
357                }
358                let out_port_id = out_rest[0];
359                let out_len = out_rest[1] as usize;
360                let out_end = 2 + out_len;
361                if out_rest.len() < out_end {
362                    return Err(Error::BufferTooShort {
363                        need: out_end,
364                        have: out_rest.len(),
365                        what: "SelectionInformation.out_signal_desc",
366                    });
367                }
368                outputs.push(OutputSignal {
369                    out_port_id,
370                    out_signal_desc: &out_rest[2..out_end],
371                });
372                out_rest = &out_rest[out_end..];
373            }
374            ports.push(InPortDescription {
375                in_port_id,
376                in_signal_desc,
377                outputs,
378            });
379            rest = &rest[outputs_end..];
380        }
381        Ok(Self { time, ports })
382    }
383}
384impl Serialize for SelectionInformation<'_> {
385    type Error = Error;
386    fn serialized_len(&self) -> usize {
387        let mut n = SELECTION_TIME_LEN;
388        for p in &self.ports {
389            n += 2 + p.in_signal_desc.len() + 2;
390            for o in &p.outputs {
391                n += 2 + o.out_signal_desc.len();
392            }
393        }
394        n
395    }
396    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
397        let total = self.serialized_len();
398        if buf.len() < total {
399            return Err(Error::OutputBufferTooSmall {
400                need: total,
401                have: buf.len(),
402            });
403        }
404        if self.time.len() != SELECTION_TIME_LEN {
405            return Err(Error::InvalidObject {
406                what: "SelectionInformation",
407                reason: "time must be exactly 5 bytes",
408            });
409        }
410        let mut pos = 0;
411        buf[pos..pos + SELECTION_TIME_LEN].copy_from_slice(self.time);
412        pos += SELECTION_TIME_LEN;
413        for p in &self.ports {
414            if p.in_signal_desc.len() > u8::MAX as usize {
415                return Err(Error::InvalidObject {
416                    what: "SelectionInformation",
417                    reason: "in_signal_desc longer than 255 bytes",
418                });
419            }
420            buf[pos] = p.in_port_id;
421            buf[pos + 1] = p.in_signal_desc.len() as u8;
422            pos += 2;
423            buf[pos..pos + p.in_signal_desc.len()].copy_from_slice(p.in_signal_desc);
424            pos += p.in_signal_desc.len();
425            // length_outputs = total bytes of the output entries.
426            let mut length_outputs = 0usize;
427            for o in &p.outputs {
428                length_outputs += 2 + o.out_signal_desc.len();
429            }
430            if length_outputs > 0x0FFF {
431                return Err(Error::InvalidObject {
432                    what: "SelectionInformation",
433                    reason: "length_outputs exceeds 12-bit maximum",
434                });
435            }
436            // reserved(4) = 0, length_outputs(12).
437            buf[pos] = ((length_outputs >> 8) & 0x0F) as u8;
438            buf[pos + 1] = (length_outputs & 0xFF) as u8;
439            pos += 2;
440            for o in &p.outputs {
441                if o.out_signal_desc.len() > u8::MAX as usize {
442                    return Err(Error::InvalidObject {
443                        what: "SelectionInformation",
444                        reason: "out_signal_desc longer than 255 bytes",
445                    });
446                }
447                buf[pos] = o.out_port_id;
448                buf[pos + 1] = o.out_signal_desc.len() as u8;
449                pos += 2;
450                buf[pos..pos + o.out_signal_desc.len()].copy_from_slice(o.out_signal_desc);
451                pos += o.out_signal_desc.len();
452            }
453        }
454        Ok(pos)
455    }
456}
457
458/// One input or output port description within a [`PortProfile`] (Table 48).
459#[derive(Debug, Clone, PartialEq, Eq, Default)]
460#[cfg_attr(feature = "serde", derive(serde::Serialize))]
461pub struct PortDescription<'a> {
462    /// `in_port_id` (Table 44).
463    pub in_port_id: u8,
464    /// `in_port_desc` — string describing the input port (DVB SI Annex A coding).
465    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
466    pub in_port_desc: &'a [u8],
467    /// `out_port_id` (Table 46).
468    pub out_port_id: u8,
469    /// `out_signal_desc` — string describing the output port (DVB SI Annex A).
470    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
471    pub out_port_desc: &'a [u8],
472}
473
474/// `Port profile status data` (Table 48) — status item 2: a textual receiver
475/// identity and per-port descriptions.
476#[derive(Debug, Clone, PartialEq, Eq, Default)]
477#[cfg_attr(feature = "serde", derive(serde::Serialize))]
478pub struct PortProfile<'a> {
479    /// `receiver_identification` — manufacturer/model/version string (DVB SI
480    /// Annex A coding).
481    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
482    pub receiver_identification: &'a [u8],
483    /// One entry per `{in,out}` port pair (`N` entries, consuming the remainder).
484    pub ports: Vec<PortDescription<'a>>,
485}
486
487impl<'a> Parse<'a> for PortProfile<'a> {
488    type Error = Error;
489    fn parse(body: &'a [u8]) -> Result<Self> {
490        if body.is_empty() {
491            return Err(Error::BufferTooShort {
492                need: 1,
493                have: 0,
494                what: "PortProfile.receiver_identification_length",
495            });
496        }
497        let id_len = body[0] as usize;
498        if body.len() < 1 + id_len {
499            return Err(Error::BufferTooShort {
500                need: 1 + id_len,
501                have: body.len(),
502                what: "PortProfile.receiver_identification",
503            });
504        }
505        let receiver_identification = &body[1..1 + id_len];
506        let mut rest = &body[1 + id_len..];
507        let mut ports = Vec::new();
508        while !rest.is_empty() {
509            // in_port_id(1) + length_in_port_desc(1).
510            if rest.len() < 2 {
511                return Err(Error::BufferTooShort {
512                    need: 2,
513                    have: rest.len(),
514                    what: "PortProfile.in_port",
515                });
516            }
517            let in_port_id = rest[0];
518            let in_len = rest[1] as usize;
519            let after_in = 2 + in_len;
520            // + out_port_id(1) + length_out_port_desc(1).
521            if rest.len() < after_in + 2 {
522                return Err(Error::BufferTooShort {
523                    need: after_in + 2,
524                    have: rest.len(),
525                    what: "PortProfile.out_port",
526                });
527            }
528            let in_port_desc = &rest[2..after_in];
529            let out_port_id = rest[after_in];
530            let out_len = rest[after_in + 1] as usize;
531            let after_out = after_in + 2 + out_len;
532            if rest.len() < after_out {
533                return Err(Error::BufferTooShort {
534                    need: after_out,
535                    have: rest.len(),
536                    what: "PortProfile.out_port_desc",
537                });
538            }
539            ports.push(PortDescription {
540                in_port_id,
541                in_port_desc,
542                out_port_id,
543                out_port_desc: &rest[after_in + 2..after_out],
544            });
545            rest = &rest[after_out..];
546        }
547        Ok(Self {
548            receiver_identification,
549            ports,
550        })
551    }
552}
553impl Serialize for PortProfile<'_> {
554    type Error = Error;
555    fn serialized_len(&self) -> usize {
556        let mut n = 1 + self.receiver_identification.len();
557        for p in &self.ports {
558            n += 2 + p.in_port_desc.len() + 2 + p.out_port_desc.len();
559        }
560        n
561    }
562    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
563        let total = self.serialized_len();
564        if buf.len() < total {
565            return Err(Error::OutputBufferTooSmall {
566                need: total,
567                have: buf.len(),
568            });
569        }
570        if self.receiver_identification.len() > u8::MAX as usize {
571            return Err(Error::InvalidObject {
572                what: "PortProfile",
573                reason: "receiver_identification longer than 255 bytes",
574            });
575        }
576        let mut pos = 0;
577        buf[pos] = self.receiver_identification.len() as u8;
578        pos += 1;
579        buf[pos..pos + self.receiver_identification.len()]
580            .copy_from_slice(self.receiver_identification);
581        pos += self.receiver_identification.len();
582        for p in &self.ports {
583            if p.in_port_desc.len() > u8::MAX as usize || p.out_port_desc.len() > u8::MAX as usize {
584                return Err(Error::InvalidObject {
585                    what: "PortProfile",
586                    reason: "port description longer than 255 bytes",
587                });
588            }
589            buf[pos] = p.in_port_id;
590            buf[pos + 1] = p.in_port_desc.len() as u8;
591            pos += 2;
592            buf[pos..pos + p.in_port_desc.len()].copy_from_slice(p.in_port_desc);
593            pos += p.in_port_desc.len();
594            buf[pos] = p.out_port_id;
595            buf[pos + 1] = p.out_port_desc.len() as u8;
596            pos += 2;
597            buf[pos..pos + p.out_port_desc.len()].copy_from_slice(p.out_port_desc);
598            pos += p.out_port_desc.len();
599        }
600        Ok(pos)
601    }
602}
603
604/// `Viewed service status data` (Table 49) — status item 3.
605#[derive(Debug, Clone, PartialEq, Eq, Default)]
606#[cfg_attr(feature = "serde", derive(serde::Serialize))]
607pub struct ViewedService {
608    /// `service_id` / program_number currently selected (`0x0000` = not in the TS).
609    pub service_id: u16,
610    /// `component_tag`s of the components currently selected for decoding.
611    pub component_tags: Vec<u8>,
612}
613
614impl<'a> Parse<'a> for ViewedService {
615    type Error = Error;
616    fn parse(body: &'a [u8]) -> Result<Self> {
617        // service_id(2) + number_components(1).
618        if body.len() < 3 {
619            return Err(Error::BufferTooShort {
620                need: 3,
621                have: body.len(),
622                what: "ViewedService",
623            });
624        }
625        let service_id = u16::from_be_bytes([body[0], body[1]]);
626        let n = body[2] as usize;
627        if body.len() < 3 + n {
628            return Err(Error::BufferTooShort {
629                need: 3 + n,
630                have: body.len(),
631                what: "ViewedService.component_tags",
632            });
633        }
634        Ok(Self {
635            service_id,
636            component_tags: body[3..3 + n].to_vec(),
637        })
638    }
639}
640impl Serialize for ViewedService {
641    type Error = Error;
642    fn serialized_len(&self) -> usize {
643        3 + self.component_tags.len()
644    }
645    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
646        let total = self.serialized_len();
647        if buf.len() < total {
648            return Err(Error::OutputBufferTooSmall {
649                need: total,
650                have: buf.len(),
651            });
652        }
653        if self.component_tags.len() > u8::MAX as usize {
654            return Err(Error::InvalidObject {
655                what: "ViewedService",
656                reason: "more than 255 component tags",
657            });
658        }
659        buf[0..2].copy_from_slice(&self.service_id.to_be_bytes());
660        buf[2] = self.component_tags.len() as u8;
661        buf[3..3 + self.component_tags.len()].copy_from_slice(&self.component_tags);
662        Ok(total)
663    }
664}
665
666/// `activation_state` (Table 51) — host power mode.
667#[derive(Debug, Clone, Copy, PartialEq, Eq)]
668#[cfg_attr(feature = "serde", derive(serde::Serialize))]
669#[non_exhaustive]
670pub enum ActivationState {
671    /// `1` — Standby-active.
672    StandbyActive,
673    /// `2` — On.
674    On,
675    /// `0` and `3`-`7` — reserved.
676    Reserved(u8),
677}
678
679impl ActivationState {
680    /// Decode the 3-bit `activation_state` value.
681    #[must_use]
682    pub fn from_u8(v: u8) -> Self {
683        match v {
684            1 => Self::StandbyActive,
685            2 => Self::On,
686            other => Self::Reserved(other),
687        }
688    }
689    /// 3-bit wire value.
690    #[must_use]
691    pub const fn to_u8(self) -> u8 {
692        match self {
693            Self::StandbyActive => 1,
694            Self::On => 2,
695            Self::Reserved(v) => v & 0x07,
696        }
697    }
698    /// Spec token, or `"reserved"`.
699    #[must_use]
700    pub fn name(&self) -> &'static str {
701        match self {
702            Self::StandbyActive => "Standby-active",
703            Self::On => "On",
704            Self::Reserved(_) => "reserved",
705        }
706    }
707}
708broadcast_common::impl_spec_display!(ActivationState, Reserved);
709
710/// `Activation status data` (Table 50) — status item 4: a single packed byte.
711#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
712#[cfg_attr(feature = "serde", derive(serde::Serialize))]
713pub struct ActivationStatus {
714    /// `event_activated` — host activated by an event (vs user action).
715    pub event_activated: bool,
716    /// `activation_state` (3 bits).
717    pub activation_state: Option<ActivationState>,
718}
719
720impl ActivationStatus {
721    const EVENT_ACTIVATED_BIT: u8 = 0x08;
722    const ACTIVATION_STATE_MASK: u8 = 0x07;
723}
724
725impl<'a> Parse<'a> for ActivationStatus {
726    type Error = Error;
727    fn parse(body: &'a [u8]) -> Result<Self> {
728        if body.is_empty() {
729            return Err(Error::BufferTooShort {
730                need: 1,
731                have: 0,
732                what: "ActivationStatus",
733            });
734        }
735        Ok(Self {
736            event_activated: body[0] & Self::EVENT_ACTIVATED_BIT != 0,
737            activation_state: Some(ActivationState::from_u8(
738                body[0] & Self::ACTIVATION_STATE_MASK,
739            )),
740        })
741    }
742}
743impl Serialize for ActivationStatus {
744    type Error = Error;
745    fn serialized_len(&self) -> usize {
746        1
747    }
748    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
749        if buf.is_empty() {
750            return Err(Error::OutputBufferTooSmall { need: 1, have: 0 });
751        }
752        let mut b = 0u8;
753        if self.event_activated {
754            b |= Self::EVENT_ACTIVATED_BIT;
755        }
756        if let Some(state) = self.activation_state {
757            b |= state.to_u8() & Self::ACTIVATION_STATE_MASK;
758        }
759        buf[0] = b;
760        Ok(1)
761    }
762}
763
764/// Resource-scoped dispatch over the Status Query objects (Tables 37-41).
765#[derive(Debug, Clone, PartialEq, Eq)]
766#[cfg_attr(feature = "serde", derive(serde::Serialize))]
767#[non_exhaustive]
768pub enum StatusQueryApdu<'a> {
769    /// `StatusQueryReq` (`9F 80 00`).
770    StatusQueryReq(StatusQueryReq),
771    /// `TrapReq` (`9F 80 01`).
772    TrapReq(TrapReq),
773    /// `GetNextItemReq` (`9F 80 02`).
774    GetNextItemReq(GetNextItemReq),
775    /// `GetNextItemAck` (`9F 80 03`).
776    GetNextItemAck(GetNextItemAck),
777    /// `StatusAck` (`9F 80 04`).
778    StatusAck(StatusAck<'a>),
779}
780
781impl<'a> StatusQueryApdu<'a> {
782    /// Parse a Status Query APDU, dispatching on the leading `apdu_tag`.
783    pub fn parse(body: &'a [u8]) -> Result<Self> {
784        if body.len() < 3 {
785            return Err(Error::BufferTooShort {
786                need: 3,
787                have: body.len(),
788                what: "status_query apdu_tag",
789            });
790        }
791        let t = ApduTag::from_bytes(body[0], body[1], body[2]);
792        match t {
793            tag::STATUS_QUERY_REQ => Ok(Self::StatusQueryReq(StatusQueryReq::parse(body)?)),
794            tag::TRAP_REQ => Ok(Self::TrapReq(TrapReq::parse(body)?)),
795            tag::GET_NEXT_ITEM_REQ => Ok(Self::GetNextItemReq(GetNextItemReq::parse(body)?)),
796            tag::GET_NEXT_ITEM_ACK => Ok(Self::GetNextItemAck(GetNextItemAck::parse(body)?)),
797            tag::STATUS_ACK => Ok(Self::StatusAck(StatusAck::parse(body)?)),
798            _ => Err(Error::UnexpectedApduTag {
799                got: t.as_u24(),
800                expected: tag::STATUS_QUERY_REQ.as_u24(),
801                what: "status_query",
802            }),
803        }
804    }
805}
806
807impl Serialize for StatusQueryApdu<'_> {
808    type Error = Error;
809    fn serialized_len(&self) -> usize {
810        match self {
811            Self::StatusQueryReq(o) => o.serialized_len(),
812            Self::TrapReq(o) => o.serialized_len(),
813            Self::GetNextItemReq(o) => o.serialized_len(),
814            Self::GetNextItemAck(o) => o.serialized_len(),
815            Self::StatusAck(o) => o.serialized_len(),
816        }
817    }
818    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
819        match self {
820            Self::StatusQueryReq(o) => o.serialize_into(buf),
821            Self::TrapReq(o) => o.serialize_into(buf),
822            Self::GetNextItemReq(o) => o.serialize_into(buf),
823            Self::GetNextItemAck(o) => o.serialize_into(buf),
824            Self::StatusAck(o) => o.serialize_into(buf),
825        }
826    }
827}
828
829#[cfg(test)]
830mod tests {
831    use super::*;
832
833    #[test]
834    fn status_query_req_round_trips_and_bites() {
835        let req = StatusQueryReq {
836            status_item: StatusItem::PortProfile,
837        };
838        let bytes = req.to_bytes();
839        // tag(3) + len(1=4) + StatusItem(4 = 0x00000002).
840        assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02]);
841        assert_eq!(StatusQueryReq::parse(&bytes).unwrap(), req);
842        let other = StatusQueryReq {
843            status_item: StatusItem::ViewedService,
844        };
845        assert_ne!(bytes, other.to_bytes());
846    }
847
848    #[test]
849    fn trap_and_getnext_round_trip() {
850        let trap = TrapReq {
851            status_item: StatusItem::ActivationStatus,
852        };
853        assert_eq!(trap.to_bytes(), [0x9F, 0x80, 0x01, 0x04, 0, 0, 0, 4]);
854        assert_eq!(TrapReq::parse(&trap.to_bytes()).unwrap(), trap);
855
856        let gnr = GetNextItemReq {
857            start_status_item: 0x0000_0007,
858        };
859        assert_eq!(gnr.to_bytes(), [0x9F, 0x80, 0x02, 0x04, 0, 0, 0, 7]);
860        assert_eq!(GetNextItemReq::parse(&gnr.to_bytes()).unwrap(), gnr);
861
862        let gna = GetNextItemAck {
863            next_status_item: 0x0000_0002,
864        };
865        assert_eq!(gna.to_bytes(), [0x9F, 0x80, 0x03, 0x04, 0, 0, 0, 2]);
866        assert_eq!(GetNextItemAck::parse(&gna.to_bytes()).unwrap(), gna);
867    }
868
869    #[test]
870    fn status_item_reserved_round_trips() {
871        let req = StatusQueryReq {
872            status_item: StatusItem::from_u32(0),
873        };
874        assert_eq!(req.status_item, StatusItem::Reserved(0));
875        assert_eq!(req.status_item.name(), "reserved");
876        assert_eq!(StatusQueryReq::parse(&req.to_bytes()).unwrap(), req);
877    }
878
879    #[test]
880    fn status_ack_with_status_bytes_round_trips_and_bites() {
881        let ack = StatusAck {
882            status_item: StatusItem::ViewedService,
883            status_bytes: &[0x01, 0x23, 0x00],
884        };
885        let bytes = ack.to_bytes();
886        // tag(3) + len(1=7) + item(4) + 3 status bytes.
887        assert_eq!(
888            bytes,
889            [0x9F, 0x80, 0x04, 0x07, 0, 0, 0, 3, 0x01, 0x23, 0x00]
890        );
891        assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
892        let mut other = ack.clone();
893        other.status_bytes = &[0x01, 0x23, 0x01];
894        assert_ne!(bytes, other.to_bytes());
895    }
896
897    #[test]
898    fn status_ack_empty_status_bytes_means_unsupported() {
899        let ack = StatusAck {
900            status_item: StatusItem::PortProfile,
901            status_bytes: &[],
902        };
903        let bytes = ack.to_bytes();
904        assert_eq!(bytes, [0x9F, 0x80, 0x04, 0x04, 0, 0, 0, 2]);
905        assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
906    }
907
908    #[test]
909    fn selection_information_multi_port_round_trips_and_bites() {
910        // Two input ports, the first routed to two outputs (boundary >= 2 loop).
911        let si = SelectionInformation {
912            time: &[0x20, 0x06, 0x18, 0x12, 0x00],
913            ports: alloc::vec![
914                InPortDescription {
915                    in_port_id: 0x01,
916                    in_signal_desc: &[0xAA, 0xBB],
917                    outputs: alloc::vec![
918                        OutputSignal {
919                            out_port_id: 0x00,
920                            out_signal_desc: &[0x02],
921                        },
922                        OutputSignal {
923                            out_port_id: 0x01,
924                            out_signal_desc: &[0x01],
925                        },
926                    ],
927                },
928                InPortDescription {
929                    in_port_id: 0x18,
930                    in_signal_desc: &[],
931                    outputs: alloc::vec![],
932                },
933            ],
934        };
935        let bytes = si.to_bytes();
936        assert_eq!(SelectionInformation::parse(&bytes).unwrap(), si);
937        // Mutate one out_port_id -> different bytes.
938        let mut other = si.clone();
939        other.ports[0].outputs[1].out_port_id = 0x02;
940        assert_ne!(bytes, other.to_bytes());
941        // Verify a precise byte layout for the first port's output block.
942        // time(5) | in_port(0x01) in_len(0x02) [AA BB] | reserved/len_out(0x00 0x06)
943        //   out0: 00 01 02 | out1: 01 01 01 | in_port(0x18) in_len(0x00) len_out(0x00 0x00)
944        assert_eq!(
945            bytes,
946            [
947                0x20, 0x06, 0x18, 0x12, 0x00, // time
948                0x01, 0x02, 0xAA, 0xBB, // in port 1
949                0x00, 0x06, // length_outputs = 6
950                0x00, 0x01, 0x02, // out 0
951                0x01, 0x01, 0x01, // out 1
952                0x18, 0x00, // in port 2
953                0x00, 0x00, // length_outputs = 0
954            ]
955        );
956    }
957
958    #[test]
959    fn port_profile_multi_port_round_trips_and_bites() {
960        let pp = PortProfile {
961            receiver_identification: b"ACME-TV1",
962            ports: alloc::vec![
963                PortDescription {
964                    in_port_id: 0x00,
965                    in_port_desc: b"RF0",
966                    out_port_id: 0x00,
967                    out_port_desc: b"D0",
968                },
969                PortDescription {
970                    in_port_id: 0x18,
971                    in_port_desc: b"CI",
972                    out_port_id: 0x10,
973                    out_port_desc: b"SCART",
974                },
975            ],
976        };
977        let bytes = pp.to_bytes();
978        assert_eq!(PortProfile::parse(&bytes).unwrap(), pp);
979        let mut other = pp.clone();
980        other.ports[1].out_port_id = 0x11;
981        assert_ne!(bytes, other.to_bytes());
982    }
983
984    #[test]
985    fn viewed_service_multi_component_round_trips_and_bites() {
986        let vs = ViewedService {
987            service_id: 0x1234,
988            component_tags: alloc::vec![0x10, 0x20, 0x30],
989        };
990        let bytes = vs.to_bytes();
991        assert_eq!(bytes, [0x12, 0x34, 0x03, 0x10, 0x20, 0x30]);
992        assert_eq!(ViewedService::parse(&bytes).unwrap(), vs);
993        let mut other = vs.clone();
994        other.component_tags[2] = 0x31;
995        assert_ne!(bytes, other.to_bytes());
996    }
997
998    #[test]
999    fn activation_status_packs_bits() {
1000        let a = ActivationStatus {
1001            event_activated: true,
1002            activation_state: Some(ActivationState::On),
1003        };
1004        let bytes = a.to_bytes();
1005        // event_activated(1<<3) | activation_state(2) = 0x0A.
1006        assert_eq!(bytes, [0x0A]);
1007        assert_eq!(ActivationStatus::parse(&bytes).unwrap(), a);
1008        // Standby-active, no event.
1009        let b = ActivationStatus {
1010            event_activated: false,
1011            activation_state: Some(ActivationState::StandbyActive),
1012        };
1013        assert_eq!(b.to_bytes(), [0x01]);
1014        assert_eq!(ActivationStatus::parse(&[0x01]).unwrap(), b);
1015    }
1016
1017    #[test]
1018    fn dispatch_routes_each_tag() {
1019        let q = StatusQueryReq {
1020            status_item: StatusItem::SelectionInformation,
1021        }
1022        .to_bytes();
1023        assert!(matches!(
1024            StatusQueryApdu::parse(&q).unwrap(),
1025            StatusQueryApdu::StatusQueryReq(_)
1026        ));
1027        let ack = StatusAck {
1028            status_item: StatusItem::ActivationStatus,
1029            status_bytes: &[0x0A],
1030        }
1031        .to_bytes();
1032        let parsed = StatusQueryApdu::parse(&ack).unwrap();
1033        assert!(matches!(parsed, StatusQueryApdu::StatusAck(_)));
1034        assert_eq!(parsed.to_bytes(), ack);
1035    }
1036}