1use std::sync::Arc;
8use tracing::debug;
9
10use super::{BinaryDataProcessor, ProcessorCapability, ProcessorContext, ProcessorKey};
11
12pub trait DispatchRule: Send + Sync {
35 fn applies_to(&self, context: &ProcessorContext) -> bool;
41
42 fn select_processor(
50 &self,
51 candidates: &[(
52 ProcessorKey,
53 Arc<dyn BinaryDataProcessor>,
54 ProcessorCapability,
55 )],
56 context: &ProcessorContext,
57 ) -> Option<(ProcessorKey, Arc<dyn BinaryDataProcessor>)>;
58
59 fn description(&self) -> &str;
61
62 fn priority(&self) -> u8 {
64 50 }
66}
67
68pub struct CanonDispatchRule;
77
78impl DispatchRule for CanonDispatchRule {
79 fn applies_to(&self, context: &ProcessorContext) -> bool {
80 context.is_manufacturer("Canon")
81 }
82
83 fn select_processor(
84 &self,
85 candidates: &[(
86 ProcessorKey,
87 Arc<dyn BinaryDataProcessor>,
88 ProcessorCapability,
89 )],
90 context: &ProcessorContext,
91 ) -> Option<(ProcessorKey, Arc<dyn BinaryDataProcessor>)> {
92 debug!(
93 "Applying Canon dispatch rule for table: {}",
94 context.table_name
95 );
96
97 match context.table_name.as_str() {
99 "Canon::SerialData" => {
100 if let Some(model) = &context.model {
102 if model.contains("EOS R5")
103 || model.contains("EOS R6")
104 || model.contains("EOS R3")
105 {
106 if let Some(processor) = self.find_processor_variant(
107 candidates,
108 "Canon",
109 "SerialData",
110 Some("MkII"),
111 ) {
112 debug!(
113 "Selected Canon SerialData MkII processor for model: {}",
114 model
115 );
116 return Some(processor);
117 }
118 }
119 }
120
121 self.find_processor_variant(candidates, "Canon", "SerialData", None)
123 }
124
125 "Canon::AFInfo" | "Canon::AFInfo2" => {
126 if let Some(af_info_version) = context.get_parent_tag("AFInfoVersion") {
128 match af_info_version.as_u16() {
129 Some(0x0001) => {
130 self.find_processor_variant(candidates, "Canon", "AFInfo", Some("V1"))
131 }
132 Some(0x0002) => {
133 self.find_processor_variant(candidates, "Canon", "AFInfo", Some("V2"))
134 }
135 Some(0x0003) => {
136 self.find_processor_variant(candidates, "Canon", "AFInfo", Some("V3"))
137 }
138 _ => self.find_processor_variant(candidates, "Canon", "AFInfo", None),
139 }
140 } else {
141 if let Some(model) = &context.model {
143 if model.contains("1D") {
144 self.find_processor_variant(candidates, "Canon", "AFInfo", Some("1D"))
145 } else if model.contains("5D") {
146 self.find_processor_variant(candidates, "Canon", "AFInfo", Some("5D"))
147 } else {
148 self.find_processor_variant(candidates, "Canon", "AFInfo", None)
149 }
150 } else {
151 self.find_processor_variant(candidates, "Canon", "AFInfo", None)
152 }
153 }
154 }
155
156 "Canon::CameraSettings" => {
157 if let Some(format_version) = &context.format_version {
159 self.find_processor_variant(
160 candidates,
161 "Canon",
162 "CameraSettings",
163 Some(format_version),
164 )
165 } else {
166 self.find_processor_variant(candidates, "Canon", "CameraSettings", None)
167 }
168 }
169
170 _ => {
171 if context.table_name.starts_with("Canon::") {
175 candidates
177 .iter()
178 .find(|(key, _, _)| key.namespace == "Canon")
179 .map(|(key, processor, _)| (key.clone(), processor.clone()))
180 } else {
181 debug!(
183 "Canon dispatch rule ignoring non-Canon table: {}",
184 context.table_name
185 );
186 None
187 }
188 }
189 }
190 }
191
192 fn description(&self) -> &str {
193 "Canon manufacturer-specific processor dispatch"
194 }
195
196 fn priority(&self) -> u8 {
197 80 }
199}
200
201impl CanonDispatchRule {
202 fn find_processor_variant(
204 &self,
205 candidates: &[(
206 ProcessorKey,
207 Arc<dyn BinaryDataProcessor>,
208 ProcessorCapability,
209 )],
210 namespace: &str,
211 processor_name: &str,
212 variant: Option<&str>,
213 ) -> Option<(ProcessorKey, Arc<dyn BinaryDataProcessor>)> {
214 candidates
215 .iter()
216 .find(|(key, _, _)| {
217 key.namespace == namespace
218 && key.processor_name == processor_name
219 && key.variant.as_deref() == variant
220 })
221 .map(|(key, processor, _)| (key.clone(), processor.clone()))
222 }
223}
224
225pub struct NikonDispatchRule;
234
235impl DispatchRule for NikonDispatchRule {
236 fn applies_to(&self, context: &ProcessorContext) -> bool {
237 context.is_manufacturer("NIKON CORPORATION") || context.is_manufacturer("NIKON")
238 }
239
240 fn select_processor(
241 &self,
242 candidates: &[(
243 ProcessorKey,
244 Arc<dyn BinaryDataProcessor>,
245 ProcessorCapability,
246 )],
247 context: &ProcessorContext,
248 ) -> Option<(ProcessorKey, Arc<dyn BinaryDataProcessor>)> {
249 debug!(
250 "Applying Nikon dispatch rule for table: {}",
251 context.table_name
252 );
253
254 match context.table_name.as_str() {
256 table_name if table_name.contains("LensData") => {
257 if context.parameters.contains_key("DecryptStart") {
259 debug!("Detected encrypted Nikon lens data");
260 self.find_processor_variant(candidates, "Nikon", "Encrypted", None)
261 } else {
262 self.find_processor_variant(candidates, "Nikon", "LensData", None)
263 }
264 }
265
266 "Nikon::ShotInfo" => {
267 if let Some(model) = &context.model {
269 if model.contains("Z 9") {
270 self.find_processor_variant(candidates, "Nikon", "ShotInfo", Some("Z9"))
271 } else if model.contains("Z 8") {
272 self.find_processor_variant(candidates, "Nikon", "ShotInfo", Some("Z8"))
273 } else if model.contains("Z 6III") {
274 self.find_processor_variant(candidates, "Nikon", "ShotInfo", Some("Z6III"))
275 } else if model.contains("Z 7II") {
276 self.find_processor_variant(candidates, "Nikon", "ShotInfo", Some("Z7II"))
277 } else {
278 self.find_processor_variant(candidates, "Nikon", "ShotInfo", None)
279 }
280 } else {
281 self.find_processor_variant(candidates, "Nikon", "ShotInfo", None)
282 }
283 }
284
285 "Nikon::ColorBalance" => {
286 if let Some(version) = context.get_parameter("Version") {
288 self.find_processor_variant(candidates, "Nikon", "ColorBalance", Some(version))
289 } else {
290 self.find_processor_variant(candidates, "Nikon", "ColorBalance", None)
291 }
292 }
293
294 _ => {
295 if context.table_name.starts_with("Nikon::") {
299 if self.has_encryption_keys(context) && self.might_be_encrypted(context) {
301 debug!("Trying encrypted processor for: {}", context.table_name);
302 if let Some(encrypted_processor) =
303 self.find_processor_variant(candidates, "Nikon", "Encrypted", None)
304 {
305 return Some(encrypted_processor);
306 }
307 }
308
309 candidates
311 .iter()
312 .find(|(key, _, _)| key.namespace == "Nikon")
313 .map(|(key, processor, _)| (key.clone(), processor.clone()))
314 } else {
315 debug!(
317 "Nikon dispatch rule ignoring non-Nikon table: {}",
318 context.table_name
319 );
320 None
321 }
322 }
323 }
324 }
325
326 fn description(&self) -> &str {
327 "Nikon manufacturer-specific processor dispatch with encryption detection"
328 }
329
330 fn priority(&self) -> u8 {
331 80 }
333}
334
335impl NikonDispatchRule {
336 fn find_processor_variant(
338 &self,
339 candidates: &[(
340 ProcessorKey,
341 Arc<dyn BinaryDataProcessor>,
342 ProcessorCapability,
343 )],
344 namespace: &str,
345 processor_name: &str,
346 variant: Option<&str>,
347 ) -> Option<(ProcessorKey, Arc<dyn BinaryDataProcessor>)> {
348 candidates
349 .iter()
350 .find(|(key, _, _)| {
351 key.namespace == namespace
352 && key.processor_name == processor_name
353 && key.variant.as_deref() == variant
354 })
355 .map(|(key, processor, _)| (key.clone(), processor.clone()))
356 }
357
358 fn has_encryption_keys(&self, context: &ProcessorContext) -> bool {
360 context.get_nikon_encryption_keys().is_some()
361 }
362
363 fn might_be_encrypted(&self, context: &ProcessorContext) -> bool {
365 context.parameters.contains_key("DecryptStart")
367 || context.parameters.contains_key("DecryptLen")
368 || context.table_name.contains("Encrypted")
369 }
370}
371
372pub struct FormatDispatchRule;
378
379impl DispatchRule for FormatDispatchRule {
380 fn applies_to(&self, _context: &ProcessorContext) -> bool {
381 true }
383
384 fn select_processor(
385 &self,
386 candidates: &[(
387 ProcessorKey,
388 Arc<dyn BinaryDataProcessor>,
389 ProcessorCapability,
390 )],
391 context: &ProcessorContext,
392 ) -> Option<(ProcessorKey, Arc<dyn BinaryDataProcessor>)> {
393 match context.file_format {
395 crate::formats::FileFormat::Tiff => {
396 candidates
398 .iter()
399 .find(|(key, _, _)| key.processor_name.contains("TIFF"))
400 .map(|(key, processor, _)| (key.clone(), processor.clone()))
401 }
402 crate::formats::FileFormat::CanonRaw | crate::formats::FileFormat::NikonRaw => {
403 candidates
405 .iter()
406 .find(|(key, _, _)| key.processor_name.contains("RAW"))
407 .map(|(key, processor, _)| (key.clone(), processor.clone()))
408 }
409 _ => None, }
411 }
412
413 fn description(&self) -> &str {
414 "Format-specific processor dispatch"
415 }
416
417 fn priority(&self) -> u8 {
418 30 }
420}
421
422pub struct TableDispatchRule;
427
428impl DispatchRule for TableDispatchRule {
429 fn applies_to(&self, _context: &ProcessorContext) -> bool {
430 true }
432
433 fn select_processor(
434 &self,
435 candidates: &[(
436 ProcessorKey,
437 Arc<dyn BinaryDataProcessor>,
438 ProcessorCapability,
439 )],
440 context: &ProcessorContext,
441 ) -> Option<(ProcessorKey, Arc<dyn BinaryDataProcessor>)> {
442 if context.table_name.contains("BinaryData") {
444 return candidates
446 .iter()
447 .find(|(key, _, _)| key.processor_name.contains("BinaryData"))
448 .map(|(key, processor, _)| (key.clone(), processor.clone()));
449 }
450
451 if context.table_name.contains("SerialData") {
452 return candidates
454 .iter()
455 .find(|(key, _, _)| key.processor_name.contains("SerialData"))
456 .map(|(key, processor, _)| (key.clone(), processor.clone()));
457 }
458
459 if context.table_name.contains("AFInfo") {
460 return candidates
462 .iter()
463 .find(|(key, _, _)| key.processor_name.contains("AFInfo"))
464 .map(|(key, processor, _)| (key.clone(), processor.clone()));
465 }
466
467 None }
469
470 fn description(&self) -> &str {
471 "Table name-based processor dispatch"
472 }
473
474 fn priority(&self) -> u8 {
475 40 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use crate::formats::FileFormat;
483 use crate::processor_registry::{ProcessorMetadata, ProcessorResult};
484
485 struct MockProcessor;
486 impl BinaryDataProcessor for MockProcessor {
487 fn can_process(&self, _context: &ProcessorContext) -> ProcessorCapability {
488 ProcessorCapability::Good
489 }
490 fn process_data(
491 &self,
492 _data: &[u8],
493 _context: &ProcessorContext,
494 ) -> crate::types::Result<ProcessorResult> {
495 Ok(ProcessorResult::new())
496 }
497 fn get_metadata(&self) -> ProcessorMetadata {
498 ProcessorMetadata::new("Mock".to_string(), "Mock processor".to_string())
499 }
500 }
501
502 #[test]
503 fn test_canon_dispatch_rule() {
504 let rule = CanonDispatchRule;
505 let canon_context =
506 ProcessorContext::new(FileFormat::Jpeg, "Canon::SerialData".to_string())
507 .with_manufacturer("Canon".to_string())
508 .with_model("EOS R5".to_string());
509
510 assert!(rule.applies_to(&canon_context));
511
512 let candidates = vec![
514 (
515 ProcessorKey::new("Canon".to_string(), "SerialData".to_string()),
516 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
517 ProcessorCapability::Good,
518 ),
519 (
520 ProcessorKey::with_variant(
521 "Canon".to_string(),
522 "SerialData".to_string(),
523 "MkII".to_string(),
524 ),
525 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
526 ProcessorCapability::Good,
527 ),
528 ];
529
530 let selected = rule.select_processor(&candidates, &canon_context);
531 assert!(selected.is_some());
532 let (key, _) = selected.unwrap();
533 assert_eq!(key.variant, Some("MkII".to_string()));
534 }
535
536 #[test]
537 fn test_nikon_dispatch_rule() {
538 let rule = NikonDispatchRule;
539 let nikon_context = ProcessorContext::new(FileFormat::Jpeg, "Nikon::LensData".to_string())
540 .with_manufacturer("NIKON CORPORATION".to_string())
541 .with_parameters({
542 let mut params = std::collections::HashMap::new();
543 params.insert("DecryptStart".to_string(), "4".to_string());
544 params
545 });
546
547 assert!(rule.applies_to(&nikon_context));
548
549 let candidates = vec![
550 (
551 ProcessorKey::new("Nikon".to_string(), "LensData".to_string()),
552 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
553 ProcessorCapability::Good,
554 ),
555 (
556 ProcessorKey::new("Nikon".to_string(), "Encrypted".to_string()),
557 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
558 ProcessorCapability::Good,
559 ),
560 ];
561
562 let selected = rule.select_processor(&candidates, &nikon_context);
563 assert!(selected.is_some());
564 let (key, _) = selected.unwrap();
565 assert_eq!(key.processor_name, "Encrypted");
566 }
567
568 #[test]
569 fn test_format_dispatch_rule() {
570 let rule = FormatDispatchRule;
571 let tiff_context = ProcessorContext::new(FileFormat::Tiff, "EXIF::Main".to_string());
572
573 assert!(rule.applies_to(&tiff_context));
574
575 let candidates = vec![
576 (
577 ProcessorKey::new("EXIF".to_string(), "Main".to_string()),
578 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
579 ProcessorCapability::Good,
580 ),
581 (
582 ProcessorKey::new("EXIF".to_string(), "TIFF".to_string()),
583 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
584 ProcessorCapability::Good,
585 ),
586 ];
587
588 let selected = rule.select_processor(&candidates, &tiff_context);
589 assert!(selected.is_some());
590 let (key, _) = selected.unwrap();
591 assert!(key.processor_name.contains("TIFF"));
592 }
593
594 #[test]
595 fn test_table_dispatch_rule() {
596 let rule = TableDispatchRule;
597 let binary_context =
598 ProcessorContext::new(FileFormat::Jpeg, "Canon::BinaryData".to_string());
599
600 assert!(rule.applies_to(&binary_context));
601
602 let candidates = vec![
603 (
604 ProcessorKey::new("Canon".to_string(), "Main".to_string()),
605 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
606 ProcessorCapability::Good,
607 ),
608 (
609 ProcessorKey::new("Canon".to_string(), "BinaryData".to_string()),
610 Arc::new(MockProcessor) as Arc<dyn BinaryDataProcessor>,
611 ProcessorCapability::Good,
612 ),
613 ];
614
615 let selected = rule.select_processor(&candidates, &binary_context);
616 assert!(selected.is_some());
617 let (key, _) = selected.unwrap();
618 assert!(key.processor_name.contains("BinaryData"));
619 }
620}