Skip to main content

exif_oxide/types/
binary_data.rs

1//! Binary data processing types for ProcessBinaryData
2//!
3//! This module defines the types used for ExifTool's ProcessBinaryData
4//! functionality, including format definitions and table structures.
5
6use crate::types::{DataMemberValue, ExifError};
7use regex::Regex;
8use std::collections::HashMap;
9use std::sync::LazyLock;
10
11// Static regexes for format expression parsing
12// ExifTool: lib/Image/ExifTool.pm:9850-9856 format parsing patterns
13static ARRAY_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(.+)\[(.+)\]$").unwrap());
14static VAL_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\$val\{([^}]+)\}").unwrap());
15
16/// Binary data formats for ProcessBinaryData
17/// ExifTool: lib/Image/ExifTool.pm %formatSize and @formatName arrays
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum BinaryDataFormat {
20    /// Fixed formats - these don't require expression evaluation
21    /// Unsigned 8-bit integer
22    /// ExifTool: int8u
23    Int8u,
24    /// Signed 8-bit integer
25    /// ExifTool: int8s
26    Int8s,
27    /// Unsigned 16-bit integer
28    /// ExifTool: int16u
29    Int16u,
30    /// Signed 16-bit integer
31    /// ExifTool: int16s
32    Int16s,
33    /// Unsigned 32-bit integer
34    /// ExifTool: int32u
35    Int32u,
36    /// Signed 32-bit integer
37    /// ExifTool: int32s
38    Int32s,
39    /// 32-bit floating point
40    /// ExifTool: float
41    Float,
42    /// 64-bit floating point
43    /// ExifTool: double
44    Double,
45    /// Null-terminated string
46    /// ExifTool: string
47    String,
48    /// Pascal string (first byte is length)
49    /// ExifTool: pstring
50    PString,
51    /// Binary/undefined data
52    /// ExifTool: undef
53    Undef,
54    /// Variable string with null termination
55    /// ExifTool: var_string
56    VarString,
57}
58
59/// Format specification that may contain expressions
60/// ExifTool: Format with expressions like "int16s[$val{0}]"
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum FormatSpec {
63    /// Fixed format without expressions
64    Fixed(BinaryDataFormat),
65    /// Variable format with expression for count
66    /// ExifTool: "int16s[$val{0}]" -> Array { base: Int16s, count_expr: "$val{0}" }
67    Array {
68        base_format: BinaryDataFormat,
69        count_expr: String,
70    },
71    /// Variable-length string with expression for length
72    /// ExifTool: "string[$val{3}]" -> StringWithLength { length_expr: "$val{3}" }
73    StringWithLength { length_expr: String },
74}
75
76/// Resolved format after expression evaluation
77/// ExifTool: Formats after $val{} expressions have been evaluated
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum ResolvedFormat {
80    /// Single value format
81    Single(BinaryDataFormat),
82    /// Array of values with known count
83    Array(BinaryDataFormat, usize),
84    /// String with known length
85    StringWithLength(usize),
86    /// Variable null-terminated string
87    VarString,
88}
89
90impl BinaryDataFormat {
91    /// Get byte size for this format
92    /// ExifTool: lib/Image/ExifTool.pm %formatSize array
93    pub fn byte_size(self) -> usize {
94        match self {
95            BinaryDataFormat::Int8u | BinaryDataFormat::Int8s | BinaryDataFormat::Undef => 1,
96            BinaryDataFormat::Int16u | BinaryDataFormat::Int16s => 2,
97            BinaryDataFormat::Int32u | BinaryDataFormat::Int32s | BinaryDataFormat::Float => 4,
98            BinaryDataFormat::Double => 8,
99            BinaryDataFormat::String | BinaryDataFormat::PString | BinaryDataFormat::VarString => 1, // Variable length
100        }
101    }
102
103    /// Parse format string to enum (for fixed formats only)
104    /// ExifTool: lib/Image/ExifTool.pm format name lookup
105    pub fn parse_format(format: &str) -> std::result::Result<Self, ExifError> {
106        match format {
107            "int8u" => Ok(BinaryDataFormat::Int8u),
108            "int8s" => Ok(BinaryDataFormat::Int8s),
109            "int16u" => Ok(BinaryDataFormat::Int16u),
110            "int16s" => Ok(BinaryDataFormat::Int16s),
111            "int32u" => Ok(BinaryDataFormat::Int32u),
112            "int32s" => Ok(BinaryDataFormat::Int32s),
113            "float" => Ok(BinaryDataFormat::Float),
114            "double" => Ok(BinaryDataFormat::Double),
115            "string" => Ok(BinaryDataFormat::String),
116            "pstring" => Ok(BinaryDataFormat::PString),
117            "undef" => Ok(BinaryDataFormat::Undef),
118            "var_string" => Ok(BinaryDataFormat::VarString),
119            _ => Err(ExifError::ParseError(format!(
120                "Unknown binary data format: {format}"
121            ))),
122        }
123    }
124}
125
126impl FormatSpec {
127    /// Parse format specification from string
128    /// ExifTool: lib/Image/ExifTool.pm:9850-9856 format parsing with expressions
129    pub fn parse(format_str: &str) -> std::result::Result<Self, ExifError> {
130        // Check for array format with expression: "format[$expression]"
131        if let Some(captures) = ARRAY_REGEX.captures(format_str) {
132            let base_format_str = captures.get(1).unwrap().as_str();
133            let count_expr = captures.get(2).unwrap().as_str().to_string();
134
135            if base_format_str == "string" {
136                // Special case: string[$expr] -> StringWithLength
137                Ok(FormatSpec::StringWithLength {
138                    length_expr: count_expr,
139                })
140            } else {
141                // Regular array format: int16s[$expr] -> Array
142                let base_format = BinaryDataFormat::parse_format(base_format_str)?;
143                Ok(FormatSpec::Array {
144                    base_format,
145                    count_expr,
146                })
147            }
148        } else {
149            // Fixed format without expressions
150            let format = BinaryDataFormat::parse_format(format_str)?;
151            Ok(FormatSpec::Fixed(format))
152        }
153    }
154
155    /// Check if this format spec contains expressions that need evaluation
156    pub fn needs_evaluation(&self) -> bool {
157        match self {
158            FormatSpec::Fixed(_) => false,
159            FormatSpec::Array { .. } => true,
160            FormatSpec::StringWithLength { .. } => true,
161        }
162    }
163}
164
165impl ResolvedFormat {
166    /// Get the byte size for extracting this format
167    pub fn byte_size(&self) -> usize {
168        match self {
169            ResolvedFormat::Single(format) => format.byte_size(),
170            ResolvedFormat::Array(format, count) => format.byte_size() * count,
171            ResolvedFormat::StringWithLength(length) => *length,
172            ResolvedFormat::VarString => 1, // Variable length, will scan for null
173        }
174    }
175
176    /// Get the base format for value extraction
177    pub fn base_format(&self) -> BinaryDataFormat {
178        match self {
179            ResolvedFormat::Single(format) => *format,
180            ResolvedFormat::Array(format, _) => *format,
181            ResolvedFormat::StringWithLength(_) => BinaryDataFormat::String,
182            ResolvedFormat::VarString => BinaryDataFormat::VarString,
183        }
184    }
185
186    /// Get the count for array formats (1 for single values)
187    pub fn count(&self) -> usize {
188        match self {
189            ResolvedFormat::Single(_) => 1,
190            ResolvedFormat::Array(_, count) => *count,
191            ResolvedFormat::StringWithLength(_) => 1,
192            ResolvedFormat::VarString => 1,
193        }
194    }
195}
196
197/// Expression evaluator for format specifications
198/// ExifTool: lib/Image/ExifTool.pm:9853-9856 eval mechanism for $val{} expressions
199pub struct ExpressionEvaluator<'a> {
200    /// Current $val hash - values from current binary data block
201    /// ExifTool: %val hash populated during ProcessBinaryData
202    val_hash: HashMap<u32, DataMemberValue>,
203    /// Global DataMember values from $$self{}
204    /// ExifTool: $$self{DataMember} values
205    data_members: &'a HashMap<String, DataMemberValue>,
206}
207
208impl<'a> ExpressionEvaluator<'a> {
209    /// Create new expression evaluator
210    pub fn new(
211        val_hash: HashMap<u32, DataMemberValue>,
212        data_members: &'a HashMap<String, DataMemberValue>,
213    ) -> Self {
214        Self {
215            val_hash,
216            data_members,
217        }
218    }
219
220    /// Evaluate a format expression to get a count value
221    /// ExifTool: lib/Image/ExifTool.pm:9853-9856 eval $count mechanism
222    pub fn evaluate_count_expression(&self, expr: &str) -> std::result::Result<usize, ExifError> {
223        // Handle complex expressions first (before simple $val patterns)
224        // ExifTool: Canon.pm:4480 'Format => int16s[int(($val{0}+15)/16)]'
225        if let Ok(result) = self.evaluate_complex_expression(expr) {
226            return Ok(result);
227        }
228
229        // Handle simple $val{N} references (only if complex expression failed)
230        if let Some(captures) = VAL_REGEX.captures(expr) {
231            let val_ref = captures.get(1).unwrap().as_str();
232
233            // Try parsing as index first
234            if let Ok(index) = val_ref.parse::<u32>() {
235                if let Some(value) = self.val_hash.get(&index) {
236                    return value.as_usize().ok_or_else(|| {
237                        ExifError::ParseError(format!(
238                            "Value at index {index} cannot be converted to count"
239                        ))
240                    });
241                }
242            }
243
244            // Try as DataMember name
245            if let Some(value) = self.data_members.get(val_ref) {
246                return value.as_usize().ok_or_else(|| {
247                    ExifError::ParseError(format!(
248                        "DataMember '{val_ref}' cannot be converted to count"
249                    ))
250                });
251            }
252
253            return Err(ExifError::ParseError(format!(
254                "Unknown value reference: {val_ref}"
255            )));
256        }
257
258        // If no patterns match, return error
259        Err(ExifError::ParseError(format!(
260            "Cannot evaluate expression: {expr}"
261        )))
262    }
263
264    /// Resolve a format specification by evaluating any expressions
265    pub fn resolve_format(
266        &self,
267        spec: &FormatSpec,
268    ) -> std::result::Result<ResolvedFormat, ExifError> {
269        match spec {
270            FormatSpec::Fixed(format) => Ok(ResolvedFormat::Single(*format)),
271            FormatSpec::Array {
272                base_format,
273                count_expr,
274            } => {
275                let count = self.evaluate_count_expression(count_expr)?;
276                Ok(ResolvedFormat::Array(*base_format, count))
277            }
278            FormatSpec::StringWithLength { length_expr } => {
279                let length = self.evaluate_count_expression(length_expr)?;
280                Ok(ResolvedFormat::StringWithLength(length))
281            }
282        }
283    }
284
285    /// Evaluate complex mathematical expressions
286    /// ExifTool: Complex expression evaluation like "int(($val{0}+15)/16)"
287    /// Reference: third-party/exiftool/lib/Image/ExifTool/Canon.pm:4480
288    pub fn evaluate_complex_expression(&self, expr: &str) -> std::result::Result<usize, ExifError> {
289        // Pattern for int(($val{N}+CONST)/DIVISOR) - ceiling division for bit arrays
290        // ExifTool: Canon.pm uses this pattern for AFPointsInFocus bit array sizing
291        static CEILING_DIV_REGEX: LazyLock<Regex> =
292            LazyLock::new(|| Regex::new(r"^int\(\(\$val\{(\d+)\}\+(\d+)\)/(\d+)\)$").unwrap());
293
294        if let Some(captures) = CEILING_DIV_REGEX.captures(expr) {
295            let val_index: u32 = captures.get(1).unwrap().as_str().parse().map_err(|_| {
296                ExifError::ParseError("Invalid value index in ceiling division".to_string())
297            })?;
298            let addend: usize = captures.get(2).unwrap().as_str().parse().map_err(|_| {
299                ExifError::ParseError("Invalid addend in ceiling division".to_string())
300            })?;
301            let divisor: usize = captures.get(3).unwrap().as_str().parse().map_err(|_| {
302                ExifError::ParseError("Invalid divisor in ceiling division".to_string())
303            })?;
304
305            if divisor == 0 {
306                return Err(ExifError::ParseError(
307                    "Division by zero in expression".to_string(),
308                ));
309            }
310
311            // Get the value from $val hash
312            let val = self.val_hash.get(&val_index).ok_or_else(|| {
313                ExifError::ParseError(format!("Value at index {val_index} not found"))
314            })?;
315
316            let val_usize = val.as_usize().ok_or_else(|| {
317                ExifError::ParseError(format!(
318                    "Value at index {val_index} cannot be converted to number"
319                ))
320            })?;
321
322            // Calculate ceiling division: int((val + addend) / divisor)
323            // This is equivalent to: (val + addend + divisor - 1) / divisor
324            let result = (val_usize + addend) / divisor;
325            return Ok(result);
326        }
327
328        // If no patterns match, return error
329        Err(ExifError::ParseError(format!(
330            "Unsupported complex expression: {expr}"
331        )))
332    }
333
334    /// Update the $val hash with a new value
335    pub fn set_val(&mut self, index: u32, value: DataMemberValue) {
336        self.val_hash.insert(index, value);
337    }
338}
339
340/// Binary data table configuration
341/// ExifTool: Tag table with PROCESS_PROC => \&ProcessBinaryData
342#[derive(Debug, Clone)]
343pub struct BinaryDataTable {
344    /// Default format for entries (ExifTool: FORMAT key)
345    pub default_format: BinaryDataFormat,
346    /// Starting index for unknown tag generation (ExifTool: FIRST_ENTRY key)
347    pub first_entry: Option<u32>,
348    /// Group hierarchy for tags (ExifTool: GROUPS key)
349    pub groups: HashMap<u8, String>,
350    /// Tag definitions indexed by position
351    pub tags: HashMap<u32, BinaryDataTag>,
352    /// Tags that are DataMembers and must be extracted first
353    /// ExifTool: DATAMEMBER => [...] array of indices
354    pub data_member_tags: Vec<u32>,
355    /// Processing order for tags with dependencies
356    /// Phase 1: DataMember tags, Phase 2: dependent tags
357    pub dependency_order: Vec<u32>,
358}
359
360/// Individual tag definition in binary data table
361/// ExifTool: Tag info hash structure
362#[derive(Debug, Clone)]
363pub struct BinaryDataTag {
364    /// Tag name
365    pub name: String,
366    /// Data format specification (may contain expressions)
367    /// ExifTool: Format field can be "int16s" or "int16s[$val{0}]"
368    pub format_spec: Option<FormatSpec>,
369    /// Legacy format field for backward compatibility
370    /// TODO: Remove once all code uses format_spec
371    pub format: Option<BinaryDataFormat>,
372    /// Bit mask for extracting value
373    pub mask: Option<u32>,
374    /// PrintConv lookup table
375    pub print_conv: Option<HashMap<u32, String>>,
376    /// DataMember name if this tag should be stored for later use
377    /// ExifTool: DataMember => 'Name' in tag definition
378    pub data_member: Option<String>,
379}
380
381impl Default for BinaryDataTable {
382    fn default() -> Self {
383        Self {
384            default_format: BinaryDataFormat::Int8u,
385            first_entry: None,
386            groups: HashMap::new(),
387            tags: HashMap::new(),
388            data_member_tags: Vec::new(),
389            dependency_order: Vec::new(),
390        }
391    }
392}
393
394impl BinaryDataTable {
395    /// Analyze table to identify DataMember dependencies and set processing order
396    /// ExifTool: Implicit dependency analysis during ProcessBinaryData
397    pub fn analyze_dependencies(&mut self) {
398        // Clear existing analysis
399        self.data_member_tags.clear();
400        self.dependency_order.clear();
401
402        // Phase 1: Identify DataMember tags
403        for (&index, tag) in &self.tags {
404            if tag.data_member.is_some() {
405                self.data_member_tags.push(index);
406            }
407        }
408
409        // Sort DataMember tags by index for consistent processing
410        self.data_member_tags.sort();
411
412        // Phase 2: Add DataMember tags to processing order first
413        self.dependency_order.extend(&self.data_member_tags);
414
415        // Phase 3: Add remaining tags that depend on DataMembers
416        let mut remaining_tags: Vec<u32> = self
417            .tags
418            .keys()
419            .filter(|&index| !self.data_member_tags.contains(index))
420            .copied()
421            .collect();
422
423        // Sort remaining tags by index
424        remaining_tags.sort();
425        self.dependency_order.extend(remaining_tags);
426    }
427
428    /// Check if a tag needs expression evaluation
429    pub fn tag_needs_evaluation(&self, index: u32) -> bool {
430        if let Some(tag) = self.tags.get(&index) {
431            if let Some(format_spec) = &tag.format_spec {
432                return format_spec.needs_evaluation();
433            }
434        }
435        false
436    }
437
438    /// Get effective format for a tag (format_spec takes precedence)
439    pub fn get_tag_format_spec(&self, index: u32) -> Option<FormatSpec> {
440        if let Some(tag) = self.tags.get(&index) {
441            if let Some(format_spec) = &tag.format_spec {
442                return Some(format_spec.clone());
443            }
444            if let Some(format) = &tag.format {
445                return Some(FormatSpec::Fixed(*format));
446            }
447            // Use table default
448            return Some(FormatSpec::Fixed(self.default_format));
449        }
450        None
451    }
452}