use crate::error::{Error, Result};
use crate::objects;
use crate::tag::ApduTag;
use broadcast_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const FILE_SYSTEM_OFFER: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x00);
pub const FILE_SYSTEM_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x01);
pub const FILE_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x02);
pub const FILE_ACKNOWLEDGE: ApduTag = ApduTag::from_bytes(0x9F, 0x94, 0x03);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum AckCode {
Ok,
UnknownDomainIdentifier,
Reserved(u8),
}
impl AckCode {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x01 => Self::Ok,
0x02 => Self::UnknownDomainIdentifier,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Ok => 0x01,
Self::UnknownDomainIdentifier => 0x02,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Ok => "ok",
Self::UnknownDomainIdentifier => "unknown_domain_identifier",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(AckCode, Reserved);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FileSystemOffer<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub domain_identifier: &'a [u8],
}
const OFFER_PREFIX: usize = 1;
impl<'a> Parse<'a> for FileSystemOffer<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::FILE_SYSTEM_OFFER, "FileSystemOffer")?;
if body.len() < OFFER_PREFIX {
return Err(Error::BufferTooShort {
need: OFFER_PREFIX,
have: body.len(),
what: "FileSystemOffer",
});
}
let len = body[0] as usize;
let end = OFFER_PREFIX + len;
if body.len() < end {
return Err(Error::LengthMismatch {
what: "FileSystemOffer DomainIdentifier",
declared: len,
actual: body.len().saturating_sub(OFFER_PREFIX),
});
}
Ok(Self {
domain_identifier: &body[OFFER_PREFIX..end],
})
}
}
impl Serialize for FileSystemOffer<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(OFFER_PREFIX + self.domain_identifier.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = OFFER_PREFIX + self.domain_identifier.len();
let pos = objects::write_apdu_header(tag::FILE_SYSTEM_OFFER, body_len, buf)?;
buf[pos] = self.domain_identifier.len() as u8;
buf[pos + OFFER_PREFIX..pos + body_len].copy_from_slice(self.domain_identifier);
Ok(pos + body_len)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FileSystemAck {
pub ack_code: AckCode,
}
const ACK_BODY: usize = 1;
impl<'a> Parse<'a> for FileSystemAck {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::FILE_SYSTEM_ACK, "FileSystemAck")?;
if body.len() < ACK_BODY {
return Err(Error::BufferTooShort {
need: ACK_BODY,
have: body.len(),
what: "FileSystemAck",
});
}
Ok(Self {
ack_code: AckCode::from_u8(body[0]),
})
}
}
impl Serialize for FileSystemAck {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(ACK_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::FILE_SYSTEM_ACK, ACK_BODY, buf)?;
buf[pos] = self.ack_code.to_u8();
Ok(pos + ACK_BODY)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FileRequest<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub body: &'a [u8],
}
impl<'a> Parse<'a> for FileRequest<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::FILE_REQUEST, "FileRequest")?;
Ok(Self { body })
}
}
impl Serialize for FileRequest<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(self.body.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::FILE_REQUEST, self.body.len(), buf)?;
buf[pos..pos + self.body.len()].copy_from_slice(self.body);
Ok(pos + self.body.len())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FileAcknowledge<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub body: &'a [u8],
}
impl<'a> Parse<'a> for FileAcknowledge<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::FILE_ACKNOWLEDGE, "FileAcknowledge")?;
Ok(Self { body })
}
}
impl Serialize for FileAcknowledge<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(self.body.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::FILE_ACKNOWLEDGE, self.body.len(), buf)?;
buf[pos..pos + self.body.len()].copy_from_slice(self.body);
Ok(pos + self.body.len())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum FileRetrievalApdu<'a> {
FileSystemOffer(#[cfg_attr(feature = "serde", serde(borrow))] FileSystemOffer<'a>),
FileSystemAck(FileSystemAck),
FileRequest(#[cfg_attr(feature = "serde", serde(borrow))] FileRequest<'a>),
FileAcknowledge(#[cfg_attr(feature = "serde", serde(borrow))] FileAcknowledge<'a>),
}
impl<'a> FileRetrievalApdu<'a> {
pub fn parse(body: &'a [u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "file_retrieval apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::FILE_SYSTEM_OFFER => Ok(Self::FileSystemOffer(FileSystemOffer::parse(body)?)),
tag::FILE_SYSTEM_ACK => Ok(Self::FileSystemAck(FileSystemAck::parse(body)?)),
tag::FILE_REQUEST => Ok(Self::FileRequest(FileRequest::parse(body)?)),
tag::FILE_ACKNOWLEDGE => Ok(Self::FileAcknowledge(FileAcknowledge::parse(body)?)),
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::FILE_SYSTEM_OFFER.as_u24(),
what: "file_retrieval",
}),
}
}
}
impl Serialize for FileRetrievalApdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::FileSystemOffer(o) => o.serialized_len(),
Self::FileSystemAck(o) => o.serialized_len(),
Self::FileRequest(o) => o.serialized_len(),
Self::FileAcknowledge(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::FileSystemOffer(o) => o.serialize_into(buf),
Self::FileSystemAck(o) => o.serialize_into(buf),
Self::FileRequest(o) => o.serialize_into(buf),
Self::FileAcknowledge(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn offer_round_trips_and_bites() {
let o = FileSystemOffer {
domain_identifier: &[0x61, 0x62],
};
let bytes = o.to_bytes();
assert_eq!(bytes, [0x9F, 0x94, 0x00, 0x03, 0x02, 0x61, 0x62]);
assert_eq!(FileSystemOffer::parse(&bytes).unwrap(), o);
let other = FileSystemOffer {
domain_identifier: &[0x61, 0x63],
};
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn offer_empty_domain() {
let o = FileSystemOffer {
domain_identifier: &[],
};
let bytes = o.to_bytes();
assert_eq!(bytes, [0x9F, 0x94, 0x00, 0x01, 0x00]);
assert_eq!(FileSystemOffer::parse(&bytes).unwrap(), o);
}
#[test]
fn ack_round_trips() {
let a = FileSystemAck {
ack_code: AckCode::UnknownDomainIdentifier,
};
let bytes = a.to_bytes();
assert_eq!(bytes, [0x9F, 0x94, 0x01, 0x01, 0x02]);
assert_eq!(FileSystemAck::parse(&bytes).unwrap(), a);
let ok = FileSystemAck {
ack_code: AckCode::Ok,
};
assert_eq!(ok.to_bytes()[4], 0x01);
}
#[test]
fn file_request_opaque_body_round_trips() {
let r = FileRequest {
body: &[0x01, 0x02, 0x03],
};
let bytes = r.to_bytes();
assert_eq!(bytes, [0x9F, 0x94, 0x02, 0x03, 0x01, 0x02, 0x03]);
assert_eq!(FileRequest::parse(&bytes).unwrap(), r);
}
#[test]
fn file_acknowledge_opaque_body_round_trips() {
let a = FileAcknowledge {
body: &[0xAA, 0xBB],
};
let bytes = a.to_bytes();
assert_eq!(bytes, [0x9F, 0x94, 0x03, 0x02, 0xAA, 0xBB]);
assert_eq!(FileAcknowledge::parse(&bytes).unwrap(), a);
}
#[test]
fn dispatch_routes_each_tag() {
let cases: alloc::vec::Vec<alloc::vec::Vec<u8>> = alloc::vec![
FileSystemOffer {
domain_identifier: &[0x61]
}
.to_bytes(),
FileSystemAck {
ack_code: AckCode::Ok
}
.to_bytes(),
FileRequest { body: &[0x00] }.to_bytes(),
FileAcknowledge { body: &[0x00] }.to_bytes(),
];
for c in &cases {
assert_eq!(&FileRetrievalApdu::parse(c).unwrap().to_bytes(), c);
}
assert!(matches!(
FileRetrievalApdu::parse(&[0x9F, 0x94, 0x7E, 0x00]),
Err(Error::UnexpectedApduTag { .. })
));
}
}