dvb_ci/objects/
date_time.rs1use crate::error::{Error, Result};
13use crate::tag::{self, ApduTag};
14use crate::traits::ApduDef;
15use dvb_common::{Parse, Serialize};
16
17pub const UTC_TIME_LEN: usize = 5;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23pub struct DateTimeEnq {
24 pub response_interval: u8,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32pub struct DateTime {
33 pub utc_time: [u8; UTC_TIME_LEN],
35 pub local_offset: Option<i16>,
38}
39
40impl<'a> Parse<'a> for DateTimeEnq {
41 type Error = Error;
42 fn parse(bytes: &'a [u8]) -> Result<Self> {
43 let body = super::parse_apdu_header(bytes, tag::DATE_TIME_ENQ, "date_time_enq")?;
44 let &[response_interval] = body else {
45 return Err(Error::InvalidObject {
46 what: "date_time_enq",
47 reason: "body must be exactly 1 byte (response_interval)",
48 });
49 };
50 Ok(Self { response_interval })
51 }
52}
53
54impl Serialize for DateTimeEnq {
55 type Error = Error;
56 fn serialized_len(&self) -> usize {
57 super::apdu_len(1)
58 }
59 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
60 let pos = super::write_apdu_header(tag::DATE_TIME_ENQ, 1, buf)?;
61 buf[pos] = self.response_interval;
62 Ok(pos + 1)
63 }
64}
65
66impl<'a> ApduDef<'a> for DateTimeEnq {
67 const TAG: ApduTag = tag::DATE_TIME_ENQ;
68 const NAME: &'static str = "DATE_TIME_ENQ";
69}
70
71impl<'a> Parse<'a> for DateTime {
72 type Error = Error;
73 fn parse(bytes: &'a [u8]) -> Result<Self> {
74 let body = super::parse_apdu_header(bytes, tag::DATE_TIME, "date_time")?;
75 let utc_time: [u8; UTC_TIME_LEN] = match body.len() {
76 UTC_TIME_LEN | 7 => body[..UTC_TIME_LEN].try_into().unwrap(),
77 _ => {
78 return Err(Error::InvalidObject {
79 what: "date_time",
80 reason: "body must be 5 or 7 bytes",
81 })
82 }
83 };
84 let local_offset = if body.len() == 7 {
85 Some(i16::from_be_bytes([body[5], body[6]]))
86 } else {
87 None
88 };
89 Ok(Self {
90 utc_time,
91 local_offset,
92 })
93 }
94}
95
96impl Serialize for DateTime {
97 type Error = Error;
98 fn serialized_len(&self) -> usize {
99 let body = if self.local_offset.is_some() { 7 } else { 5 };
100 super::apdu_len(body)
101 }
102 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
103 let body_len = if self.local_offset.is_some() { 7 } else { 5 };
104 let mut pos = super::write_apdu_header(tag::DATE_TIME, body_len, buf)?;
105 buf[pos..pos + UTC_TIME_LEN].copy_from_slice(&self.utc_time);
106 pos += UTC_TIME_LEN;
107 if let Some(off) = self.local_offset {
108 buf[pos..pos + 2].copy_from_slice(&off.to_be_bytes());
109 pos += 2;
110 }
111 Ok(pos)
112 }
113}
114
115impl<'a> ApduDef<'a> for DateTime {
116 const TAG: ApduTag = tag::DATE_TIME;
117 const NAME: &'static str = "DATE_TIME";
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn enq_round_trip() {
126 let e = DateTimeEnq {
127 response_interval: 10,
128 };
129 let bytes = e.to_bytes();
130 assert_eq!(bytes, [0x9F, 0x84, 0x40, 0x01, 0x0A]);
131 assert_eq!(DateTimeEnq::parse(&bytes).unwrap(), e);
132 }
133
134 #[test]
135 fn date_time_no_offset_round_trip() {
136 let dt = DateTime {
137 utc_time: [0xC0, 0x79, 0x12, 0x34, 0x56],
138 local_offset: None,
139 };
140 let bytes = dt.to_bytes();
141 assert_eq!(&bytes[..4], &[0x9F, 0x84, 0x41, 0x05]); assert_eq!(DateTime::parse(&bytes).unwrap(), dt);
143 }
144
145 #[test]
146 fn date_time_with_offset_round_trip() {
147 let dt = DateTime {
148 utc_time: [0xC0, 0x79, 0x12, 0x34, 0x56],
149 local_offset: Some(-60),
150 };
151 let bytes = dt.to_bytes();
152 assert_eq!(&bytes[..4], &[0x9F, 0x84, 0x41, 0x07]); let parsed = DateTime::parse(&bytes).unwrap();
154 assert_eq!(parsed, dt);
155 assert_eq!(parsed.local_offset, Some(-60));
156 }
157
158 #[test]
159 fn mutating_offset_changes_bytes_and_length() {
160 let dt = DateTime {
161 utc_time: [0; 5],
162 local_offset: None,
163 };
164 let a = dt.to_bytes();
165 let mut other = dt;
166 other.local_offset = Some(120);
167 let b = other.to_bytes();
168 assert_ne!(a, b);
169 assert_eq!(a.len(), 9);
170 assert_eq!(b.len(), 11);
171 }
172}