Skip to main content

dvb_ci/ci_ext/
service_gateway.rs

1//! Generic Service Gateway objects — ETSI TS 101 699 V1.1.1 §6.1.3, Tables 21-31
2//! (PDF pp. 32-37). See `docs/ci_plus/input-modules.md`.
3//!
4//! A Type 'B' input module presents a **ServiceGateway** (Generic Service
5//! Gateway) resource for **service-level** access. These calls are inherited by
6//! all network-specific gateway resources (e.g. [`super::broadcast_service_gateway`]).
7//! The Generic Service Gateway is never instantiated on its own (Table 87 NOTE);
8//! its objects are modelled here so the network-specific gateways can route them.
9//!
10//! A service reference is the `{OriginalNetworkID, ServiceID}` pair (Figure 12).
11//!
12//! - `ServiceListReq` (`9F 80 00`, Table 22) — A → R: header-only.
13//! - `ServiceListAck` (`9F 80 01`, Table 23) — R → A: version + service list.
14//! - `ServiceListVersionReq` (`9F 80 02`, Table 24) — A → R: header-only.
15//! - `ServiceListVersionAck` (`9F 80 03`, Table 25) — R → A: version number.
16//! - `ServiceListChanged` (`9F 80 04`, Table 26) — R → A: new version number.
17//! - `ServiceDescReq` (`9F 80 05`, Table 27) — A → R: a service reference.
18//! - `ServiceDescAck` (`9F 80 06`, Table 28) — R → A: SDT-modelled service params.
19//! - `GetServiceReq` (`9F 80 07`, Table 29) — A → R: a service reference.
20//! - `GetServiceAck` (`9F 80 08`, Table 30) — R → A: service availability.
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 the Generic Service Gateway (Tables 22-30).
29pub mod tag {
30    use crate::tag::ApduTag;
31    /// `ServiceListReqTag` = `9F 80 00`.
32    pub const SERVICE_LIST_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
33    /// `ServiceListAckTag` = `9F 80 01`.
34    pub const SERVICE_LIST_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
35    /// `ServiceListVersionReqTag` = `9F 80 02`.
36    pub const SERVICE_LIST_VERSION_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
37    /// `ServiceListVersionAckTag` = `9F 80 03`.
38    pub const SERVICE_LIST_VERSION_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
39    /// `ServiceListChangedTag` = `9F 80 04`.
40    pub const SERVICE_LIST_CHANGED: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
41    /// `ServiceDescReqTag` = `9F 80 05`.
42    pub const SERVICE_DESC_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x05);
43    /// `ServiceDescAckTag` = `9F 80 06`.
44    pub const SERVICE_DESC_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x06);
45    /// `GetServiceReqTag` = `9F 80 07`.
46    pub const GET_SERVICE_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x07);
47    /// `GetServiceAckTag` = `9F 80 08`.
48    pub const GET_SERVICE_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x08);
49}
50
51/// A service reference — the `{OriginalNetworkID, ServiceID}` pair (Figure 12).
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize))]
54pub struct ServiceReference {
55    /// `OriginalNetworkID` (16-bit, allocated within ETR 162).
56    pub original_network_id: u16,
57    /// `ServiceID` (16-bit, unique within the original network).
58    pub service_id: u16,
59}
60
61/// `ServiceListReq()` (Table 22) — A → R: header-only.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize))]
64pub struct ServiceListReq;
65
66/// `ServiceListVersionReq()` (Table 24) — A → R: header-only.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize))]
69pub struct ServiceListVersionReq;
70
71/// `ServiceListAck()` (Table 23) — R → A: the services the resource can supply.
72#[derive(Debug, Clone, PartialEq, Eq, Default)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74pub struct ServiceListAck {
75    /// `VersionNumber` — increments each time the service list is updated.
76    pub version_number: u8,
77    /// The service references (`NumberOfServices` of them, may be 0).
78    pub services: Vec<ServiceReference>,
79}
80
81/// `ServiceListVersionAck()` (Table 25) — R → A: the service-list version.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize))]
84pub struct ServiceListVersionAck {
85    /// `VersionNumber`.
86    pub version_number: u8,
87}
88
89/// `ServiceListChanged()` (Table 26) — R → A: the service list changed.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize))]
92pub struct ServiceListChanged {
93    /// `VersionNumber` — the new version.
94    pub version_number: u8,
95}
96
97/// `ServiceDescReq()` (Table 27) — A → R: request info on a particular service.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize))]
100pub struct ServiceDescReq {
101    /// The service reference being queried.
102    pub service: ServiceReference,
103}
104
105/// `ServiceDescAck()` (Table 28) — R → A: SDT-modelled service parameters and a
106/// descriptor loop. The parameters mirror the SDT in ETS 300 468.
107#[derive(Debug, Clone, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
109pub struct ServiceDescAck<'a> {
110    /// The service reference being described.
111    pub service: ServiceReference,
112    /// `EIT_schedule_flag` — SDT meaning (ETS 300 468).
113    pub eit_schedule_flag: bool,
114    /// `EIT_present_following_flag` — SDT meaning.
115    pub eit_present_following_flag: bool,
116    /// `running_status` — 3-bit SDT running status (the spec prose "6 bit" is a
117    /// typo; 3 bits is authoritative — see `docs/ci_plus/input-modules.md`).
118    pub running_status: u8,
119    /// `free_CA_mode` — SDT meaning.
120    pub free_ca_mode: bool,
121    /// The SDT descriptor loop (`descriptors_loop_length` bytes), carried verbatim;
122    /// walk it with the dvb-si descriptor parsers.
123    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
124    pub descriptors: &'a [u8],
125}
126
127/// `GetServiceReq()` (Table 29) — A → R: request the resource to provide a
128/// service. An absent service reference (zero following bytes) requests a
129/// network disconnect.
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132pub struct GetServiceReq {
133    /// The requested service reference, or `None` for a network-disconnect request.
134    pub service: Option<ServiceReference>,
135}
136
137/// `GetServiceAck()` (Table 30) — R → A: availability of a requested service.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize))]
140pub struct GetServiceAck {
141    /// The service reference replied about.
142    pub service: ServiceReference,
143    /// `ServiceTerminated` — `1`: the service has finished (or a disconnect was
144    /// requested); navigation reverts to the host.
145    pub service_terminated: bool,
146    /// `ServiceNotAvailable` — `1`: the requested service is not available.
147    pub service_not_available: bool,
148    /// `CAServiceFlag` — `1`: conditional-access restrictions apply.
149    pub ca_service_flag: bool,
150    /// `ActualService` — the actual service id (MPEG program number) delivered;
151    /// `0` = no valid TS (the host should not attempt to decode).
152    pub actual_service: u16,
153}
154
155// --- header-only objects ---
156
157macro_rules! empty_object {
158    ($ty:ty, $tag:expr, $what:literal) => {
159        impl<'a> Parse<'a> for $ty {
160            type Error = Error;
161            fn parse(bytes: &'a [u8]) -> Result<Self> {
162                objects::parse_empty_apdu(bytes, $tag, $what)?;
163                Ok(Self)
164            }
165        }
166        impl Serialize for $ty {
167            type Error = Error;
168            fn serialized_len(&self) -> usize {
169                objects::empty_apdu_len()
170            }
171            fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
172                objects::serialize_empty_apdu($tag, buf)
173            }
174        }
175    };
176}
177
178empty_object!(ServiceListReq, tag::SERVICE_LIST_REQ, "ServiceListReq");
179empty_object!(
180    ServiceListVersionReq,
181    tag::SERVICE_LIST_VERSION_REQ,
182    "ServiceListVersionReq"
183);
184
185// --- single version-byte objects ---
186
187macro_rules! version_byte_object {
188    ($ty:ty, $tag:expr, $what:literal) => {
189        impl<'a> Parse<'a> for $ty {
190            type Error = Error;
191            fn parse(bytes: &'a [u8]) -> Result<Self> {
192                let body = objects::parse_apdu_header(bytes, $tag, $what)?;
193                if body.is_empty() {
194                    return Err(Error::BufferTooShort {
195                        need: 1,
196                        have: 0,
197                        what: $what,
198                    });
199                }
200                Ok(Self {
201                    version_number: body[0],
202                })
203            }
204        }
205        impl Serialize for $ty {
206            type Error = Error;
207            fn serialized_len(&self) -> usize {
208                objects::apdu_len(1)
209            }
210            fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
211                let pos = objects::write_apdu_header($tag, 1, buf)?;
212                buf[pos] = self.version_number;
213                Ok(pos + 1)
214            }
215        }
216    };
217}
218
219version_byte_object!(
220    ServiceListVersionAck,
221    tag::SERVICE_LIST_VERSION_ACK,
222    "ServiceListVersionAck"
223);
224version_byte_object!(
225    ServiceListChanged,
226    tag::SERVICE_LIST_CHANGED,
227    "ServiceListChanged"
228);
229
230// --- ServiceListAck ---
231
232/// Width of one service reference on the wire (`OriginalNetworkID` + `ServiceID`).
233const SERVICE_REF_LEN: usize = 4;
234// VersionNumber(1) + NumberOfServices(2).
235const SERVICE_LIST_ACK_PREFIX: usize = 3;
236
237impl<'a> Parse<'a> for ServiceListAck {
238    type Error = Error;
239    fn parse(bytes: &'a [u8]) -> Result<Self> {
240        let body = objects::parse_apdu_header(bytes, tag::SERVICE_LIST_ACK, "ServiceListAck")?;
241        if body.len() < SERVICE_LIST_ACK_PREFIX {
242            return Err(Error::BufferTooShort {
243                need: SERVICE_LIST_ACK_PREFIX,
244                have: body.len(),
245                what: "ServiceListAck",
246            });
247        }
248        let version_number = body[0];
249        let count = u16::from_be_bytes([body[1], body[2]]) as usize;
250        let list = &body[SERVICE_LIST_ACK_PREFIX..];
251        if list.len() < count * SERVICE_REF_LEN {
252            return Err(Error::LengthMismatch {
253                what: "ServiceListAck services",
254                declared: count * SERVICE_REF_LEN,
255                actual: list.len(),
256            });
257        }
258        let mut services = Vec::with_capacity(count);
259        for chunk in list[..count * SERVICE_REF_LEN].chunks_exact(SERVICE_REF_LEN) {
260            services.push(ServiceReference {
261                original_network_id: u16::from_be_bytes([chunk[0], chunk[1]]),
262                service_id: u16::from_be_bytes([chunk[2], chunk[3]]),
263            });
264        }
265        Ok(Self {
266            version_number,
267            services,
268        })
269    }
270}
271impl Serialize for ServiceListAck {
272    type Error = Error;
273    fn serialized_len(&self) -> usize {
274        objects::apdu_len(SERVICE_LIST_ACK_PREFIX + self.services.len() * SERVICE_REF_LEN)
275    }
276    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
277        if self.services.len() > u16::MAX as usize {
278            return Err(Error::InvalidObject {
279                what: "ServiceListAck",
280                reason: "more than 65535 services",
281            });
282        }
283        let body_len = SERVICE_LIST_ACK_PREFIX + self.services.len() * SERVICE_REF_LEN;
284        let mut pos = objects::write_apdu_header(tag::SERVICE_LIST_ACK, body_len, buf)?;
285        buf[pos] = self.version_number;
286        buf[pos + 1..pos + 3].copy_from_slice(&(self.services.len() as u16).to_be_bytes());
287        pos += SERVICE_LIST_ACK_PREFIX;
288        for s in &self.services {
289            buf[pos..pos + 2].copy_from_slice(&s.original_network_id.to_be_bytes());
290            buf[pos + 2..pos + 4].copy_from_slice(&s.service_id.to_be_bytes());
291            pos += SERVICE_REF_LEN;
292        }
293        Ok(pos)
294    }
295}
296
297// --- ServiceDescReq (service reference) ---
298
299impl<'a> Parse<'a> for ServiceDescReq {
300    type Error = Error;
301    fn parse(bytes: &'a [u8]) -> Result<Self> {
302        let body = objects::parse_apdu_header(bytes, tag::SERVICE_DESC_REQ, "ServiceDescReq")?;
303        if body.len() < SERVICE_REF_LEN {
304            return Err(Error::BufferTooShort {
305                need: SERVICE_REF_LEN,
306                have: body.len(),
307                what: "ServiceDescReq",
308            });
309        }
310        Ok(Self {
311            service: ServiceReference {
312                original_network_id: u16::from_be_bytes([body[0], body[1]]),
313                service_id: u16::from_be_bytes([body[2], body[3]]),
314            },
315        })
316    }
317}
318impl Serialize for ServiceDescReq {
319    type Error = Error;
320    fn serialized_len(&self) -> usize {
321        objects::apdu_len(SERVICE_REF_LEN)
322    }
323    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
324        let pos = objects::write_apdu_header(tag::SERVICE_DESC_REQ, SERVICE_REF_LEN, buf)?;
325        buf[pos..pos + 2].copy_from_slice(&self.service.original_network_id.to_be_bytes());
326        buf[pos + 2..pos + 4].copy_from_slice(&self.service.service_id.to_be_bytes());
327        Ok(pos + SERVICE_REF_LEN)
328    }
329}
330
331// --- ServiceDescAck ---
332
333// OriginalNetworkID(2) + ServiceID(2) + flags/status/loop_len(3) = 7 fixed bytes.
334const SERVICE_DESC_ACK_PREFIX: usize = SERVICE_REF_LEN + 3;
335
336impl<'a> Parse<'a> for ServiceDescAck<'a> {
337    type Error = Error;
338    fn parse(bytes: &'a [u8]) -> Result<Self> {
339        let body = objects::parse_apdu_header(bytes, tag::SERVICE_DESC_ACK, "ServiceDescAck")?;
340        if body.len() < SERVICE_DESC_ACK_PREFIX {
341            return Err(Error::BufferTooShort {
342                need: SERVICE_DESC_ACK_PREFIX,
343                have: body.len(),
344                what: "ServiceDescAck",
345            });
346        }
347        // byte 4: reserved_future_use(6) + EIT_schedule(1) + EIT_present_following(1)
348        let flags = body[4];
349        // byte 5..6: running_status(3) + free_CA_mode(1) + descriptors_loop_length(12)
350        let b5 = body[5];
351        let b6 = body[6];
352        let running_status = (b5 >> 5) & 0x07;
353        let free_ca_mode = (b5 & 0x10) != 0;
354        let loop_len = ((u16::from(b5 & 0x0F) << 8) | u16::from(b6)) as usize;
355        let desc_start = SERVICE_DESC_ACK_PREFIX;
356        let desc_end = desc_start + loop_len;
357        if body.len() < desc_end {
358            return Err(Error::LengthMismatch {
359                what: "ServiceDescAck descriptors",
360                declared: loop_len,
361                actual: body.len() - desc_start,
362            });
363        }
364        Ok(Self {
365            service: ServiceReference {
366                original_network_id: u16::from_be_bytes([body[0], body[1]]),
367                service_id: u16::from_be_bytes([body[2], body[3]]),
368            },
369            eit_schedule_flag: (flags & 0x02) != 0,
370            eit_present_following_flag: (flags & 0x01) != 0,
371            running_status,
372            free_ca_mode,
373            descriptors: &body[desc_start..desc_end],
374        })
375    }
376}
377impl Serialize for ServiceDescAck<'_> {
378    type Error = Error;
379    fn serialized_len(&self) -> usize {
380        objects::apdu_len(SERVICE_DESC_ACK_PREFIX + self.descriptors.len())
381    }
382    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
383        if self.descriptors.len() > 0x0FFF {
384            return Err(Error::InvalidObject {
385                what: "ServiceDescAck",
386                reason: "descriptors loop longer than 4095 bytes",
387            });
388        }
389        let body_len = SERVICE_DESC_ACK_PREFIX + self.descriptors.len();
390        let mut pos = objects::write_apdu_header(tag::SERVICE_DESC_ACK, body_len, buf)?;
391        buf[pos..pos + 2].copy_from_slice(&self.service.original_network_id.to_be_bytes());
392        buf[pos + 2..pos + 4].copy_from_slice(&self.service.service_id.to_be_bytes());
393        // reserved_future_use(6)=0b111111, EIT_schedule(1), EIT_present_following(1).
394        buf[pos + 4] = 0xFC
395            | (u8::from(self.eit_schedule_flag) << 1)
396            | u8::from(self.eit_present_following_flag);
397        let loop_len = self.descriptors.len() as u16;
398        // running_status(3), free_CA_mode(1), descriptors_loop_length(12).
399        buf[pos + 5] = ((self.running_status & 0x07) << 5)
400            | (u8::from(self.free_ca_mode) << 4)
401            | ((loop_len >> 8) as u8 & 0x0F);
402        buf[pos + 6] = loop_len as u8;
403        pos += SERVICE_DESC_ACK_PREFIX;
404        buf[pos..pos + self.descriptors.len()].copy_from_slice(self.descriptors);
405        Ok(pos + self.descriptors.len())
406    }
407}
408
409// --- GetServiceReq ---
410
411impl<'a> Parse<'a> for GetServiceReq {
412    type Error = Error;
413    fn parse(bytes: &'a [u8]) -> Result<Self> {
414        let body = objects::parse_apdu_header(bytes, tag::GET_SERVICE_REQ, "GetServiceReq")?;
415        let service = if body.is_empty() {
416            None
417        } else {
418            if body.len() < SERVICE_REF_LEN {
419                return Err(Error::BufferTooShort {
420                    need: SERVICE_REF_LEN,
421                    have: body.len(),
422                    what: "GetServiceReq",
423                });
424            }
425            Some(ServiceReference {
426                original_network_id: u16::from_be_bytes([body[0], body[1]]),
427                service_id: u16::from_be_bytes([body[2], body[3]]),
428            })
429        };
430        Ok(Self { service })
431    }
432}
433impl Serialize for GetServiceReq {
434    type Error = Error;
435    fn serialized_len(&self) -> usize {
436        objects::apdu_len(if self.service.is_some() {
437            SERVICE_REF_LEN
438        } else {
439            0
440        })
441    }
442    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
443        match self.service {
444            None => objects::write_apdu_header(tag::GET_SERVICE_REQ, 0, buf),
445            Some(s) => {
446                let pos = objects::write_apdu_header(tag::GET_SERVICE_REQ, SERVICE_REF_LEN, buf)?;
447                buf[pos..pos + 2].copy_from_slice(&s.original_network_id.to_be_bytes());
448                buf[pos + 2..pos + 4].copy_from_slice(&s.service_id.to_be_bytes());
449                Ok(pos + SERVICE_REF_LEN)
450            }
451        }
452    }
453}
454
455// --- GetServiceAck ---
456
457// OriginalNetworkID(2) + ServiceID(2) + flags(1) + ActualService(2).
458const GET_SERVICE_ACK_BODY: usize = SERVICE_REF_LEN + 1 + 2;
459
460impl<'a> Parse<'a> for GetServiceAck {
461    type Error = Error;
462    fn parse(bytes: &'a [u8]) -> Result<Self> {
463        let body = objects::parse_apdu_header(bytes, tag::GET_SERVICE_ACK, "GetServiceAck")?;
464        if body.len() < GET_SERVICE_ACK_BODY {
465            return Err(Error::BufferTooShort {
466                need: GET_SERVICE_ACK_BODY,
467                have: body.len(),
468                what: "GetServiceAck",
469            });
470        }
471        // byte 4: Reserved(5) + ServiceTerminated(1) + ServiceNotAvailable(1) + CAServiceFlag(1)
472        let flags = body[4];
473        Ok(Self {
474            service: ServiceReference {
475                original_network_id: u16::from_be_bytes([body[0], body[1]]),
476                service_id: u16::from_be_bytes([body[2], body[3]]),
477            },
478            service_terminated: (flags & 0x04) != 0,
479            service_not_available: (flags & 0x02) != 0,
480            ca_service_flag: (flags & 0x01) != 0,
481            actual_service: u16::from_be_bytes([body[5], body[6]]),
482        })
483    }
484}
485impl Serialize for GetServiceAck {
486    type Error = Error;
487    fn serialized_len(&self) -> usize {
488        objects::apdu_len(GET_SERVICE_ACK_BODY)
489    }
490    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
491        let pos = objects::write_apdu_header(tag::GET_SERVICE_ACK, GET_SERVICE_ACK_BODY, buf)?;
492        buf[pos..pos + 2].copy_from_slice(&self.service.original_network_id.to_be_bytes());
493        buf[pos + 2..pos + 4].copy_from_slice(&self.service.service_id.to_be_bytes());
494        // Reserved(5)=0, then the three flag bits.
495        buf[pos + 4] = (u8::from(self.service_terminated) << 2)
496            | (u8::from(self.service_not_available) << 1)
497            | u8::from(self.ca_service_flag);
498        buf[pos + 5..pos + 7].copy_from_slice(&self.actual_service.to_be_bytes());
499        Ok(pos + GET_SERVICE_ACK_BODY)
500    }
501}
502
503/// Resource-scoped dispatch over the Generic Service Gateway objects (Tables 22-30).
504#[derive(Debug, Clone, PartialEq, Eq)]
505#[cfg_attr(feature = "serde", derive(serde::Serialize))]
506#[non_exhaustive]
507pub enum ServiceGatewayApdu<'a> {
508    /// `ServiceListReq` (`9F 80 00`).
509    ServiceListReq(ServiceListReq),
510    /// `ServiceListAck` (`9F 80 01`).
511    ServiceListAck(ServiceListAck),
512    /// `ServiceListVersionReq` (`9F 80 02`).
513    ServiceListVersionReq(ServiceListVersionReq),
514    /// `ServiceListVersionAck` (`9F 80 03`).
515    ServiceListVersionAck(ServiceListVersionAck),
516    /// `ServiceListChanged` (`9F 80 04`).
517    ServiceListChanged(ServiceListChanged),
518    /// `ServiceDescReq` (`9F 80 05`).
519    ServiceDescReq(ServiceDescReq),
520    /// `ServiceDescAck` (`9F 80 06`).
521    ServiceDescAck(ServiceDescAck<'a>),
522    /// `GetServiceReq` (`9F 80 07`).
523    GetServiceReq(GetServiceReq),
524    /// `GetServiceAck` (`9F 80 08`).
525    GetServiceAck(GetServiceAck),
526}
527
528impl<'a> ServiceGatewayApdu<'a> {
529    /// Parse a Generic Service Gateway APDU, dispatching on the leading `apdu_tag`.
530    pub fn parse(body: &'a [u8]) -> Result<Self> {
531        if body.len() < 3 {
532            return Err(Error::BufferTooShort {
533                need: 3,
534                have: body.len(),
535                what: "service_gateway apdu_tag",
536            });
537        }
538        let t = ApduTag::from_bytes(body[0], body[1], body[2]);
539        match t {
540            tag::SERVICE_LIST_REQ => Ok(Self::ServiceListReq(ServiceListReq::parse(body)?)),
541            tag::SERVICE_LIST_ACK => Ok(Self::ServiceListAck(ServiceListAck::parse(body)?)),
542            tag::SERVICE_LIST_VERSION_REQ => Ok(Self::ServiceListVersionReq(
543                ServiceListVersionReq::parse(body)?,
544            )),
545            tag::SERVICE_LIST_VERSION_ACK => Ok(Self::ServiceListVersionAck(
546                ServiceListVersionAck::parse(body)?,
547            )),
548            tag::SERVICE_LIST_CHANGED => {
549                Ok(Self::ServiceListChanged(ServiceListChanged::parse(body)?))
550            }
551            tag::SERVICE_DESC_REQ => Ok(Self::ServiceDescReq(ServiceDescReq::parse(body)?)),
552            tag::SERVICE_DESC_ACK => Ok(Self::ServiceDescAck(ServiceDescAck::parse(body)?)),
553            tag::GET_SERVICE_REQ => Ok(Self::GetServiceReq(GetServiceReq::parse(body)?)),
554            tag::GET_SERVICE_ACK => Ok(Self::GetServiceAck(GetServiceAck::parse(body)?)),
555            _ => Err(Error::UnexpectedApduTag {
556                got: t.as_u24(),
557                expected: tag::SERVICE_LIST_REQ.as_u24(),
558                what: "service_gateway",
559            }),
560        }
561    }
562}
563
564impl Serialize for ServiceGatewayApdu<'_> {
565    type Error = Error;
566    fn serialized_len(&self) -> usize {
567        match self {
568            Self::ServiceListReq(o) => o.serialized_len(),
569            Self::ServiceListAck(o) => o.serialized_len(),
570            Self::ServiceListVersionReq(o) => o.serialized_len(),
571            Self::ServiceListVersionAck(o) => o.serialized_len(),
572            Self::ServiceListChanged(o) => o.serialized_len(),
573            Self::ServiceDescReq(o) => o.serialized_len(),
574            Self::ServiceDescAck(o) => o.serialized_len(),
575            Self::GetServiceReq(o) => o.serialized_len(),
576            Self::GetServiceAck(o) => o.serialized_len(),
577        }
578    }
579    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
580        match self {
581            Self::ServiceListReq(o) => o.serialize_into(buf),
582            Self::ServiceListAck(o) => o.serialize_into(buf),
583            Self::ServiceListVersionReq(o) => o.serialize_into(buf),
584            Self::ServiceListVersionAck(o) => o.serialize_into(buf),
585            Self::ServiceListChanged(o) => o.serialize_into(buf),
586            Self::ServiceDescReq(o) => o.serialize_into(buf),
587            Self::ServiceDescAck(o) => o.serialize_into(buf),
588            Self::GetServiceReq(o) => o.serialize_into(buf),
589            Self::GetServiceAck(o) => o.serialize_into(buf),
590        }
591    }
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597
598    #[test]
599    fn header_only_objects_round_trip() {
600        assert_eq!(ServiceListReq.to_bytes(), [0x9F, 0x80, 0x00, 0x00]);
601        assert_eq!(ServiceListVersionReq.to_bytes(), [0x9F, 0x80, 0x02, 0x00]);
602        assert_eq!(
603            ServiceListReq::parse(&[0x9F, 0x80, 0x00, 0x00]).unwrap(),
604            ServiceListReq
605        );
606    }
607
608    #[test]
609    fn service_list_ack_multi_round_trips_and_bites() {
610        let ack = ServiceListAck {
611            version_number: 0x07,
612            services: alloc::vec![
613                ServiceReference {
614                    original_network_id: 0x0001,
615                    service_id: 0x0064,
616                },
617                ServiceReference {
618                    original_network_id: 0x0001,
619                    service_id: 0x0065,
620                },
621            ],
622        };
623        let bytes = ack.to_bytes();
624        // tag(3) + len(1) + ver(1) + count(2) + 2*4 = 15; body = 11 = 0x0B.
625        assert_eq!(
626            bytes,
627            [
628                0x9F, 0x80, 0x01, 0x0B, 0x07, 0x00, 0x02, 0x00, 0x01, 0x00, 0x64, 0x00, 0x01, 0x00,
629                0x65
630            ]
631        );
632        assert_eq!(ServiceListAck::parse(&bytes).unwrap(), ack);
633        let mut other = ack.clone();
634        other.services[1].service_id = 0x0066;
635        assert_ne!(bytes, other.to_bytes());
636    }
637
638    #[test]
639    fn service_list_ack_empty_round_trips() {
640        let ack = ServiceListAck {
641            version_number: 0x02,
642            services: alloc::vec![],
643        };
644        let bytes = ack.to_bytes();
645        assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x03, 0x02, 0x00, 0x00]);
646        assert_eq!(ServiceListAck::parse(&bytes).unwrap(), ack);
647    }
648
649    #[test]
650    fn version_ack_and_changed_round_trip_and_bite() {
651        let v = ServiceListVersionAck { version_number: 9 };
652        let bytes = v.to_bytes();
653        assert_eq!(bytes, [0x9F, 0x80, 0x03, 0x01, 0x09]);
654        assert_eq!(ServiceListVersionAck::parse(&bytes).unwrap(), v);
655        assert_ne!(
656            bytes,
657            ServiceListVersionAck { version_number: 10 }.to_bytes()
658        );
659
660        let c = ServiceListChanged { version_number: 3 };
661        let cbytes = c.to_bytes();
662        assert_eq!(cbytes, [0x9F, 0x80, 0x04, 0x01, 0x03]);
663        assert_eq!(ServiceListChanged::parse(&cbytes).unwrap(), c);
664    }
665
666    #[test]
667    fn service_desc_req_round_trips_and_bites() {
668        let r = ServiceDescReq {
669            service: ServiceReference {
670                original_network_id: 0x1234,
671                service_id: 0x5678,
672            },
673        };
674        let bytes = r.to_bytes();
675        assert_eq!(bytes, [0x9F, 0x80, 0x05, 0x04, 0x12, 0x34, 0x56, 0x78]);
676        assert_eq!(ServiceDescReq::parse(&bytes).unwrap(), r);
677        let mut other = r;
678        other.service.service_id = 0x5679;
679        assert_ne!(bytes, other.to_bytes());
680    }
681
682    #[test]
683    fn service_desc_ack_round_trips_and_bites() {
684        // 2 descriptors: 0x48 (service descriptor) len 2, and 0x52 len 1.
685        let desc = [0x48, 0x02, 0xAA, 0xBB, 0x52, 0x01, 0x03];
686        let ack = ServiceDescAck {
687            service: ServiceReference {
688                original_network_id: 0x0001,
689                service_id: 0x0064,
690            },
691            eit_schedule_flag: true,
692            eit_present_following_flag: false,
693            running_status: 4, // running
694            free_ca_mode: true,
695            descriptors: &desc,
696        };
697        let bytes = ack.to_bytes();
698        // prefix(7) + 7 desc = 14 body; tag(3)+len(1)+14 = 18.
699        // byte4 = 0xFC | (1<<1) | 0 = 0xFE
700        // byte5 = (4<<5) | (1<<4) | (loop_len>>8) = 0x80 | 0x10 | 0x00 = 0x90
701        // byte6 = loop_len = 7
702        assert_eq!(
703            bytes,
704            [
705                0x9F, 0x80, 0x06, 0x0E, 0x00, 0x01, 0x00, 0x64, 0xFE, 0x90, 0x07, 0x48, 0x02, 0xAA,
706                0xBB, 0x52, 0x01, 0x03
707            ]
708        );
709        let parsed = ServiceDescAck::parse(&bytes).unwrap();
710        assert_eq!(parsed, ack);
711        assert_eq!(parsed.running_status, 4);
712        assert!(parsed.eit_schedule_flag);
713        assert!(!parsed.eit_present_following_flag);
714        assert!(parsed.free_ca_mode);
715        let mut other = ack.clone();
716        other.running_status = 1;
717        assert_ne!(bytes, other.to_bytes());
718    }
719
720    #[test]
721    fn service_desc_ack_empty_loop() {
722        let ack = ServiceDescAck {
723            service: ServiceReference {
724                original_network_id: 0xAAAA,
725                service_id: 0xBBBB,
726            },
727            eit_schedule_flag: false,
728            eit_present_following_flag: true,
729            running_status: 0,
730            free_ca_mode: false,
731            descriptors: &[],
732        };
733        let bytes = ack.to_bytes();
734        // byte4 = 0xFC | 0 | 1 = 0xFD ; byte5 = 0 ; byte6 = 0
735        assert_eq!(
736            bytes,
737            [
738                0x9F, 0x80, 0x06, 0x07, 0xAA, 0xAA, 0xBB, 0xBB, 0xFD, 0x00, 0x00
739            ]
740        );
741        assert_eq!(ServiceDescAck::parse(&bytes).unwrap(), ack);
742    }
743
744    #[test]
745    fn get_service_req_with_and_without_ref() {
746        let r = GetServiceReq {
747            service: Some(ServiceReference {
748                original_network_id: 0x0001,
749                service_id: 0x0064,
750            }),
751        };
752        let bytes = r.to_bytes();
753        assert_eq!(bytes, [0x9F, 0x80, 0x07, 0x04, 0x00, 0x01, 0x00, 0x64]);
754        assert_eq!(GetServiceReq::parse(&bytes).unwrap(), r);
755
756        let disconnect = GetServiceReq { service: None };
757        let dbytes = disconnect.to_bytes();
758        assert_eq!(dbytes, [0x9F, 0x80, 0x07, 0x00]);
759        assert_eq!(GetServiceReq::parse(&dbytes).unwrap(), disconnect);
760        assert_ne!(bytes, dbytes);
761    }
762
763    #[test]
764    fn get_service_ack_round_trips_and_bites() {
765        let ack = GetServiceAck {
766            service: ServiceReference {
767                original_network_id: 0x0001,
768                service_id: 0x0064,
769            },
770            service_terminated: false,
771            service_not_available: false,
772            ca_service_flag: true,
773            actual_service: 0x0064,
774        };
775        let bytes = ack.to_bytes();
776        // byte4 = CAServiceFlag = 0x01 ; ActualService = 0x0064
777        assert_eq!(
778            bytes,
779            [
780                0x9F, 0x80, 0x08, 0x07, 0x00, 0x01, 0x00, 0x64, 0x01, 0x00, 0x64
781            ]
782        );
783        assert_eq!(GetServiceAck::parse(&bytes).unwrap(), ack);
784        // Table 31: a "not available" combination.
785        let mut other = ack;
786        other.ca_service_flag = false;
787        other.service_not_available = true;
788        other.actual_service = 0;
789        assert_ne!(bytes, other.to_bytes());
790        assert_eq!(other.to_bytes()[8], 0x02);
791    }
792
793    #[test]
794    fn dispatch_routes_each_tag() {
795        let req = ServiceListReq.to_bytes();
796        assert!(matches!(
797            ServiceGatewayApdu::parse(&req).unwrap(),
798            ServiceGatewayApdu::ServiceListReq(_)
799        ));
800        let gs = GetServiceAck {
801            service: ServiceReference::default(),
802            service_terminated: true,
803            service_not_available: false,
804            ca_service_flag: false,
805            actual_service: 0,
806        }
807        .to_bytes();
808        let parsed = ServiceGatewayApdu::parse(&gs).unwrap();
809        assert!(matches!(parsed, ServiceGatewayApdu::GetServiceAck(_)));
810        assert_eq!(parsed.to_bytes(), gs);
811    }
812}