1use crate::error::{Error, Result};
28use crate::resource::ResourceId;
29
30pub mod application_info_v2;
31pub mod application_mmi;
32pub mod broadcast_service_gateway;
33pub mod ca_pipeline;
34pub mod copy_protection;
35pub mod event_manager;
36pub mod power_manager;
37pub mod resource_manager_v2;
38pub mod service_gateway;
39pub mod software_download;
40pub mod status_query;
41pub mod stream_input;
42
43pub const RESOURCE_MANAGER_V2: ResourceId = ResourceId(0x0001_0042);
47pub const APPLICATION_INFO_V2: ResourceId = ResourceId(0x0002_0042);
49pub const POWER_MANAGER: ResourceId = ResourceId(0x0022_0041);
51pub const APPLICATION_MMI: ResourceId = ResourceId(0x0041_0041);
53pub const DOWNLOAD: ResourceId = ResourceId(0x0005_1041);
58
59pub const STREAM_INPUT_TEMPLATE: ResourceId = ResourceId(0x0080_1001);
62pub const BROADCAST_SERVICE_GATEWAY_TEMPLATE: ResourceId = ResourceId(0x0081_1001);
64pub const STATUS_QUERY_TEMPLATE: ResourceId = ResourceId(0x0021_1001);
66pub const EVENT_MANAGER_TEMPLATE: ResourceId = ResourceId(0x0023_1001);
68pub const COPY_PROTECTION_TEMPLATE: ResourceId = ResourceId(0x0004_1001);
70pub const CA_PIPELINE_TEMPLATE: ResourceId = ResourceId(0x0006_1001);
72
73pub const MODULE_ID_MASK: u32 = 0xFFFF_F03F;
77
78#[must_use]
80pub const fn module_id(id: ResourceId) -> u8 {
81 ((id.0 >> 6) & 0x3F) as u8
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87#[cfg_attr(feature = "serde", derive(serde::Serialize))]
88#[non_exhaustive]
89pub enum CiExtResource {
90 ResourceManagerV2,
92 ApplicationInfoV2,
94 PowerManager,
96 ApplicationMmi,
98 Download,
100 StreamInput(u8),
102 BroadcastServiceGateway(u8),
104 StatusQuery(u8),
106 EventManager(u8),
108 CopyProtection(u8),
110 CaPipeline(u8),
112}
113
114impl CiExtResource {
115 #[must_use]
117 pub fn name(&self) -> &'static str {
118 match self {
119 Self::ResourceManagerV2 => "resource_manager_v2",
120 Self::ApplicationInfoV2 => "application_information_v2",
121 Self::PowerManager => "power_manager",
122 Self::ApplicationMmi => "application_mmi",
123 Self::Download => "download",
124 Self::StreamInput(_) => "stream_input",
125 Self::BroadcastServiceGateway(_) => "broadcast_service_gateway",
126 Self::StatusQuery(_) => "status_query",
127 Self::EventManager(_) => "event_manager",
128 Self::CopyProtection(_) => "copy_protection",
129 Self::CaPipeline(_) => "ca_pipeline",
130 }
131 }
132}
133
134impl core::fmt::Display for CiExtResource {
135 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136 match self {
137 Self::StreamInput(ii)
138 | Self::BroadcastServiceGateway(ii)
139 | Self::StatusQuery(ii)
140 | Self::EventManager(ii)
141 | Self::CopyProtection(ii)
142 | Self::CaPipeline(ii) => write!(f, "{}(module_id={ii})", self.name()),
143 other => f.write_str(other.name()),
144 }
145 }
146}
147
148#[must_use]
152pub fn classify(id: ResourceId) -> Option<CiExtResource> {
153 match id {
154 RESOURCE_MANAGER_V2 => return Some(CiExtResource::ResourceManagerV2),
155 APPLICATION_INFO_V2 => return Some(CiExtResource::ApplicationInfoV2),
156 POWER_MANAGER => return Some(CiExtResource::PowerManager),
157 APPLICATION_MMI => return Some(CiExtResource::ApplicationMmi),
158 DOWNLOAD => return Some(CiExtResource::Download),
159 _ => {}
160 }
161 let ii = module_id(id);
162 match ResourceId(id.0 & MODULE_ID_MASK) {
163 STREAM_INPUT_TEMPLATE => Some(CiExtResource::StreamInput(ii)),
164 BROADCAST_SERVICE_GATEWAY_TEMPLATE => Some(CiExtResource::BroadcastServiceGateway(ii)),
165 STATUS_QUERY_TEMPLATE => Some(CiExtResource::StatusQuery(ii)),
166 EVENT_MANAGER_TEMPLATE => Some(CiExtResource::EventManager(ii)),
167 COPY_PROTECTION_TEMPLATE => Some(CiExtResource::CopyProtection(ii)),
168 CA_PIPELINE_TEMPLATE => Some(CiExtResource::CaPipeline(ii)),
169 _ => None,
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize))]
179#[non_exhaustive]
180pub enum CiExtApdu<'a> {
181 ResourceManagerV2(resource_manager_v2::ResourceManagerV2Apdu),
183 ApplicationInfoV2(application_info_v2::ApplicationInfoV2Apdu<'a>),
185 PowerManager(power_manager::PowerManagerApdu),
187 EventManager(event_manager::EventManagerApdu<'a>),
189 CopyProtection(copy_protection::CopyProtectionApdu<'a>),
191 StreamInput(stream_input::StreamInputApdu<'a>),
193 BroadcastServiceGateway(broadcast_service_gateway::BroadcastServiceGatewayApdu<'a>),
196 StatusQuery(status_query::StatusQueryApdu<'a>),
198 ApplicationMmi(application_mmi::ApplicationMmiApdu<'a>),
200 Download(software_download::DownloadApdu<'a>),
202 CaPipeline(ca_pipeline::CaPipelineApdu<'a>),
204}
205
206impl<'a> CiExtApdu<'a> {
207 pub fn parse(resource_id: ResourceId, body: &'a [u8]) -> Result<Self> {
214 match classify(resource_id) {
215 Some(CiExtResource::ResourceManagerV2) => Ok(Self::ResourceManagerV2(
216 resource_manager_v2::ResourceManagerV2Apdu::parse(body)?,
217 )),
218 Some(CiExtResource::ApplicationInfoV2) => Ok(Self::ApplicationInfoV2(
219 application_info_v2::ApplicationInfoV2Apdu::parse(body)?,
220 )),
221 Some(CiExtResource::PowerManager) => Ok(Self::PowerManager(
222 power_manager::PowerManagerApdu::parse(body)?,
223 )),
224 Some(CiExtResource::EventManager(_)) => Ok(Self::EventManager(
225 event_manager::EventManagerApdu::parse(body)?,
226 )),
227 Some(CiExtResource::CopyProtection(_)) => Ok(Self::CopyProtection(
228 copy_protection::CopyProtectionApdu::parse(body)?,
229 )),
230 Some(CiExtResource::StreamInput(_)) => Ok(Self::StreamInput(
231 stream_input::StreamInputApdu::parse(body)?,
232 )),
233 Some(CiExtResource::BroadcastServiceGateway(_)) => Ok(Self::BroadcastServiceGateway(
234 broadcast_service_gateway::BroadcastServiceGatewayApdu::parse(body)?,
235 )),
236 Some(CiExtResource::StatusQuery(_)) => Ok(Self::StatusQuery(
237 status_query::StatusQueryApdu::parse(body)?,
238 )),
239 Some(CiExtResource::ApplicationMmi) => Ok(Self::ApplicationMmi(
240 application_mmi::ApplicationMmiApdu::parse(body)?,
241 )),
242 Some(CiExtResource::Download) => Ok(Self::Download(
243 software_download::DownloadApdu::parse(body)?,
244 )),
245 Some(CiExtResource::CaPipeline(_)) => {
246 Ok(Self::CaPipeline(ca_pipeline::CaPipelineApdu::parse(body)?))
247 }
248 _ => Err(Error::UnknownResource {
249 resource_id: resource_id.0,
250 }),
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn classify_fixed_ids() {
261 assert_eq!(
262 classify(RESOURCE_MANAGER_V2),
263 Some(CiExtResource::ResourceManagerV2)
264 );
265 assert_eq!(
266 classify(APPLICATION_INFO_V2),
267 Some(CiExtResource::ApplicationInfoV2)
268 );
269 assert_eq!(classify(POWER_MANAGER), Some(CiExtResource::PowerManager));
270 assert_eq!(classify(ResourceId(0xDEAD_BEEF)), None);
271 assert_eq!(classify(ResourceId(0x0001_0041)), None);
273 }
274
275 #[test]
276 fn classify_masks_module_id() {
277 let cp3 = ResourceId(0x0004_10C1);
279 assert_eq!(classify(cp3), Some(CiExtResource::CopyProtection(3)));
280 assert_eq!(module_id(cp3), 3);
281 assert_eq!(
283 classify(ResourceId(0x0023_1041)),
284 Some(CiExtResource::EventManager(1))
285 );
286 assert_eq!(
287 classify(ResourceId(0x0023_1081)),
288 Some(CiExtResource::EventManager(2))
289 );
290 assert_eq!(
292 classify(EVENT_MANAGER_TEMPLATE),
293 Some(CiExtResource::EventManager(0))
294 );
295 }
296
297 #[test]
298 fn same_tag_different_resource_routes_differently() {
299 let pm_body = [0x9F, 0x80, 0x00, 0x01, 0x00];
302 let cp_body = [0x9F, 0x80, 0x00, 0x03, 0xAA, 0xBB, 0xCC];
304 let em_body = [0x9F, 0x80, 0x00, 0x01, 0x00];
306
307 let pm = CiExtApdu::parse(POWER_MANAGER, &pm_body).unwrap();
308 assert!(matches!(pm, CiExtApdu::PowerManager(_)));
309
310 let cp = CiExtApdu::parse(ResourceId(0x0004_10C1), &cp_body).unwrap();
311 assert!(matches!(cp, CiExtApdu::CopyProtection(_)));
312
313 let em = CiExtApdu::parse(ResourceId(0x0023_1041), &em_body).unwrap();
314 assert!(matches!(em, CiExtApdu::EventManager(_)));
315 }
316
317 #[test]
318 fn stream_input_and_bsg_classify_and_mask() {
319 assert_eq!(
321 classify(ResourceId(0x0080_1041)),
322 Some(CiExtResource::StreamInput(1))
323 );
324 assert_eq!(
325 classify(ResourceId(0x0080_1141)),
326 Some(CiExtResource::StreamInput(5))
327 );
328 assert_eq!(module_id(ResourceId(0x0080_1141)), 5);
329 assert_eq!(
331 classify(ResourceId(0x0081_1041)),
332 Some(CiExtResource::BroadcastServiceGateway(1))
333 );
334 assert_eq!(
336 classify(STREAM_INPUT_TEMPLATE),
337 Some(CiExtResource::StreamInput(0))
338 );
339 assert_eq!(
340 classify(BROADCAST_SERVICE_GATEWAY_TEMPLATE),
341 Some(CiExtResource::BroadcastServiceGateway(0))
342 );
343 }
344
345 #[test]
346 fn stream_input_9f8000_vs_power_manager_9f8000() {
347 let si_body = [0x9F, 0x80, 0x00, 0x00];
351 let si = CiExtApdu::parse(ResourceId(0x0080_1041), &si_body).unwrap();
352 assert!(matches!(
353 si,
354 CiExtApdu::StreamInput(stream_input::StreamInputApdu::DeliverySystemInfoReq(_))
355 ));
356 let pm_body = [0x9F, 0x80, 0x00, 0x01, 0x00];
358 let pm = CiExtApdu::parse(POWER_MANAGER, &pm_body).unwrap();
359 assert!(matches!(pm, CiExtApdu::PowerManager(_)));
360 assert!(!matches!(si, CiExtApdu::PowerManager(_)));
362 }
363
364 #[test]
365 fn bsg_dispatch_routes_eit_and_inherited() {
366 let bsg = ResourceId(0x0081_1041);
367 let eit = [
369 0x9F, 0x80, 0x10, 0x08, 0x00, 0x4E, 0x00, 0x64, 0x00, 0x00, 0x01, 0x00,
370 ];
371 let parsed = CiExtApdu::parse(bsg, &eit).unwrap();
372 assert!(matches!(
373 parsed,
374 CiExtApdu::BroadcastServiceGateway(
375 broadcast_service_gateway::BroadcastServiceGatewayApdu::EitSectionReq(_)
376 )
377 ));
378 let slr = [0x9F, 0x80, 0x00, 0x00];
380 let parsed = CiExtApdu::parse(bsg, &slr).unwrap();
381 assert!(matches!(
382 parsed,
383 CiExtApdu::BroadcastServiceGateway(
384 broadcast_service_gateway::BroadcastServiceGatewayApdu::ServiceGateway(_)
385 )
386 ));
387 }
388
389 #[test]
390 fn new_type1_ids_classify_and_mask() {
391 assert_eq!(
393 classify(ResourceId(0x0021_1041)),
394 Some(CiExtResource::StatusQuery(1))
395 );
396 assert_eq!(
397 classify(ResourceId(0x0021_1081)),
398 Some(CiExtResource::StatusQuery(2))
399 );
400 assert_eq!(module_id(ResourceId(0x0021_1081)), 2);
401 assert_eq!(
403 classify(ResourceId(0x0006_1041)),
404 Some(CiExtResource::CaPipeline(1))
405 );
406 assert_eq!(
407 classify(ResourceId(0x0006_1081)),
408 Some(CiExtResource::CaPipeline(2))
409 );
410 assert_eq!(
412 classify(STATUS_QUERY_TEMPLATE),
413 Some(CiExtResource::StatusQuery(0))
414 );
415 assert_eq!(
416 classify(CA_PIPELINE_TEMPLATE),
417 Some(CiExtResource::CaPipeline(0))
418 );
419 assert_eq!(
421 classify(APPLICATION_MMI),
422 Some(CiExtResource::ApplicationMmi)
423 );
424 assert_eq!(classify(DOWNLOAD), Some(CiExtResource::Download));
425 }
426
427 #[test]
428 fn tag_9f8000_routes_per_resource_across_all_resources() {
429 let body = [0x9F, 0x80, 0x00, 0x00]; let sq_body = [0x9F, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01];
435 let sq = CiExtApdu::parse(ResourceId(0x0021_1041), &sq_body).unwrap();
436 assert!(matches!(
437 sq,
438 CiExtApdu::StatusQuery(status_query::StatusQueryApdu::StatusQueryReq(_))
439 ));
440
441 let mmi_body = [0x9F, 0x80, 0x00, 0x02, 0x00, 0x00];
443 let mmi = CiExtApdu::parse(APPLICATION_MMI, &mmi_body).unwrap();
444 assert!(matches!(
445 mmi,
446 CiExtApdu::ApplicationMmi(application_mmi::ApplicationMmiApdu::RequestStart(_))
447 ));
448
449 let dl = CiExtApdu::parse(DOWNLOAD, &body).unwrap();
451 assert!(matches!(
452 dl,
453 CiExtApdu::Download(software_download::DownloadApdu::DownloadEnquiry(_))
454 ));
455
456 let cap = CiExtApdu::parse(ResourceId(0x0006_1041), &body).unwrap();
458 assert!(matches!(
459 cap,
460 CiExtApdu::CaPipeline(ca_pipeline::CaPipelineApdu::Request(_))
461 ));
462
463 let si = CiExtApdu::parse(ResourceId(0x0080_1041), &body).unwrap();
465 assert!(matches!(
466 si,
467 CiExtApdu::StreamInput(stream_input::StreamInputApdu::DeliverySystemInfoReq(_))
468 ));
469
470 assert!(!matches!(sq, CiExtApdu::ApplicationMmi(_)));
472 assert!(!matches!(dl, CiExtApdu::CaPipeline(_)));
473 assert!(!matches!(cap, CiExtApdu::Download(_)));
474 }
475
476 #[test]
477 fn unknown_resource_errors() {
478 let body = [0x9F, 0x80, 0x00, 0x00];
479 assert!(matches!(
480 CiExtApdu::parse(ResourceId(0x1234_5678), &body),
481 Err(Error::UnknownResource { .. })
482 ));
483 }
484
485 #[test]
486 fn display_carries_module_id() {
487 use alloc::format;
488 assert_eq!(
489 format!("{}", CiExtResource::CopyProtection(3)),
490 "copy_protection(module_id=3)"
491 );
492 assert_eq!(format!("{}", CiExtResource::PowerManager), "power_manager");
493 }
494}