1use crate::error::{Error, Result};
24use crate::objects;
25use crate::tag::ApduTag;
26use alloc::vec::Vec;
27use broadcast_common::{Parse, Serialize};
28
29pub mod tag {
32 use crate::tag::ApduTag;
33 pub const CC_PIN_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x90, 0x14);
35 pub const CC_PIN_EVENT: ApduTag = ApduTag::from_bytes(0x9F, 0x90, 0x15);
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize))]
45pub struct CcPinReply {
46 pub lts_id: Option<u8>,
49 pub pincode_status: u8,
52}
53
54const CC_PIN_REPLY_BODY: usize = 3;
56const LTS_BOUND_FLAG_BIT: u8 = 0x01;
58
59impl<'a> Parse<'a> for CcPinReply {
60 type Error = Error;
61 fn parse(bytes: &'a [u8]) -> Result<Self> {
62 let body = objects::parse_apdu_header(bytes, tag::CC_PIN_REPLY, "cc_PIN_reply")?;
63 if body.len() < CC_PIN_REPLY_BODY {
64 return Err(Error::BufferTooShort {
65 need: CC_PIN_REPLY_BODY,
66 have: body.len(),
67 what: "cc_PIN_reply",
68 });
69 }
70 let lts_bound = body[0] & LTS_BOUND_FLAG_BIT != 0;
71 let lts_id = if lts_bound { Some(body[1]) } else { None };
72 Ok(Self {
73 lts_id,
74 pincode_status: body[2],
75 })
76 }
77}
78impl Serialize for CcPinReply {
79 type Error = Error;
80 fn serialized_len(&self) -> usize {
81 objects::apdu_len(CC_PIN_REPLY_BODY)
82 }
83 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
84 let pos = objects::write_apdu_header(tag::CC_PIN_REPLY, CC_PIN_REPLY_BODY, buf)?;
85 match self.lts_id {
86 Some(id) => {
87 buf[pos] = LTS_BOUND_FLAG_BIT;
88 buf[pos + 1] = id;
89 }
90 None => {
91 buf[pos] = 0;
92 buf[pos + 1] = 0;
94 }
95 }
96 buf[pos + 2] = self.pincode_status;
97 Ok(pos + CC_PIN_REPLY_BODY)
98 }
99}
100
101pub const PIN_EVENT_PRIVATE_DATA_LEN: usize = 15;
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111pub struct CcPinEvent {
112 pub lts_id: u8,
114 pub program_number: u16,
116 pub pincode_status: u8,
118 pub rating: u8,
120 pub pin_event_time_utc: u64,
122 pub pin_event_time_centiseconds: u8,
124 pub private_data: [u8; PIN_EVENT_PRIVATE_DATA_LEN],
126}
127
128const CC_PIN_EVENT_BODY: usize = 1 + 2 + 1 + 1 + 5 + 1 + PIN_EVENT_PRIVATE_DATA_LEN;
130
131impl<'a> Parse<'a> for CcPinEvent {
132 type Error = Error;
133 fn parse(bytes: &'a [u8]) -> Result<Self> {
134 let body = objects::parse_apdu_header(bytes, tag::CC_PIN_EVENT, "cc_PIN_event")?;
135 if body.len() < CC_PIN_EVENT_BODY {
136 return Err(Error::BufferTooShort {
137 need: CC_PIN_EVENT_BODY,
138 have: body.len(),
139 what: "cc_PIN_event",
140 });
141 }
142 let mut utc = 0u64;
146 for &b in &body[5..10] {
147 utc = (utc << 8) | b as u64;
148 }
149 let mut private_data = [0u8; PIN_EVENT_PRIVATE_DATA_LEN];
150 private_data.copy_from_slice(&body[11..11 + PIN_EVENT_PRIVATE_DATA_LEN]);
151 Ok(Self {
152 lts_id: body[0],
153 program_number: u16::from_be_bytes([body[1], body[2]]),
154 pincode_status: body[3],
155 rating: body[4],
156 pin_event_time_utc: utc,
157 pin_event_time_centiseconds: body[10],
158 private_data,
159 })
160 }
161}
162impl Serialize for CcPinEvent {
163 type Error = Error;
164 fn serialized_len(&self) -> usize {
165 objects::apdu_len(CC_PIN_EVENT_BODY)
166 }
167 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
168 let pos = objects::write_apdu_header(tag::CC_PIN_EVENT, CC_PIN_EVENT_BODY, buf)?;
169 buf[pos] = self.lts_id;
170 buf[pos + 1..pos + 3].copy_from_slice(&self.program_number.to_be_bytes());
171 buf[pos + 3] = self.pincode_status;
172 buf[pos + 4] = self.rating;
173 let utc = self.pin_event_time_utc.to_be_bytes();
175 buf[pos + 5..pos + 10].copy_from_slice(&utc[3..8]);
176 buf[pos + 10] = self.pin_event_time_centiseconds;
177 buf[pos + 11..pos + 11 + PIN_EVENT_PRIVATE_DATA_LEN].copy_from_slice(&self.private_data);
178 Ok(pos + CC_PIN_EVENT_BODY)
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize))]
189#[non_exhaustive]
190pub enum DatatypeId {
191 UriMessage,
193 ProgramNumber,
195 UriConfirm,
197 CicamLicense,
199 LicenseStatus,
201 LicenseRcvdStatus,
203 OperatingMode,
205 PinCode,
207 RecordStartStatus,
209 ModeChangeStatus,
211 RecordStopStatus,
213 LtsId,
215 Other(u8),
217}
218
219impl DatatypeId {
220 #[must_use]
222 pub fn from_u8(v: u8) -> Self {
223 match v {
224 25 => Self::UriMessage,
225 26 => Self::ProgramNumber,
226 27 => Self::UriConfirm,
227 33 => Self::CicamLicense,
228 34 => Self::LicenseStatus,
229 35 => Self::LicenseRcvdStatus,
230 38 => Self::OperatingMode,
231 39 => Self::PinCode,
232 40 => Self::RecordStartStatus,
233 41 => Self::ModeChangeStatus,
234 42 => Self::RecordStopStatus,
235 50 => Self::LtsId,
236 other => Self::Other(other),
237 }
238 }
239 #[must_use]
241 pub const fn to_u8(self) -> u8 {
242 match self {
243 Self::UriMessage => 25,
244 Self::ProgramNumber => 26,
245 Self::UriConfirm => 27,
246 Self::CicamLicense => 33,
247 Self::LicenseStatus => 34,
248 Self::LicenseRcvdStatus => 35,
249 Self::OperatingMode => 38,
250 Self::PinCode => 39,
251 Self::RecordStartStatus => 40,
252 Self::ModeChangeStatus => 41,
253 Self::RecordStopStatus => 42,
254 Self::LtsId => 50,
255 Self::Other(v) => v,
256 }
257 }
258 #[must_use]
260 pub fn name(&self) -> &'static str {
261 match self {
262 Self::UriMessage => "uri_message",
263 Self::ProgramNumber => "program_number",
264 Self::UriConfirm => "uri_confirm",
265 Self::CicamLicense => "cicam_license",
266 Self::LicenseStatus => "license_status",
267 Self::LicenseRcvdStatus => "license_rcvd_status",
268 Self::OperatingMode => "operating_mode",
269 Self::PinCode => "PINcode",
270 Self::RecordStartStatus => "record_start_status",
271 Self::ModeChangeStatus => "mode_change_status",
272 Self::RecordStopStatus => "record_stop_status",
273 Self::LtsId => "LTS_id",
274 Self::Other(_) => "reserved",
275 }
276 }
277}
278broadcast_common::impl_spec_display!(DatatypeId, Other);
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283#[cfg_attr(feature = "serde", derive(serde::Serialize))]
284#[non_exhaustive]
285pub enum OperatingMode {
286 Timeshift,
288 UnattendedRecording,
290 Other(u8),
292}
293
294impl OperatingMode {
295 #[must_use]
297 pub fn from_u8(v: u8) -> Self {
298 match v {
299 0x01 => Self::Timeshift,
300 0x02 => Self::UnattendedRecording,
301 other => Self::Other(other),
302 }
303 }
304 #[must_use]
306 pub const fn to_u8(self) -> u8 {
307 match self {
308 Self::Timeshift => 0x01,
309 Self::UnattendedRecording => 0x02,
310 Self::Other(v) => v,
311 }
312 }
313 #[must_use]
315 pub fn name(&self) -> &'static str {
316 match self {
317 Self::Timeshift => "Timeshift",
318 Self::UnattendedRecording => "Unattended_Recording",
319 Self::Other(_) => "reserved",
320 }
321 }
322}
323broadcast_common::impl_spec_display!(OperatingMode, Other);
324
325#[derive(Debug, Clone, PartialEq, Eq)]
329#[cfg_attr(feature = "serde", derive(serde::Serialize))]
330pub struct SacDatatype<'a> {
331 pub datatype_id: DatatypeId,
333 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
335 pub value: &'a [u8],
336}
337
338const SAC_DATATYPE_HEADER: usize = 3;
340
341impl SacDatatype<'_> {
342 #[must_use]
344 pub fn serialized_len(&self) -> usize {
345 SAC_DATATYPE_HEADER + self.value.len()
346 }
347}
348
349#[derive(Debug, Clone, PartialEq, Eq)]
354#[cfg_attr(feature = "serde", derive(serde::Serialize))]
355pub struct SacMessage<'a> {
356 #[cfg_attr(feature = "serde", serde(borrow))]
358 pub datatypes: Vec<SacDatatype<'a>>,
359}
360
361impl<'a> SacMessage<'a> {
362 pub fn parse(mut body: &'a [u8]) -> Result<Self> {
365 let mut datatypes = Vec::new();
366 while !body.is_empty() {
367 if body.len() < SAC_DATATYPE_HEADER {
368 return Err(Error::BufferTooShort {
369 need: SAC_DATATYPE_HEADER,
370 have: body.len(),
371 what: "SAC datatype header",
372 });
373 }
374 let datatype_id = DatatypeId::from_u8(body[0]);
375 let len = u16::from_be_bytes([body[1], body[2]]) as usize;
376 let end = SAC_DATATYPE_HEADER + len;
377 if body.len() < end {
378 return Err(Error::BufferTooShort {
379 need: end,
380 have: body.len(),
381 what: "SAC datatype value",
382 });
383 }
384 datatypes.push(SacDatatype {
385 datatype_id,
386 value: &body[SAC_DATATYPE_HEADER..end],
387 });
388 body = &body[end..];
389 }
390 Ok(Self { datatypes })
391 }
392
393 #[must_use]
395 pub fn serialized_len(&self) -> usize {
396 self.datatypes.iter().map(SacDatatype::serialized_len).sum()
397 }
398
399 pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
401 let total = self.serialized_len();
402 if buf.len() < total {
403 return Err(Error::OutputBufferTooSmall {
404 need: total,
405 have: buf.len(),
406 });
407 }
408 let mut pos = 0;
409 for dt in &self.datatypes {
410 if dt.value.len() > u16::MAX as usize {
411 return Err(Error::LengthTooLarge(dt.value.len()));
412 }
413 buf[pos] = dt.datatype_id.to_u8();
414 buf[pos + 1..pos + 3].copy_from_slice(&(dt.value.len() as u16).to_be_bytes());
415 pos += SAC_DATATYPE_HEADER;
416 buf[pos..pos + dt.value.len()].copy_from_slice(dt.value);
417 pos += dt.value.len();
418 }
419 Ok(pos)
420 }
421
422 #[must_use]
424 pub fn to_bytes(&self) -> Vec<u8> {
425 let mut buf = alloc::vec![0u8; self.serialized_len()];
426 let n = self.serialize_into(&mut buf).expect("buffer sized exactly");
427 debug_assert_eq!(n, buf.len());
428 buf
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, Eq)]
434#[cfg_attr(feature = "serde", derive(serde::Serialize))]
435#[non_exhaustive]
436pub enum ContentControlApdu {
437 CcPinReply(CcPinReply),
439 CcPinEvent(CcPinEvent),
441}
442
443impl ContentControlApdu {
444 pub fn parse(body: &[u8]) -> Result<Self> {
450 if body.len() < 3 {
451 return Err(Error::BufferTooShort {
452 need: 3,
453 have: body.len(),
454 what: "content_control apdu_tag",
455 });
456 }
457 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
458 match t {
459 tag::CC_PIN_REPLY => Ok(Self::CcPinReply(CcPinReply::parse(body)?)),
460 tag::CC_PIN_EVENT => Ok(Self::CcPinEvent(CcPinEvent::parse(body)?)),
461 _ => Err(Error::UnexpectedApduTag {
462 got: t.as_u24(),
463 expected: tag::CC_PIN_REPLY.as_u24(),
464 what: "content_control",
465 }),
466 }
467 }
468}
469
470impl Serialize for ContentControlApdu {
471 type Error = Error;
472 fn serialized_len(&self) -> usize {
473 match self {
474 Self::CcPinReply(o) => o.serialized_len(),
475 Self::CcPinEvent(o) => o.serialized_len(),
476 }
477 }
478 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
479 match self {
480 Self::CcPinReply(o) => o.serialize_into(buf),
481 Self::CcPinEvent(o) => o.serialize_into(buf),
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[test]
491 fn cc_pin_reply_bound_round_trips_and_bites() {
492 let r = CcPinReply {
493 lts_id: Some(0x07),
494 pincode_status: 0x42,
495 };
496 let bytes = r.to_bytes();
497 assert_eq!(bytes, [0x9F, 0x90, 0x14, 0x03, 0x01, 0x07, 0x42]);
499 assert_eq!(CcPinReply::parse(&bytes).unwrap(), r);
500 let other = CcPinReply {
502 lts_id: None,
503 pincode_status: 0x42,
504 };
505 let ob = other.to_bytes();
506 assert_eq!(ob, [0x9F, 0x90, 0x14, 0x03, 0x00, 0x00, 0x42]);
507 assert_ne!(bytes, ob);
508 assert_eq!(CcPinReply::parse(&ob).unwrap(), other);
509 }
510
511 #[test]
512 fn cc_pin_event_round_trips_and_bites() {
513 let e = CcPinEvent {
514 lts_id: 0x03,
515 program_number: 0x1234,
516 pincode_status: 0x05,
517 rating: 0x0A,
518 pin_event_time_utc: 0x01_0203_0405,
519 pin_event_time_centiseconds: 0x63,
520 private_data: [
521 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
522 0x1E,
523 ],
524 };
525 let bytes = e.to_bytes();
526 let expected = [
527 0x9F, 0x90, 0x15, 0x1A, 0x03, 0x12, 0x34, 0x05, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x63, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
535 0x1E, ];
537 assert_eq!(bytes, expected);
538 assert_eq!(CcPinEvent::parse(&bytes).unwrap(), e);
539 let mut other = e;
541 other.pin_event_time_utc += 1;
542 assert_ne!(bytes, other.to_bytes());
543 }
544
545 #[test]
546 fn sac_message_two_datatypes_round_trips() {
547 let msg = SacMessage {
549 datatypes: alloc::vec![
550 SacDatatype {
551 datatype_id: DatatypeId::ProgramNumber,
552 value: &[0x12, 0x34],
553 },
554 SacDatatype {
555 datatype_id: DatatypeId::LtsId,
556 value: &[0x07],
557 },
558 ],
559 };
560 let bytes = msg.to_bytes();
561 assert_eq!(
563 bytes,
564 [0x1A, 0x00, 0x02, 0x12, 0x34, 0x32, 0x00, 0x01, 0x07]
565 );
566 assert_eq!(SacMessage::parse(&bytes).unwrap(), msg);
567 let mut other = msg.clone();
569 other.datatypes[1].datatype_id = DatatypeId::Other(99);
570 assert_ne!(bytes, other.to_bytes());
571 assert_eq!(other.to_bytes()[5], 99);
572 }
573
574 #[test]
575 fn sac_message_with_opaque_license() {
576 let msg = SacMessage {
578 datatypes: alloc::vec![SacDatatype {
579 datatype_id: DatatypeId::CicamLicense,
580 value: &[0xDE, 0xAD, 0xBE, 0xEF],
581 }],
582 };
583 let bytes = msg.to_bytes();
584 assert_eq!(bytes, [0x21, 0x00, 0x04, 0xDE, 0xAD, 0xBE, 0xEF]);
585 let parsed = SacMessage::parse(&bytes).unwrap();
586 assert_eq!(parsed, msg);
587 assert_eq!(parsed.datatypes[0].datatype_id.name(), "cicam_license");
588 }
589
590 #[test]
591 fn datatype_id_and_operating_mode_labels() {
592 assert_eq!(DatatypeId::LtsId.to_u8(), 50);
593 assert_eq!(DatatypeId::from_u8(50), DatatypeId::LtsId);
594 assert_eq!(DatatypeId::from_u8(33), DatatypeId::CicamLicense);
595 assert_eq!(DatatypeId::from_u8(39), DatatypeId::PinCode);
596 assert_eq!(DatatypeId::PinCode.name(), "PINcode");
597 assert_eq!(DatatypeId::Other(7).name(), "reserved");
598 assert_eq!(OperatingMode::from_u8(0x01), OperatingMode::Timeshift);
599 assert_eq!(
600 OperatingMode::from_u8(0x02),
601 OperatingMode::UnattendedRecording
602 );
603 assert_eq!(OperatingMode::Timeshift.to_u8(), 0x01);
604 }
605
606 #[test]
607 fn dispatch_routes_each_tag() {
608 let r = CcPinReply {
609 lts_id: None,
610 pincode_status: 0,
611 }
612 .to_bytes();
613 let parsed = ContentControlApdu::parse(&r).unwrap();
614 assert!(matches!(parsed, ContentControlApdu::CcPinReply(_)));
615 assert_eq!(parsed.to_bytes(), r);
616 let cc_open = [0x9F, 0x90, 0x01, 0x00];
618 assert!(matches!(
619 ContentControlApdu::parse(&cc_open),
620 Err(Error::UnexpectedApduTag { .. })
621 ));
622 }
623}