use crate::error::{Error, Result};
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],
}
use crate::objects::application_info as v1;
impl<'a> Parse<'a> for ApplicationInfoEnq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
v1::ApplicationInfoEnq::parse(bytes)?;
Ok(Self)
}
}
impl Serialize for ApplicationInfoEnq {
type Error = Error;
fn serialized_len(&self) -> usize {
v1::ApplicationInfoEnq.serialized_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
v1::ApplicationInfoEnq.serialize_into(buf)
}
}
impl<'a> Parse<'a> for EnterMenu {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
v1::EnterMenu::parse(bytes)?;
Ok(Self)
}
}
impl Serialize for EnterMenu {
type Error = Error;
fn serialized_len(&self) -> usize {
v1::EnterMenu.serialized_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
v1::EnterMenu.serialize_into(buf)
}
}
impl<'a> Parse<'a> for ApplicationInfo<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let v = v1::ApplicationInfo::parse(bytes)?;
Ok(Self {
application_type: ApplicationTypeV2::from_u8(v.application_type.to_u8()),
application_manufacturer: v.application_manufacturer,
manufacturer_code: v.manufacturer_code,
menu_string: v.menu_string,
})
}
}
impl ApplicationInfo<'_> {
fn as_v1(&self) -> v1::ApplicationInfo<'_> {
v1::ApplicationInfo {
application_type: v1::ApplicationType::from_u8(self.application_type.to_u8()),
application_manufacturer: self.application_manufacturer,
manufacturer_code: self.manufacturer_code,
menu_string: self.menu_string,
}
}
}
impl Serialize for ApplicationInfo<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
self.as_v1().serialized_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
self.as_v1().serialize_into(buf)
}
}
crate::dispatch::declare_resource_apdus! {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ApplicationInfoV2Apdu<'a> ("application_info_v2") {
ApplicationInfoEnq(ApplicationInfoEnq) = tag::APPLICATION_INFO_ENQ,
ApplicationInfo(ApplicationInfo<'a>) = tag::APPLICATION_INFO,
EnterMenu(EnterMenu) = tag::ENTER_MENU,
}
}
#[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(_)
));
}
}