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 CAP_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
pub const CAP_RESPONSE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
pub const CAP_NOTIFICATION: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CaPipelineRequest<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub ca_specific_data: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CaPipelineResponse<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub ca_specific_data: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CaPipelineNotification<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub ca_specific_data: &'a [u8],
}
macro_rules! opaque_object {
($ty:ident, $tag:expr, $what:literal) => {
impl<'a> Parse<'a> for $ty<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, $tag, $what)?;
Ok(Self {
ca_specific_data: body,
})
}
}
impl Serialize for $ty<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(self.ca_specific_data.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = self.ca_specific_data.len();
let pos = objects::write_apdu_header($tag, body_len, buf)?;
buf[pos..pos + body_len].copy_from_slice(self.ca_specific_data);
Ok(pos + body_len)
}
}
};
}
opaque_object!(CaPipelineRequest, tag::CAP_REQUEST, "CAPipelineRequest");
opaque_object!(CaPipelineResponse, tag::CAP_RESPONSE, "CAPipelineResponse");
opaque_object!(
CaPipelineNotification,
tag::CAP_NOTIFICATION,
"CAPipelineNotification"
);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum CaPipelineApdu<'a> {
Request(CaPipelineRequest<'a>),
Response(CaPipelineResponse<'a>),
Notification(CaPipelineNotification<'a>),
}
impl<'a> CaPipelineApdu<'a> {
pub fn parse(body: &'a [u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "ca_pipeline apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::CAP_REQUEST => Ok(Self::Request(CaPipelineRequest::parse(body)?)),
tag::CAP_RESPONSE => Ok(Self::Response(CaPipelineResponse::parse(body)?)),
tag::CAP_NOTIFICATION => Ok(Self::Notification(CaPipelineNotification::parse(body)?)),
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::CAP_REQUEST.as_u24(),
what: "ca_pipeline",
}),
}
}
}
impl Serialize for CaPipelineApdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::Request(o) => o.serialized_len(),
Self::Response(o) => o.serialized_len(),
Self::Notification(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::Request(o) => o.serialize_into(buf),
Self::Response(o) => o.serialize_into(buf),
Self::Notification(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_round_trips_and_bites() {
let req = CaPipelineRequest {
ca_specific_data: &[0xAA, 0xBB, 0xCC],
};
let bytes = req.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x03, 0xAA, 0xBB, 0xCC]);
assert_eq!(CaPipelineRequest::parse(&bytes).unwrap(), req);
let other = CaPipelineRequest {
ca_specific_data: &[0xAA, 0xBB, 0xCD],
};
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn response_and_notification_round_trip() {
let resp = CaPipelineResponse {
ca_specific_data: &[0x01],
};
let bytes = resp.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0x01]);
assert_eq!(CaPipelineResponse::parse(&bytes).unwrap(), resp);
let note = CaPipelineNotification {
ca_specific_data: &[],
};
let bytes = note.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x02, 0x00]);
assert_eq!(CaPipelineNotification::parse(&bytes).unwrap(), note);
}
#[test]
fn dispatch_routes_each_tag() {
let req = CaPipelineRequest {
ca_specific_data: &[0x10],
}
.to_bytes();
assert!(matches!(
CaPipelineApdu::parse(&req).unwrap(),
CaPipelineApdu::Request(_)
));
let resp = CaPipelineResponse {
ca_specific_data: &[0x20],
}
.to_bytes();
assert!(matches!(
CaPipelineApdu::parse(&resp).unwrap(),
CaPipelineApdu::Response(_)
));
let note = CaPipelineNotification {
ca_specific_data: &[0x30],
}
.to_bytes();
let parsed = CaPipelineApdu::parse(¬e).unwrap();
assert!(matches!(parsed, CaPipelineApdu::Notification(_)));
assert_eq!(parsed.to_bytes(), note);
}
#[test]
fn unexpected_tag_errors() {
let bad = [0x9F, 0x80, 0x09, 0x00];
assert!(matches!(
CaPipelineApdu::parse(&bad),
Err(Error::UnexpectedApduTag { .. })
));
}
}