1use crate::types::{ExifError, Result};
10use std::collections::HashMap;
11
12pub struct EntryBasedOffsetProcessor {
16 offset_rules: HashMap<u16, OffsetExtractionRule>,
19}
20
21#[derive(Debug, Clone)]
24pub struct OffsetExtractionRule {
25 pub tag_id: u16,
28
29 pub offset_field: OffsetField,
32
33 pub base: OffsetBase,
36
37 pub additional_offset: i32,
40}
41
42#[derive(Debug, Clone, Copy)]
45pub enum OffsetField {
46 ValueOffset,
49
50 ActualValue,
53}
54
55#[derive(Debug, Clone, Copy)]
58pub enum OffsetBase {
59 FileStart,
62
63 IfdStart,
66
67 MakerNoteStart,
70
71 DataPosition,
74}
75
76#[derive(Debug, Clone)]
79pub struct IfdEntry {
80 pub tag: u16,
83
84 pub data_type: u16,
87
88 pub count: u32,
91
92 pub value_offset: u32,
95
96 pub entry_position: u64,
99}
100
101impl EntryBasedOffsetProcessor {
102 pub fn new(offset_rules: HashMap<u16, OffsetExtractionRule>) -> Self {
105 Self { offset_rules }
106 }
107
108 pub fn get_rule(&self, tag_id: u16) -> Option<&OffsetExtractionRule> {
111 self.offset_rules.get(&tag_id)
112 }
113
114 pub fn calculate_offset(
117 &self,
118 entry: &IfdEntry,
119 rule: &OffsetExtractionRule,
120 context: &OffsetContext,
121 ) -> Result<u64> {
122 let raw_offset = match rule.offset_field {
125 OffsetField::ValueOffset => entry.value_offset as i64,
126 OffsetField::ActualValue => {
127 entry.value_offset as i64
130 }
131 };
132
133 let base_offset = match rule.base {
136 OffsetBase::FileStart => 0i64,
137 OffsetBase::IfdStart => context.ifd_start as i64,
138 OffsetBase::MakerNoteStart => context.maker_note_start as i64,
139 OffsetBase::DataPosition => context.current_position as i64,
140 };
141
142 let final_offset = base_offset + raw_offset + rule.additional_offset as i64;
145
146 if final_offset < 0 {
147 return Err(ExifError::ParseError(format!(
148 "Calculated negative offset for tag 0x{:04x}: {}",
149 entry.tag, final_offset
150 )));
151 }
152
153 Ok(final_offset as u64)
154 }
155
156 pub fn add_rule(&mut self, rule: OffsetExtractionRule) {
159 self.offset_rules.insert(rule.tag_id, rule);
160 }
161
162 pub fn remove_rule(&mut self, tag_id: u16) -> Option<OffsetExtractionRule> {
165 self.offset_rules.remove(&tag_id)
166 }
167
168 pub fn get_configured_tags(&self) -> Vec<u16> {
171 self.offset_rules.keys().copied().collect()
172 }
173}
174
175#[derive(Debug, Clone)]
178pub struct OffsetContext {
179 pub ifd_start: u64,
182
183 pub maker_note_start: u64,
186
187 pub current_position: u64,
190
191 pub file_size: u64,
194}
195
196impl OffsetContext {
197 pub fn new(
199 ifd_start: u64,
200 maker_note_start: u64,
201 current_position: u64,
202 file_size: u64,
203 ) -> Self {
204 Self {
205 ifd_start,
206 maker_note_start,
207 current_position,
208 file_size,
209 }
210 }
211
212 pub fn validate_offset(&self, offset: u64, data_size: u64) -> Result<()> {
215 if offset >= self.file_size {
216 return Err(ExifError::ParseError(format!(
217 "Offset {} exceeds file size {}",
218 offset, self.file_size
219 )));
220 }
221
222 if offset + data_size > self.file_size {
223 return Err(ExifError::ParseError(format!(
224 "Data at offset {offset} with size {data_size} exceeds file bounds"
225 )));
226 }
227
228 Ok(())
229 }
230}
231
232pub struct SimpleOffsetProcessor {
235 base_offset: u64,
238}
239
240impl SimpleOffsetProcessor {
241 pub fn new(base_offset: u64) -> Self {
244 Self { base_offset }
245 }
246
247 pub fn calculate_offset(&self, relative_offset: u64) -> u64 {
250 self.base_offset + relative_offset
251 }
252
253 pub fn set_base_offset(&mut self, base_offset: u64) {
256 self.base_offset = base_offset;
257 }
258
259 pub fn get_base_offset(&self) -> u64 {
262 self.base_offset
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_entry_based_offset_processor() {
272 let rule = OffsetExtractionRule {
274 tag_id: 0x002e,
275 offset_field: OffsetField::ActualValue,
276 base: OffsetBase::MakerNoteStart,
277 additional_offset: 0,
278 };
279
280 let mut rules = HashMap::new();
281 rules.insert(0x002e, rule);
282
283 let processor = EntryBasedOffsetProcessor::new(rules);
284
285 assert!(processor.get_rule(0x002e).is_some());
287 assert!(processor.get_rule(0x0000).is_none());
288
289 let tags = processor.get_configured_tags();
291 assert_eq!(tags.len(), 1);
292 assert!(tags.contains(&0x002e));
293 }
294
295 #[test]
296 fn test_offset_calculation() {
297 let rule = OffsetExtractionRule {
298 tag_id: 0x002e,
299 offset_field: OffsetField::ActualValue,
300 base: OffsetBase::MakerNoteStart,
301 additional_offset: 10,
302 };
303
304 let mut rules = HashMap::new();
305 rules.insert(0x002e, rule);
306
307 let processor = EntryBasedOffsetProcessor::new(rules);
308
309 let entry = IfdEntry {
310 tag: 0x002e,
311 data_type: 4, count: 1,
313 value_offset: 0x1000,
314 entry_position: 0x500,
315 };
316
317 let context = OffsetContext::new(0x400, 0x800, 0x500, 0x10000);
318
319 let offset = processor
320 .calculate_offset(&entry, processor.get_rule(0x002e).unwrap(), &context)
321 .unwrap();
322
323 assert_eq!(offset, 0x180A);
325 }
326
327 #[test]
328 fn test_offset_context_validation() {
329 let context = OffsetContext::new(0x400, 0x800, 0x500, 0x10000);
330
331 assert!(context.validate_offset(0x1000, 0x100).is_ok());
333
334 assert!(context.validate_offset(0x20000, 0x100).is_err());
336
337 assert!(context.validate_offset(0xFF00, 0x200).is_err());
339 }
340
341 #[test]
342 fn test_simple_offset_processor() {
343 let mut processor = SimpleOffsetProcessor::new(0x1000);
344
345 assert_eq!(processor.calculate_offset(0x100), 0x1100);
346 assert_eq!(processor.get_base_offset(), 0x1000);
347
348 processor.set_base_offset(0x2000);
349 assert_eq!(processor.calculate_offset(0x100), 0x2100);
350 assert_eq!(processor.get_base_offset(), 0x2000);
351 }
352
353 #[test]
354 fn test_offset_field_types() {
355 let rule_value_offset = OffsetExtractionRule {
356 tag_id: 0x0001,
357 offset_field: OffsetField::ValueOffset,
358 base: OffsetBase::FileStart,
359 additional_offset: 0,
360 };
361
362 let rule_actual_value = OffsetExtractionRule {
363 tag_id: 0x0002,
364 offset_field: OffsetField::ActualValue,
365 base: OffsetBase::FileStart,
366 additional_offset: 0,
367 };
368
369 let entry = IfdEntry {
371 tag: 0x0001,
372 data_type: 4,
373 count: 1,
374 value_offset: 0x1234,
375 entry_position: 0x500,
376 };
377
378 let context = OffsetContext::new(0, 0, 0, 0x10000);
379
380 let mut rules = HashMap::new();
381 rules.insert(0x0001, rule_value_offset);
382 rules.insert(0x0002, rule_actual_value);
383
384 let processor = EntryBasedOffsetProcessor::new(rules);
385
386 let offset1 = processor
387 .calculate_offset(&entry, processor.get_rule(0x0001).unwrap(), &context)
388 .unwrap();
389 let offset2 = processor
390 .calculate_offset(&entry, processor.get_rule(0x0002).unwrap(), &context)
391 .unwrap();
392
393 assert_eq!(offset1, 0x1234);
395 assert_eq!(offset2, 0x1234);
396 }
397}