1use crate::error::{Error, Result};
24use crate::objects;
25use crate::tag::ApduTag;
26use broadcast_common::{Parse, Serialize};
27
28pub mod tag {
30 use crate::tag::ApduTag;
31 pub const FILE_SYSTEM_OFFER: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x00);
33 pub const FILE_SYSTEM_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x01);
35 pub const FILE_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x02);
37 pub const FILE_ACKNOWLEDGE: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x03);
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize))]
46#[non_exhaustive]
47pub enum AckCode {
48 Ok,
50 UnknownDomainIdentifier,
52 Reserved(u8),
54}
55impl AckCode {
56 #[must_use]
58 pub fn from_u8(v: u8) -> Self {
59 match v {
60 0x01 => Self::Ok,
61 0x02 => Self::UnknownDomainIdentifier,
62 other => Self::Reserved(other),
63 }
64 }
65 #[must_use]
67 pub const fn to_u8(self) -> u8 {
68 match self {
69 Self::Ok => 0x01,
70 Self::UnknownDomainIdentifier => 0x02,
71 Self::Reserved(v) => v,
72 }
73 }
74 #[must_use]
76 pub fn name(&self) -> &'static str {
77 match self {
78 Self::Ok => "ok",
79 Self::UnknownDomainIdentifier => "unknown_domain_identifier",
80 Self::Reserved(_) => "reserved",
81 }
82 }
83}
84broadcast_common::impl_spec_display!(AckCode, Reserved);
85
86#[derive(Debug, Clone, PartialEq, Eq)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize))]
94pub struct FileSystemOffer<'a> {
95 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
98 pub domain_identifier: &'a [u8],
99}
100
101const OFFER_PREFIX: usize = 1;
103
104impl<'a> Parse<'a> for FileSystemOffer<'a> {
105 type Error = Error;
106 fn parse(bytes: &'a [u8]) -> Result<Self> {
107 let body = objects::parse_apdu_header(bytes, tag::FILE_SYSTEM_OFFER, "FileSystemOffer")?;
108 if body.len() < OFFER_PREFIX {
109 return Err(Error::BufferTooShort {
110 need: OFFER_PREFIX,
111 have: body.len(),
112 what: "FileSystemOffer",
113 });
114 }
115 let len = body[0] as usize;
116 let end = OFFER_PREFIX + len;
117 if body.len() < end {
118 return Err(Error::LengthMismatch {
119 what: "FileSystemOffer DomainIdentifier",
120 declared: len,
121 actual: body.len().saturating_sub(OFFER_PREFIX),
122 });
123 }
124 Ok(Self {
125 domain_identifier: &body[OFFER_PREFIX..end],
126 })
127 }
128}
129impl Serialize for FileSystemOffer<'_> {
130 type Error = Error;
131 fn serialized_len(&self) -> usize {
132 objects::apdu_len(OFFER_PREFIX + self.domain_identifier.len())
133 }
134 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
135 let body_len = OFFER_PREFIX + self.domain_identifier.len();
136 let pos = objects::write_apdu_header(tag::FILE_SYSTEM_OFFER, body_len, buf)?;
137 buf[pos] = self.domain_identifier.len() as u8;
138 buf[pos + OFFER_PREFIX..pos + body_len].copy_from_slice(self.domain_identifier);
139 Ok(pos + body_len)
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize))]
150pub struct FileSystemAck {
151 pub ack_code: AckCode,
153}
154
155const ACK_BODY: usize = 1;
156
157impl<'a> Parse<'a> for FileSystemAck {
158 type Error = Error;
159 fn parse(bytes: &'a [u8]) -> Result<Self> {
160 let body = objects::parse_apdu_header(bytes, tag::FILE_SYSTEM_ACK, "FileSystemAck")?;
161 if body.len() < ACK_BODY {
162 return Err(Error::BufferTooShort {
163 need: ACK_BODY,
164 have: body.len(),
165 what: "FileSystemAck",
166 });
167 }
168 Ok(Self {
169 ack_code: AckCode::from_u8(body[0]),
170 })
171 }
172}
173impl Serialize for FileSystemAck {
174 type Error = Error;
175 fn serialized_len(&self) -> usize {
176 objects::apdu_len(ACK_BODY)
177 }
178 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
179 let pos = objects::write_apdu_header(tag::FILE_SYSTEM_ACK, ACK_BODY, buf)?;
180 buf[pos] = self.ack_code.to_u8();
181 Ok(pos + ACK_BODY)
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize))]
195pub struct FileRequest<'a> {
196 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
198 pub body: &'a [u8],
199}
200
201impl<'a> Parse<'a> for FileRequest<'a> {
202 type Error = Error;
203 fn parse(bytes: &'a [u8]) -> Result<Self> {
204 let body = objects::parse_apdu_header(bytes, tag::FILE_REQUEST, "FileRequest")?;
205 Ok(Self { body })
206 }
207}
208impl Serialize for FileRequest<'_> {
209 type Error = Error;
210 fn serialized_len(&self) -> usize {
211 objects::apdu_len(self.body.len())
212 }
213 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
214 let pos = objects::write_apdu_header(tag::FILE_REQUEST, self.body.len(), buf)?;
215 buf[pos..pos + self.body.len()].copy_from_slice(self.body);
216 Ok(pos + self.body.len())
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq)]
225#[cfg_attr(feature = "serde", derive(serde::Serialize))]
226pub struct FileAcknowledge<'a> {
227 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
229 pub body: &'a [u8],
230}
231
232impl<'a> Parse<'a> for FileAcknowledge<'a> {
233 type Error = Error;
234 fn parse(bytes: &'a [u8]) -> Result<Self> {
235 let body = objects::parse_apdu_header(bytes, tag::FILE_ACKNOWLEDGE, "FileAcknowledge")?;
236 Ok(Self { body })
237 }
238}
239impl Serialize for FileAcknowledge<'_> {
240 type Error = Error;
241 fn serialized_len(&self) -> usize {
242 objects::apdu_len(self.body.len())
243 }
244 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
245 let pos = objects::write_apdu_header(tag::FILE_ACKNOWLEDGE, self.body.len(), buf)?;
246 buf[pos..pos + self.body.len()].copy_from_slice(self.body);
247 Ok(pos + self.body.len())
248 }
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize))]
258#[non_exhaustive]
259pub enum FileRetrievalApdu<'a> {
260 FileSystemOffer(#[cfg_attr(feature = "serde", serde(borrow))] FileSystemOffer<'a>),
262 FileSystemAck(FileSystemAck),
264 FileRequest(#[cfg_attr(feature = "serde", serde(borrow))] FileRequest<'a>),
266 FileAcknowledge(#[cfg_attr(feature = "serde", serde(borrow))] FileAcknowledge<'a>),
268}
269
270impl<'a> FileRetrievalApdu<'a> {
271 pub fn parse(body: &'a [u8]) -> Result<Self> {
273 if body.len() < 3 {
274 return Err(Error::BufferTooShort {
275 need: 3,
276 have: body.len(),
277 what: "file_retrieval apdu_tag",
278 });
279 }
280 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
281 match t {
282 tag::FILE_SYSTEM_OFFER => Ok(Self::FileSystemOffer(FileSystemOffer::parse(body)?)),
283 tag::FILE_SYSTEM_ACK => Ok(Self::FileSystemAck(FileSystemAck::parse(body)?)),
284 tag::FILE_REQUEST => Ok(Self::FileRequest(FileRequest::parse(body)?)),
285 tag::FILE_ACKNOWLEDGE => Ok(Self::FileAcknowledge(FileAcknowledge::parse(body)?)),
286 _ => Err(Error::UnexpectedApduTag {
287 got: t.as_u24(),
288 expected: tag::FILE_SYSTEM_OFFER.as_u24(),
289 what: "file_retrieval",
290 }),
291 }
292 }
293}
294
295impl Serialize for FileRetrievalApdu<'_> {
296 type Error = Error;
297 fn serialized_len(&self) -> usize {
298 match self {
299 Self::FileSystemOffer(o) => o.serialized_len(),
300 Self::FileSystemAck(o) => o.serialized_len(),
301 Self::FileRequest(o) => o.serialized_len(),
302 Self::FileAcknowledge(o) => o.serialized_len(),
303 }
304 }
305 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
306 match self {
307 Self::FileSystemOffer(o) => o.serialize_into(buf),
308 Self::FileSystemAck(o) => o.serialize_into(buf),
309 Self::FileRequest(o) => o.serialize_into(buf),
310 Self::FileAcknowledge(o) => o.serialize_into(buf),
311 }
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn offer_round_trips_and_bites() {
321 let o = FileSystemOffer {
323 domain_identifier: &[0x61, 0x62],
324 };
325 let bytes = o.to_bytes();
326 assert_eq!(bytes, [0x9F, 0x94, 0x00, 0x03, 0x02, 0x61, 0x62]);
328 assert_eq!(FileSystemOffer::parse(&bytes).unwrap(), o);
329 let other = FileSystemOffer {
330 domain_identifier: &[0x61, 0x63],
331 };
332 assert_ne!(bytes, other.to_bytes());
333 }
334
335 #[test]
336 fn offer_empty_domain() {
337 let o = FileSystemOffer {
338 domain_identifier: &[],
339 };
340 let bytes = o.to_bytes();
341 assert_eq!(bytes, [0x9F, 0x94, 0x00, 0x01, 0x00]);
342 assert_eq!(FileSystemOffer::parse(&bytes).unwrap(), o);
343 }
344
345 #[test]
346 fn ack_round_trips() {
347 let a = FileSystemAck {
348 ack_code: AckCode::UnknownDomainIdentifier,
349 };
350 let bytes = a.to_bytes();
351 assert_eq!(bytes, [0x9F, 0x94, 0x01, 0x01, 0x02]);
352 assert_eq!(FileSystemAck::parse(&bytes).unwrap(), a);
353 let ok = FileSystemAck {
354 ack_code: AckCode::Ok,
355 };
356 assert_eq!(ok.to_bytes()[4], 0x01);
357 }
358
359 #[test]
360 fn file_request_opaque_body_round_trips() {
361 let r = FileRequest {
362 body: &[0x01, 0x02, 0x03],
363 };
364 let bytes = r.to_bytes();
365 assert_eq!(bytes, [0x9F, 0x94, 0x02, 0x03, 0x01, 0x02, 0x03]);
366 assert_eq!(FileRequest::parse(&bytes).unwrap(), r);
367 }
368
369 #[test]
370 fn file_acknowledge_opaque_body_round_trips() {
371 let a = FileAcknowledge {
372 body: &[0xAA, 0xBB],
373 };
374 let bytes = a.to_bytes();
375 assert_eq!(bytes, [0x9F, 0x94, 0x03, 0x02, 0xAA, 0xBB]);
376 assert_eq!(FileAcknowledge::parse(&bytes).unwrap(), a);
377 }
378
379 #[test]
380 fn dispatch_routes_each_tag() {
381 let cases: alloc::vec::Vec<alloc::vec::Vec<u8>> = alloc::vec![
382 FileSystemOffer {
383 domain_identifier: &[0x61]
384 }
385 .to_bytes(),
386 FileSystemAck {
387 ack_code: AckCode::Ok
388 }
389 .to_bytes(),
390 FileRequest { body: &[0x00] }.to_bytes(),
391 FileAcknowledge { body: &[0x00] }.to_bytes(),
392 ];
393 for c in &cases {
394 assert_eq!(&FileRetrievalApdu::parse(c).unwrap().to_bytes(), c);
395 }
396 assert!(matches!(
397 FileRetrievalApdu::parse(&[0x9F, 0x94, 0x7E, 0x00]),
398 Err(Error::UnexpectedApduTag { .. })
399 ));
400 }
401}