1use super::service_gateway::ServiceGatewayApdu;
19use crate::error::{Error, Result};
20use crate::objects;
21use crate::tag::ApduTag;
22use alloc::vec::Vec;
23use broadcast_common::{Parse, Serialize};
24
25pub mod tag {
28 use crate::tag::ApduTag;
29 pub const EIT_SECTION_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x10);
31 pub const EIT_SECTION_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x11);
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize))]
38#[non_exhaustive]
39pub enum EitResponseCode {
40 NotOnPresentDocument,
42 NotAvailable,
44 SectionFound,
46 Reserved,
48}
49
50impl EitResponseCode {
51 #[must_use]
53 pub fn from_u8(v: u8) -> Self {
54 match v & 0x03 {
55 0b00 => Self::NotOnPresentDocument,
56 0b01 => Self::NotAvailable,
57 0b10 => Self::SectionFound,
58 _ => Self::Reserved,
59 }
60 }
61 #[must_use]
63 pub const fn to_u8(self) -> u8 {
64 match self {
65 Self::NotOnPresentDocument => 0b00,
66 Self::NotAvailable => 0b01,
67 Self::SectionFound => 0b10,
68 Self::Reserved => 0b11,
69 }
70 }
71 #[must_use]
73 pub fn name(&self) -> &'static str {
74 match self {
75 Self::NotOnPresentDocument => "Section not on the present document",
76 Self::NotAvailable => "Section not available",
77 Self::SectionFound => "Section found",
78 Self::Reserved => "reserved",
79 }
80 }
81}
82broadcast_common::impl_spec_display!(EitResponseCode);
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize))]
87pub struct EitSectionReq {
88 pub table_id: u16,
91 pub service_id: u16,
93 pub section_number: u8,
95 pub original_network_id: u16,
97 pub ok_to_disrupt_service: bool,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize))]
106pub struct EitEvent<'a> {
107 pub event_id: u16,
109 pub start_time: [u8; 5],
111 pub duration: [u8; 3],
113 pub running_status: u8,
115 pub free_ca_mode: bool,
117 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
119 pub descriptors: &'a [u8],
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
124#[cfg_attr(feature = "serde", derive(serde::Serialize))]
125pub struct EitSectionAck<'a> {
126 pub response_code: EitResponseCode,
128 #[cfg_attr(feature = "serde", serde(borrow))]
130 pub events: Vec<EitEvent<'a>>,
131}
132
133const EIT_SECTION_REQ_BODY: usize = 2 + 2 + 1 + 2 + 1;
138
139impl<'a> Parse<'a> for EitSectionReq {
140 type Error = Error;
141 fn parse(bytes: &'a [u8]) -> Result<Self> {
142 let body = objects::parse_apdu_header(bytes, tag::EIT_SECTION_REQ, "EITSectionReq")?;
143 if body.len() < EIT_SECTION_REQ_BODY {
144 return Err(Error::BufferTooShort {
145 need: EIT_SECTION_REQ_BODY,
146 have: body.len(),
147 what: "EITSectionReq",
148 });
149 }
150 Ok(Self {
151 table_id: u16::from_be_bytes([body[0], body[1]]),
152 service_id: u16::from_be_bytes([body[2], body[3]]),
153 section_number: body[4],
154 original_network_id: u16::from_be_bytes([body[5], body[6]]),
155 ok_to_disrupt_service: (body[7] & 0x01) != 0,
156 })
157 }
158}
159impl Serialize for EitSectionReq {
160 type Error = Error;
161 fn serialized_len(&self) -> usize {
162 objects::apdu_len(EIT_SECTION_REQ_BODY)
163 }
164 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
165 let pos = objects::write_apdu_header(tag::EIT_SECTION_REQ, EIT_SECTION_REQ_BODY, buf)?;
166 buf[pos..pos + 2].copy_from_slice(&self.table_id.to_be_bytes());
167 buf[pos + 2..pos + 4].copy_from_slice(&self.service_id.to_be_bytes());
168 buf[pos + 4] = self.section_number;
169 buf[pos + 5..pos + 7].copy_from_slice(&self.original_network_id.to_be_bytes());
170 buf[pos + 7] = u8::from(self.ok_to_disrupt_service);
172 Ok(pos + EIT_SECTION_REQ_BODY)
173 }
174}
175
176const EIT_SECTION_ACK_PREFIX: usize = 2;
180const EIT_EVENT_FIXED: usize = 2 + 5 + 3 + 2;
182
183impl<'a> Parse<'a> for EitSectionAck<'a> {
184 type Error = Error;
185 fn parse(bytes: &'a [u8]) -> Result<Self> {
186 let body = objects::parse_apdu_header(bytes, tag::EIT_SECTION_ACK, "EITSectionAck")?;
187 if body.len() < EIT_SECTION_ACK_PREFIX {
188 return Err(Error::BufferTooShort {
189 need: EIT_SECTION_ACK_PREFIX,
190 have: body.len(),
191 what: "EITSectionAck",
192 });
193 }
194 let response_code = EitResponseCode::from_u8((body[0] >> 4) & 0x03);
196 let length = ((u16::from(body[0] & 0x0F) << 8) | u16::from(body[1])) as usize;
197 let loop_start = EIT_SECTION_ACK_PREFIX;
198 let loop_end = loop_start + length;
199 if body.len() < loop_end {
200 return Err(Error::LengthMismatch {
201 what: "EITSectionAck event loop",
202 declared: length,
203 actual: body.len() - loop_start,
204 });
205 }
206 let mut events = Vec::new();
207 let mut p = loop_start;
208 while p < loop_end {
209 if loop_end - p < EIT_EVENT_FIXED {
210 return Err(Error::InvalidObject {
211 what: "EITSectionAck event",
212 reason: "truncated event header",
213 });
214 }
215 let event_id = u16::from_be_bytes([body[p], body[p + 1]]);
216 let mut start_time = [0u8; 5];
217 start_time.copy_from_slice(&body[p + 2..p + 7]);
218 let mut duration = [0u8; 3];
219 duration.copy_from_slice(&body[p + 7..p + 10]);
220 let b10 = body[p + 10];
221 let b11 = body[p + 11];
222 let running_status = (b10 >> 5) & 0x07;
223 let free_ca_mode = (b10 & 0x10) != 0;
224 let dll = ((u16::from(b10 & 0x0F) << 8) | u16::from(b11)) as usize;
225 let desc_start = p + EIT_EVENT_FIXED;
226 let desc_end = desc_start + dll;
227 if desc_end > loop_end {
228 return Err(Error::LengthMismatch {
229 what: "EITSectionAck event descriptors",
230 declared: dll,
231 actual: loop_end - desc_start,
232 });
233 }
234 events.push(EitEvent {
235 event_id,
236 start_time,
237 duration,
238 running_status,
239 free_ca_mode,
240 descriptors: &body[desc_start..desc_end],
241 });
242 p = desc_end;
243 }
244 Ok(Self {
245 response_code,
246 events,
247 })
248 }
249}
250
251impl EitSectionAck<'_> {
252 fn loop_len(&self) -> usize {
254 self.events
255 .iter()
256 .map(|e| EIT_EVENT_FIXED + e.descriptors.len())
257 .sum()
258 }
259}
260
261impl Serialize for EitSectionAck<'_> {
262 type Error = Error;
263 fn serialized_len(&self) -> usize {
264 objects::apdu_len(EIT_SECTION_ACK_PREFIX + self.loop_len())
265 }
266 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
267 let length = self.loop_len();
268 if length > 0x0FFF {
269 return Err(Error::InvalidObject {
270 what: "EITSectionAck",
271 reason: "event loop longer than 4095 bytes",
272 });
273 }
274 let body_len = EIT_SECTION_ACK_PREFIX + length;
275 let mut pos = objects::write_apdu_header(tag::EIT_SECTION_ACK, body_len, buf)?;
276 let len16 = length as u16;
277 buf[pos] = (self.response_code.to_u8() << 4) | ((len16 >> 8) as u8 & 0x0F);
279 buf[pos + 1] = len16 as u8;
280 pos += EIT_SECTION_ACK_PREFIX;
281 for e in &self.events {
282 if e.descriptors.len() > 0x0FFF {
283 return Err(Error::InvalidObject {
284 what: "EITSectionAck event",
285 reason: "event descriptors loop longer than 4095 bytes",
286 });
287 }
288 buf[pos..pos + 2].copy_from_slice(&e.event_id.to_be_bytes());
289 buf[pos + 2..pos + 7].copy_from_slice(&e.start_time);
290 buf[pos + 7..pos + 10].copy_from_slice(&e.duration);
291 let dll = e.descriptors.len() as u16;
292 buf[pos + 10] = ((e.running_status & 0x07) << 5)
293 | (u8::from(e.free_ca_mode) << 4)
294 | ((dll >> 8) as u8 & 0x0F);
295 buf[pos + 11] = dll as u8;
296 pos += EIT_EVENT_FIXED;
297 buf[pos..pos + e.descriptors.len()].copy_from_slice(e.descriptors);
298 pos += e.descriptors.len();
299 }
300 Ok(pos)
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
309#[cfg_attr(feature = "serde", derive(serde::Serialize))]
310#[non_exhaustive]
311pub enum BroadcastServiceGatewayApdu<'a> {
312 #[cfg_attr(feature = "serde", serde(borrow))]
314 ServiceGateway(ServiceGatewayApdu<'a>),
315 EitSectionReq(EitSectionReq),
317 EitSectionAck(EitSectionAck<'a>),
319}
320
321impl<'a> BroadcastServiceGatewayApdu<'a> {
322 pub fn parse(body: &'a [u8]) -> Result<Self> {
326 if body.len() < 3 {
327 return Err(Error::BufferTooShort {
328 need: 3,
329 have: body.len(),
330 what: "broadcast_service_gateway apdu_tag",
331 });
332 }
333 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
334 match t {
335 tag::EIT_SECTION_REQ => Ok(Self::EitSectionReq(EitSectionReq::parse(body)?)),
336 tag::EIT_SECTION_ACK => Ok(Self::EitSectionAck(EitSectionAck::parse(body)?)),
337 _ => Ok(Self::ServiceGateway(ServiceGatewayApdu::parse(body)?)),
339 }
340 }
341}
342
343impl Serialize for BroadcastServiceGatewayApdu<'_> {
344 type Error = Error;
345 fn serialized_len(&self) -> usize {
346 match self {
347 Self::ServiceGateway(o) => o.serialized_len(),
348 Self::EitSectionReq(o) => o.serialized_len(),
349 Self::EitSectionAck(o) => o.serialized_len(),
350 }
351 }
352 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
353 match self {
354 Self::ServiceGateway(o) => o.serialize_into(buf),
355 Self::EitSectionReq(o) => o.serialize_into(buf),
356 Self::EitSectionAck(o) => o.serialize_into(buf),
357 }
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::super::service_gateway::{ServiceListReq, ServiceReference};
364 use super::*;
365
366 #[test]
367 fn eit_section_req_round_trips_and_bites() {
368 let req = EitSectionReq {
369 table_id: 0x004E,
370 service_id: 0x0064,
371 section_number: 0x00,
372 original_network_id: 0x0001,
373 ok_to_disrupt_service: true,
374 };
375 let bytes = req.to_bytes();
376 assert_eq!(
378 bytes,
379 [
380 0x9F, 0x80, 0x10, 0x08, 0x00, 0x4E, 0x00, 0x64, 0x00, 0x00, 0x01, 0x01
381 ]
382 );
383 assert_eq!(EitSectionReq::parse(&bytes).unwrap(), req);
384 let mut other = req;
385 other.ok_to_disrupt_service = false;
386 assert_ne!(bytes, other.to_bytes());
387 assert_eq!(other.to_bytes()[11], 0x00);
388 }
389
390 #[test]
391 fn eit_section_ack_multi_event_round_trips_and_bites() {
392 let d0 = [0x4D, 0x00]; let e0 = EitEvent {
395 event_id: 0x1001,
396 start_time: [0xC0, 0x79, 0x12, 0x45, 0x00],
397 duration: [0x01, 0x30, 0x00],
398 running_status: 4,
399 free_ca_mode: false,
400 descriptors: &d0,
401 };
402 let e1 = EitEvent {
403 event_id: 0x1002,
404 start_time: [0xC0, 0x79, 0x14, 0x15, 0x00],
405 duration: [0x00, 0x45, 0x00],
406 running_status: 1,
407 free_ca_mode: true,
408 descriptors: &[],
409 };
410 let ack = EitSectionAck {
411 response_code: EitResponseCode::SectionFound,
412 events: alloc::vec![e0, e1],
413 };
414 let bytes = ack.to_bytes();
415 assert_eq!(bytes[0..6], [0x9F, 0x80, 0x11, 0x1C, 0x20, 0x1A]);
418 assert_eq!(bytes[16], 0x80);
420 assert_eq!(bytes[17], 0x02);
421 let parsed = EitSectionAck::parse(&bytes).unwrap();
422 assert_eq!(parsed, ack);
423 assert_eq!(parsed.events.len(), 2);
424 assert_eq!(parsed.events[0].running_status, 4);
425 assert!(parsed.events[1].free_ca_mode);
426 let mut other = ack.clone();
427 other.events[0].event_id = 0x1003;
428 assert_ne!(bytes, other.to_bytes());
429 }
430
431 #[test]
432 fn eit_section_ack_empty_loop() {
433 let ack = EitSectionAck {
434 response_code: EitResponseCode::NotAvailable,
435 events: alloc::vec![],
436 };
437 let bytes = ack.to_bytes();
438 assert_eq!(bytes, [0x9F, 0x80, 0x11, 0x02, 0x10, 0x00]);
440 assert_eq!(EitSectionAck::parse(&bytes).unwrap(), ack);
441 assert_eq!(ack.response_code.name(), "Section not available");
442 }
443
444 #[test]
445 fn bsg_routes_eit_tags() {
446 let req = EitSectionReq {
447 table_id: 0x4E,
448 service_id: 1,
449 section_number: 0,
450 original_network_id: 1,
451 ok_to_disrupt_service: false,
452 }
453 .to_bytes();
454 assert!(matches!(
455 BroadcastServiceGatewayApdu::parse(&req).unwrap(),
456 BroadcastServiceGatewayApdu::EitSectionReq(_)
457 ));
458 }
459
460 #[test]
461 fn bsg_delegates_generic_gateway_tags() {
462 let req = ServiceListReq.to_bytes();
464 let parsed = BroadcastServiceGatewayApdu::parse(&req).unwrap();
465 assert!(matches!(
466 parsed,
467 BroadcastServiceGatewayApdu::ServiceGateway(ServiceGatewayApdu::ServiceListReq(_))
468 ));
469 assert_eq!(parsed.to_bytes(), req);
470
471 use super::super::service_gateway::ServiceDescAck;
473 let sda = ServiceDescAck {
474 service: ServiceReference {
475 original_network_id: 1,
476 service_id: 0x64,
477 },
478 eit_schedule_flag: true,
479 eit_present_following_flag: true,
480 running_status: 4,
481 free_ca_mode: false,
482 descriptors: &[0x48, 0x01, 0x01],
483 }
484 .to_bytes();
485 let parsed = BroadcastServiceGatewayApdu::parse(&sda).unwrap();
486 assert!(matches!(
487 parsed,
488 BroadcastServiceGatewayApdu::ServiceGateway(ServiceGatewayApdu::ServiceDescAck(_))
489 ));
490 assert_eq!(parsed.to_bytes(), sda);
491 }
492}