use crate::error::{Error, Result};
use crate::resource::ResourceId;
pub mod application_info_v2;
pub mod application_mmi;
pub mod broadcast_service_gateway;
pub mod ca_pipeline;
pub mod copy_protection;
pub mod event_manager;
pub mod power_manager;
pub mod resource_manager_v2;
pub mod service_gateway;
pub mod software_download;
pub mod status_query;
pub mod stream_input;
pub const RESOURCE_MANAGER_V2: ResourceId = ResourceId(0x0001_0042);
pub const APPLICATION_INFO_V2: ResourceId = ResourceId(0x0002_0042);
pub const POWER_MANAGER: ResourceId = ResourceId(0x0022_0041);
pub const APPLICATION_MMI: ResourceId = ResourceId(0x0041_0041);
pub const DOWNLOAD: ResourceId = ResourceId(0x0005_1041);
pub const STREAM_INPUT_TEMPLATE: ResourceId = ResourceId(0x0080_1001);
pub const BROADCAST_SERVICE_GATEWAY_TEMPLATE: ResourceId = ResourceId(0x0081_1001);
pub const STATUS_QUERY_TEMPLATE: ResourceId = ResourceId(0x0021_1001);
pub const EVENT_MANAGER_TEMPLATE: ResourceId = ResourceId(0x0023_1001);
pub const COPY_PROTECTION_TEMPLATE: ResourceId = ResourceId(0x0004_1001);
pub const CA_PIPELINE_TEMPLATE: ResourceId = ResourceId(0x0006_1001);
pub const MODULE_ID_MASK: u32 = 0xFFFF_F03F;
#[must_use]
pub const fn module_id(id: ResourceId) -> u8 {
((id.0 >> 6) & 0x3F) as u8
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum CiExtResource {
ResourceManagerV2,
ApplicationInfoV2,
PowerManager,
ApplicationMmi,
Download,
StreamInput(u8),
BroadcastServiceGateway(u8),
StatusQuery(u8),
EventManager(u8),
CopyProtection(u8),
CaPipeline(u8),
}
impl CiExtResource {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::ResourceManagerV2 => "resource_manager_v2",
Self::ApplicationInfoV2 => "application_information_v2",
Self::PowerManager => "power_manager",
Self::ApplicationMmi => "application_mmi",
Self::Download => "download",
Self::StreamInput(_) => "stream_input",
Self::BroadcastServiceGateway(_) => "broadcast_service_gateway",
Self::StatusQuery(_) => "status_query",
Self::EventManager(_) => "event_manager",
Self::CopyProtection(_) => "copy_protection",
Self::CaPipeline(_) => "ca_pipeline",
}
}
}
impl core::fmt::Display for CiExtResource {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::StreamInput(ii)
| Self::BroadcastServiceGateway(ii)
| Self::StatusQuery(ii)
| Self::EventManager(ii)
| Self::CopyProtection(ii)
| Self::CaPipeline(ii) => write!(f, "{}(module_id={ii})", self.name()),
other => f.write_str(other.name()),
}
}
}
#[must_use]
pub fn classify(id: ResourceId) -> Option<CiExtResource> {
match id {
RESOURCE_MANAGER_V2 => return Some(CiExtResource::ResourceManagerV2),
APPLICATION_INFO_V2 => return Some(CiExtResource::ApplicationInfoV2),
POWER_MANAGER => return Some(CiExtResource::PowerManager),
APPLICATION_MMI => return Some(CiExtResource::ApplicationMmi),
DOWNLOAD => return Some(CiExtResource::Download),
_ => {}
}
let ii = module_id(id);
match ResourceId(id.0 & MODULE_ID_MASK) {
STREAM_INPUT_TEMPLATE => Some(CiExtResource::StreamInput(ii)),
BROADCAST_SERVICE_GATEWAY_TEMPLATE => Some(CiExtResource::BroadcastServiceGateway(ii)),
STATUS_QUERY_TEMPLATE => Some(CiExtResource::StatusQuery(ii)),
EVENT_MANAGER_TEMPLATE => Some(CiExtResource::EventManager(ii)),
COPY_PROTECTION_TEMPLATE => Some(CiExtResource::CopyProtection(ii)),
CA_PIPELINE_TEMPLATE => Some(CiExtResource::CaPipeline(ii)),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum CiExtApdu<'a> {
ResourceManagerV2(resource_manager_v2::ResourceManagerV2Apdu),
ApplicationInfoV2(application_info_v2::ApplicationInfoV2Apdu<'a>),
PowerManager(power_manager::PowerManagerApdu),
EventManager(event_manager::EventManagerApdu<'a>),
CopyProtection(copy_protection::CopyProtectionApdu<'a>),
StreamInput(stream_input::StreamInputApdu<'a>),
BroadcastServiceGateway(broadcast_service_gateway::BroadcastServiceGatewayApdu<'a>),
StatusQuery(status_query::StatusQueryApdu<'a>),
ApplicationMmi(application_mmi::ApplicationMmiApdu<'a>),
Download(software_download::DownloadApdu<'a>),
CaPipeline(ca_pipeline::CaPipelineApdu<'a>),
}
impl<'a> CiExtApdu<'a> {
pub fn parse(resource_id: ResourceId, body: &'a [u8]) -> Result<Self> {
match classify(resource_id) {
Some(CiExtResource::ResourceManagerV2) => Ok(Self::ResourceManagerV2(
resource_manager_v2::ResourceManagerV2Apdu::parse(body)?,
)),
Some(CiExtResource::ApplicationInfoV2) => Ok(Self::ApplicationInfoV2(
application_info_v2::ApplicationInfoV2Apdu::parse(body)?,
)),
Some(CiExtResource::PowerManager) => Ok(Self::PowerManager(
power_manager::PowerManagerApdu::parse(body)?,
)),
Some(CiExtResource::EventManager(_)) => Ok(Self::EventManager(
event_manager::EventManagerApdu::parse(body)?,
)),
Some(CiExtResource::CopyProtection(_)) => Ok(Self::CopyProtection(
copy_protection::CopyProtectionApdu::parse(body)?,
)),
Some(CiExtResource::StreamInput(_)) => Ok(Self::StreamInput(
stream_input::StreamInputApdu::parse(body)?,
)),
Some(CiExtResource::BroadcastServiceGateway(_)) => Ok(Self::BroadcastServiceGateway(
broadcast_service_gateway::BroadcastServiceGatewayApdu::parse(body)?,
)),
Some(CiExtResource::StatusQuery(_)) => Ok(Self::StatusQuery(
status_query::StatusQueryApdu::parse(body)?,
)),
Some(CiExtResource::ApplicationMmi) => Ok(Self::ApplicationMmi(
application_mmi::ApplicationMmiApdu::parse(body)?,
)),
Some(CiExtResource::Download) => Ok(Self::Download(
software_download::DownloadApdu::parse(body)?,
)),
Some(CiExtResource::CaPipeline(_)) => {
Ok(Self::CaPipeline(ca_pipeline::CaPipelineApdu::parse(body)?))
}
_ => Err(Error::UnknownResource {
resource_id: resource_id.0,
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_fixed_ids() {
assert_eq!(
classify(RESOURCE_MANAGER_V2),
Some(CiExtResource::ResourceManagerV2)
);
assert_eq!(
classify(APPLICATION_INFO_V2),
Some(CiExtResource::ApplicationInfoV2)
);
assert_eq!(classify(POWER_MANAGER), Some(CiExtResource::PowerManager));
assert_eq!(classify(ResourceId(0xDEAD_BEEF)), None);
assert_eq!(classify(ResourceId(0x0001_0041)), None);
}
#[test]
fn classify_masks_module_id() {
let cp3 = ResourceId(0x0004_10C1);
assert_eq!(classify(cp3), Some(CiExtResource::CopyProtection(3)));
assert_eq!(module_id(cp3), 3);
assert_eq!(
classify(ResourceId(0x0023_1041)),
Some(CiExtResource::EventManager(1))
);
assert_eq!(
classify(ResourceId(0x0023_1081)),
Some(CiExtResource::EventManager(2))
);
assert_eq!(
classify(EVENT_MANAGER_TEMPLATE),
Some(CiExtResource::EventManager(0))
);
}
#[test]
fn same_tag_different_resource_routes_differently() {
let pm_body = [0x9F, 0x80, 0x00, 0x01, 0x00];
let cp_body = [0x9F, 0x80, 0x00, 0x03, 0xAA, 0xBB, 0xCC];
let em_body = [0x9F, 0x80, 0x00, 0x01, 0x00];
let pm = CiExtApdu::parse(POWER_MANAGER, &pm_body).unwrap();
assert!(matches!(pm, CiExtApdu::PowerManager(_)));
let cp = CiExtApdu::parse(ResourceId(0x0004_10C1), &cp_body).unwrap();
assert!(matches!(cp, CiExtApdu::CopyProtection(_)));
let em = CiExtApdu::parse(ResourceId(0x0023_1041), &em_body).unwrap();
assert!(matches!(em, CiExtApdu::EventManager(_)));
}
#[test]
fn stream_input_and_bsg_classify_and_mask() {
assert_eq!(
classify(ResourceId(0x0080_1041)),
Some(CiExtResource::StreamInput(1))
);
assert_eq!(
classify(ResourceId(0x0080_1141)),
Some(CiExtResource::StreamInput(5))
);
assert_eq!(module_id(ResourceId(0x0080_1141)), 5);
assert_eq!(
classify(ResourceId(0x0081_1041)),
Some(CiExtResource::BroadcastServiceGateway(1))
);
assert_eq!(
classify(STREAM_INPUT_TEMPLATE),
Some(CiExtResource::StreamInput(0))
);
assert_eq!(
classify(BROADCAST_SERVICE_GATEWAY_TEMPLATE),
Some(CiExtResource::BroadcastServiceGateway(0))
);
}
#[test]
fn stream_input_9f8000_vs_power_manager_9f8000() {
let si_body = [0x9F, 0x80, 0x00, 0x00];
let si = CiExtApdu::parse(ResourceId(0x0080_1041), &si_body).unwrap();
assert!(matches!(
si,
CiExtApdu::StreamInput(stream_input::StreamInputApdu::DeliverySystemInfoReq(_))
));
let pm_body = [0x9F, 0x80, 0x00, 0x01, 0x00];
let pm = CiExtApdu::parse(POWER_MANAGER, &pm_body).unwrap();
assert!(matches!(pm, CiExtApdu::PowerManager(_)));
assert!(!matches!(si, CiExtApdu::PowerManager(_)));
}
#[test]
fn bsg_dispatch_routes_eit_and_inherited() {
let bsg = ResourceId(0x0081_1041);
let eit = [
0x9F, 0x80, 0x10, 0x08, 0x00, 0x4E, 0x00, 0x64, 0x00, 0x00, 0x01, 0x00,
];
let parsed = CiExtApdu::parse(bsg, &eit).unwrap();
assert!(matches!(
parsed,
CiExtApdu::BroadcastServiceGateway(
broadcast_service_gateway::BroadcastServiceGatewayApdu::EitSectionReq(_)
)
));
let slr = [0x9F, 0x80, 0x00, 0x00];
let parsed = CiExtApdu::parse(bsg, &slr).unwrap();
assert!(matches!(
parsed,
CiExtApdu::BroadcastServiceGateway(
broadcast_service_gateway::BroadcastServiceGatewayApdu::ServiceGateway(_)
)
));
}
#[test]
fn new_type1_ids_classify_and_mask() {
assert_eq!(
classify(ResourceId(0x0021_1041)),
Some(CiExtResource::StatusQuery(1))
);
assert_eq!(
classify(ResourceId(0x0021_1081)),
Some(CiExtResource::StatusQuery(2))
);
assert_eq!(module_id(ResourceId(0x0021_1081)), 2);
assert_eq!(
classify(ResourceId(0x0006_1041)),
Some(CiExtResource::CaPipeline(1))
);
assert_eq!(
classify(ResourceId(0x0006_1081)),
Some(CiExtResource::CaPipeline(2))
);
assert_eq!(
classify(STATUS_QUERY_TEMPLATE),
Some(CiExtResource::StatusQuery(0))
);
assert_eq!(
classify(CA_PIPELINE_TEMPLATE),
Some(CiExtResource::CaPipeline(0))
);
assert_eq!(
classify(APPLICATION_MMI),
Some(CiExtResource::ApplicationMmi)
);
assert_eq!(classify(DOWNLOAD), Some(CiExtResource::Download));
}
#[test]
fn tag_9f8000_routes_per_resource_across_all_resources() {
let body = [0x9F, 0x80, 0x00, 0x00];
let sq_body = [0x9F, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01];
let sq = CiExtApdu::parse(ResourceId(0x0021_1041), &sq_body).unwrap();
assert!(matches!(
sq,
CiExtApdu::StatusQuery(status_query::StatusQueryApdu::StatusQueryReq(_))
));
let mmi_body = [0x9F, 0x80, 0x00, 0x02, 0x00, 0x00];
let mmi = CiExtApdu::parse(APPLICATION_MMI, &mmi_body).unwrap();
assert!(matches!(
mmi,
CiExtApdu::ApplicationMmi(application_mmi::ApplicationMmiApdu::RequestStart(_))
));
let dl = CiExtApdu::parse(DOWNLOAD, &body).unwrap();
assert!(matches!(
dl,
CiExtApdu::Download(software_download::DownloadApdu::DownloadEnquiry(_))
));
let cap = CiExtApdu::parse(ResourceId(0x0006_1041), &body).unwrap();
assert!(matches!(
cap,
CiExtApdu::CaPipeline(ca_pipeline::CaPipelineApdu::Request(_))
));
let si = CiExtApdu::parse(ResourceId(0x0080_1041), &body).unwrap();
assert!(matches!(
si,
CiExtApdu::StreamInput(stream_input::StreamInputApdu::DeliverySystemInfoReq(_))
));
assert!(!matches!(sq, CiExtApdu::ApplicationMmi(_)));
assert!(!matches!(dl, CiExtApdu::CaPipeline(_)));
assert!(!matches!(cap, CiExtApdu::Download(_)));
}
#[test]
fn unknown_resource_errors() {
let body = [0x9F, 0x80, 0x00, 0x00];
assert!(matches!(
CiExtApdu::parse(ResourceId(0x1234_5678), &body),
Err(Error::UnknownResource { .. })
));
}
#[test]
fn display_carries_module_id() {
use alloc::format;
assert_eq!(
format!("{}", CiExtResource::CopyProtection(3)),
"copy_protection(module_id=3)"
);
assert_eq!(format!("{}", CiExtResource::PowerManager), "power_manager");
}
}