Skip to main content

libmagic_rs/evaluator/offset/
mod.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Offset resolution for magic rule evaluation
5//!
6//! This module provides functions for resolving different types of offset specifications
7//! into absolute byte positions within file buffers, with proper bounds checking.
8
9mod absolute;
10mod indirect;
11mod relative;
12
13pub use absolute::{OffsetError, resolve_absolute_offset};
14
15use crate::LibmagicError;
16use crate::parser::ast::OffsetSpec;
17
18/// Map an `OffsetError` to a `LibmagicError` for a given original offset value
19fn map_offset_error(e: &OffsetError, original_offset: i64) -> LibmagicError {
20    match e {
21        OffsetError::BufferOverrun {
22            offset,
23            buffer_len: _,
24        } => LibmagicError::EvaluationError(crate::error::EvaluationError::BufferOverrun {
25            offset: *offset,
26        }),
27        OffsetError::InvalidOffset { reason: _ } | OffsetError::ArithmeticOverflow => {
28            LibmagicError::EvaluationError(crate::error::EvaluationError::InvalidOffset {
29                offset: original_offset,
30            })
31        }
32    }
33}
34
35/// Resolve any offset specification to an absolute position
36///
37/// This is a higher-level function that handles all types of offset specifications.
38/// Currently only supports absolute offsets, but will be extended to handle indirect,
39/// relative, and from-end offsets in future tasks.
40///
41/// # Arguments
42///
43/// * `spec` - The offset specification to resolve
44/// * `buffer` - The file buffer to resolve against
45///
46/// # Returns
47///
48/// Returns the resolved absolute offset as a `usize`, or a `LibmagicError` if resolution fails.
49///
50/// # Examples
51///
52/// ```rust
53/// use libmagic_rs::evaluator::offset::resolve_offset;
54/// use libmagic_rs::parser::ast::OffsetSpec;
55///
56/// let buffer = b"Test data";
57/// let spec = OffsetSpec::Absolute(4);
58///
59/// let offset = resolve_offset(&spec, buffer).unwrap();
60/// assert_eq!(offset, 4);
61/// ```
62///
63/// # Errors
64///
65/// * `LibmagicError::EvaluationError` - If offset resolution fails
66pub fn resolve_offset(spec: &OffsetSpec, buffer: &[u8]) -> Result<usize, LibmagicError> {
67    match spec {
68        OffsetSpec::Absolute(offset) => {
69            resolve_absolute_offset(*offset, buffer).map_err(|e| map_offset_error(&e, *offset))
70        }
71        OffsetSpec::Indirect { .. } => indirect::resolve_indirect_offset(spec, buffer),
72        OffsetSpec::Relative(_) => relative::resolve_relative_offset(spec, buffer),
73        OffsetSpec::FromEnd(offset) => {
74            // FromEnd is handled the same as negative Absolute offsets
75            resolve_absolute_offset(*offset, buffer).map_err(|e| map_offset_error(&e, *offset))
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_resolve_offset_absolute() {
86        let buffer = b"Test data for offset resolution";
87        let spec = OffsetSpec::Absolute(5);
88
89        let result = resolve_offset(&spec, buffer).unwrap();
90        assert_eq!(result, 5);
91    }
92
93    #[test]
94    fn test_resolve_offset_absolute_negative() {
95        let buffer = b"Test data";
96        let spec = OffsetSpec::Absolute(-4);
97
98        let result = resolve_offset(&spec, buffer).unwrap();
99        assert_eq!(result, 5); // 9 - 4 = 5
100    }
101
102    #[test]
103    fn test_resolve_offset_from_end() {
104        let buffer = b"Test data";
105        let spec = OffsetSpec::FromEnd(-3);
106
107        let result = resolve_offset(&spec, buffer).unwrap();
108        assert_eq!(result, 6); // 9 - 3 = 6
109    }
110
111    #[test]
112    fn test_resolve_offset_absolute_out_of_bounds() {
113        let buffer = b"Short";
114        let spec = OffsetSpec::Absolute(10);
115
116        let result = resolve_offset(&spec, buffer);
117        assert!(result.is_err());
118
119        match result.unwrap_err() {
120            LibmagicError::EvaluationError(crate::error::EvaluationError::BufferOverrun {
121                ..
122            }) => {
123                // Expected error type
124            }
125            _ => panic!("Expected EvaluationError with BufferOverrun"),
126        }
127    }
128
129    #[test]
130    fn test_resolve_offset_indirect_not_implemented() {
131        let buffer = b"Test data";
132        let spec = OffsetSpec::Indirect {
133            base_offset: 0,
134            pointer_type: crate::parser::ast::TypeKind::Byte { signed: true },
135            adjustment: 0,
136            endian: crate::parser::ast::Endianness::Little,
137        };
138
139        let result = resolve_offset(&spec, buffer);
140        assert!(result.is_err());
141
142        match result.unwrap_err() {
143            LibmagicError::EvaluationError(crate::error::EvaluationError::UnsupportedType {
144                type_name,
145            }) => {
146                assert!(type_name.contains("Indirect offsets not yet implemented"));
147            }
148            _ => panic!("Expected EvaluationError with UnsupportedType"),
149        }
150    }
151
152    #[test]
153    fn test_resolve_offset_relative_not_implemented() {
154        let buffer = b"Test data";
155        let spec = OffsetSpec::Relative(4);
156
157        let result = resolve_offset(&spec, buffer);
158        assert!(result.is_err());
159
160        match result.unwrap_err() {
161            LibmagicError::EvaluationError(crate::error::EvaluationError::UnsupportedType {
162                type_name,
163            }) => {
164                assert!(type_name.contains("Relative offsets not yet implemented"));
165            }
166            _ => panic!("Expected EvaluationError with UnsupportedType"),
167        }
168    }
169
170    #[test]
171    fn test_resolve_offset_comprehensive() {
172        let buffer = b"0123456789ABCDEF";
173
174        // Test various absolute offsets
175        let test_cases = vec![
176            (OffsetSpec::Absolute(0), 0),
177            (OffsetSpec::Absolute(8), 8),
178            (OffsetSpec::Absolute(15), 15),
179            (OffsetSpec::Absolute(-1), 15),
180            (OffsetSpec::Absolute(-8), 8),
181            (OffsetSpec::Absolute(-16), 0),
182            (OffsetSpec::FromEnd(-1), 15),
183            (OffsetSpec::FromEnd(-8), 8),
184            (OffsetSpec::FromEnd(-16), 0),
185        ];
186
187        for (spec, expected) in test_cases {
188            let result = resolve_offset(&spec, buffer).unwrap();
189            assert_eq!(result, expected, "Failed for spec: {spec:?}");
190        }
191    }
192}