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 APPLICATION_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x20);
pub const APPLICATION_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x21);
pub const ENTER_MENU: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x22);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ApplicationTypeV2 {
ConditionalAccess,
ElectronicProgrammeGuide,
SoftwareUpgrade,
NetworkInterface,
AccessibilityAids,
Unclassified,
Reserved(u8),
}
impl ApplicationTypeV2 {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x01 => Self::ConditionalAccess,
0x02 => Self::ElectronicProgrammeGuide,
0x03 => Self::SoftwareUpgrade,
0x04 => Self::NetworkInterface,
0x05 => Self::AccessibilityAids,
0x06 => Self::Unclassified,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::ConditionalAccess => 0x01,
Self::ElectronicProgrammeGuide => 0x02,
Self::SoftwareUpgrade => 0x03,
Self::NetworkInterface => 0x04,
Self::AccessibilityAids => 0x05,
Self::Unclassified => 0x06,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::ConditionalAccess => "Conditional_Access",
Self::ElectronicProgrammeGuide => "Electronic_Programme_Guide",
Self::SoftwareUpgrade => "Software_upgrade",
Self::NetworkInterface => "Network_interface",
Self::AccessibilityAids => "Accessibility_aids",
Self::Unclassified => "Unclassified",
Self::Reserved(_) => "reserved",
}
}
#[must_use]
pub fn effective(self) -> Self {
match self {
Self::Reserved(_) => Self::Unclassified,
known => known,
}
}
}
broadcast_common::impl_spec_display!(ApplicationTypeV2, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ApplicationInfoEnq;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EnterMenu;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ApplicationInfo<'a> {
pub application_type: ApplicationTypeV2,
pub application_manufacturer: u16,
pub manufacturer_code: u16,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub menu_string: &'a [u8],
}
impl<'a> Parse<'a> for ApplicationInfoEnq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
objects::parse_empty_apdu(bytes, tag::APPLICATION_INFO_ENQ, "application_info_enq")?;
Ok(Self)
}
}
impl Serialize for ApplicationInfoEnq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::empty_apdu_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::serialize_empty_apdu(tag::APPLICATION_INFO_ENQ, buf)
}
}
impl<'a> Parse<'a> for EnterMenu {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
objects::parse_empty_apdu(bytes, tag::ENTER_MENU, "enter_menu")?;
Ok(Self)
}
}
impl Serialize for EnterMenu {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::empty_apdu_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::serialize_empty_apdu(tag::ENTER_MENU, buf)
}
}
const APP_INFO_PREFIX: usize = 6;
impl<'a> Parse<'a> for ApplicationInfo<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::APPLICATION_INFO, "application_info")?;
if body.len() < APP_INFO_PREFIX {
return Err(Error::BufferTooShort {
need: APP_INFO_PREFIX,
have: body.len(),
what: "application_info",
});
}
let menu_len = body[5] as usize;
let menu_end = APP_INFO_PREFIX + menu_len;
if body.len() < menu_end {
return Err(Error::LengthMismatch {
what: "application_info menu_string",
declared: menu_len,
actual: body.len() - APP_INFO_PREFIX,
});
}
Ok(Self {
application_type: ApplicationTypeV2::from_u8(body[0]),
application_manufacturer: u16::from_be_bytes([body[1], body[2]]),
manufacturer_code: u16::from_be_bytes([body[3], body[4]]),
menu_string: &body[APP_INFO_PREFIX..menu_end],
})
}
}
impl Serialize for ApplicationInfo<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(APP_INFO_PREFIX + self.menu_string.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if self.menu_string.len() > u8::MAX as usize {
return Err(Error::InvalidObject {
what: "application_info",
reason: "menu_string longer than 255 bytes",
});
}
let body_len = APP_INFO_PREFIX + self.menu_string.len();
let mut pos = objects::write_apdu_header(tag::APPLICATION_INFO, body_len, buf)?;
buf[pos] = self.application_type.to_u8();
buf[pos + 1..pos + 3].copy_from_slice(&self.application_manufacturer.to_be_bytes());
buf[pos + 3..pos + 5].copy_from_slice(&self.manufacturer_code.to_be_bytes());
buf[pos + 5] = self.menu_string.len() as u8;
pos += APP_INFO_PREFIX;
buf[pos..pos + self.menu_string.len()].copy_from_slice(self.menu_string);
Ok(pos + self.menu_string.len())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ApplicationInfoV2Apdu<'a> {
ApplicationInfoEnq(ApplicationInfoEnq),
ApplicationInfo(ApplicationInfo<'a>),
EnterMenu(EnterMenu),
}
impl<'a> ApplicationInfoV2Apdu<'a> {
pub fn parse(body: &'a [u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "application_info_v2 apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::APPLICATION_INFO_ENQ => {
Ok(Self::ApplicationInfoEnq(ApplicationInfoEnq::parse(body)?))
}
tag::APPLICATION_INFO => Ok(Self::ApplicationInfo(ApplicationInfo::parse(body)?)),
tag::ENTER_MENU => Ok(Self::EnterMenu(EnterMenu::parse(body)?)),
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::APPLICATION_INFO.as_u24(),
what: "application_info_v2",
}),
}
}
}
impl Serialize for ApplicationInfoV2Apdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::ApplicationInfoEnq(o) => o.serialized_len(),
Self::ApplicationInfo(o) => o.serialized_len(),
Self::EnterMenu(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::ApplicationInfoEnq(o) => o.serialize_into(buf),
Self::ApplicationInfo(o) => o.serialize_into(buf),
Self::EnterMenu(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn enq_and_enter_menu_round_trip() {
assert_eq!(ApplicationInfoEnq.to_bytes(), [0x9F, 0x80, 0x20, 0x00]);
assert_eq!(EnterMenu.to_bytes(), [0x9F, 0x80, 0x22, 0x00]);
assert_eq!(
ApplicationInfoEnq::parse(&[0x9F, 0x80, 0x20, 0x00]).unwrap(),
ApplicationInfoEnq
);
}
#[test]
fn application_info_round_trips_and_bites() {
let info = ApplicationInfo {
application_type: ApplicationTypeV2::AccessibilityAids,
application_manufacturer: 0x1234,
manufacturer_code: 0x5678,
menu_string: b"Audio",
};
let bytes = info.to_bytes();
assert_eq!(
bytes,
[
0x9F, 0x80, 0x21, 0x0B, 0x05, 0x12, 0x34, 0x56, 0x78, 0x05, b'A', b'u', b'd', b'i',
b'o'
]
);
assert_eq!(ApplicationInfo::parse(&bytes).unwrap(), info);
assert_eq!(info.application_type.name(), "Accessibility_aids");
let mut other = info.clone();
other.application_type = ApplicationTypeV2::Unclassified;
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn unrecognized_type_treated_as_unclassified() {
let t = ApplicationTypeV2::from_u8(0x7F);
assert_eq!(t, ApplicationTypeV2::Reserved(0x7F));
assert_eq!(t.effective(), ApplicationTypeV2::Unclassified);
assert_eq!(t.to_u8(), 0x7F);
}
#[test]
fn dispatch_routes_each_tag() {
let info = ApplicationInfo {
application_type: ApplicationTypeV2::SoftwareUpgrade,
application_manufacturer: 0,
manufacturer_code: 0,
menu_string: b"",
}
.to_bytes();
assert!(matches!(
ApplicationInfoV2Apdu::parse(&info).unwrap(),
ApplicationInfoV2Apdu::ApplicationInfo(_)
));
}
}