mod absolute;
mod indirect;
mod relative;
pub use absolute::{OffsetError, resolve_absolute_offset};
use crate::LibmagicError;
use crate::parser::ast::OffsetSpec;
pub(crate) fn map_offset_error(e: &OffsetError, original_offset: i64) -> LibmagicError {
match e {
OffsetError::BufferOverrun {
offset,
buffer_len: _,
} => LibmagicError::EvaluationError(crate::error::EvaluationError::BufferOverrun {
offset: *offset,
}),
OffsetError::InvalidOffset { reason: _ } | OffsetError::ArithmeticOverflow => {
LibmagicError::EvaluationError(crate::error::EvaluationError::InvalidOffset {
offset: original_offset,
})
}
}
}
pub fn resolve_offset(spec: &OffsetSpec, buffer: &[u8]) -> Result<usize, LibmagicError> {
resolve_offset_with_context(spec, buffer, 0)
}
pub(crate) fn resolve_offset_with_context(
spec: &OffsetSpec,
buffer: &[u8],
last_match_end: usize,
) -> Result<usize, LibmagicError> {
resolve_offset_with_base(spec, buffer, last_match_end, 0)
}
pub(crate) fn resolve_offset_with_base(
spec: &OffsetSpec,
buffer: &[u8],
last_match_end: usize,
base_offset: usize,
) -> Result<usize, LibmagicError> {
match spec {
OffsetSpec::Absolute(offset) => {
let effective = if *offset >= 0 {
let abs = usize::try_from(*offset).map_err(|_| {
LibmagicError::EvaluationError(crate::error::EvaluationError::InvalidOffset {
offset: *offset,
})
})?;
let biased = base_offset
.checked_add(abs)
.ok_or(LibmagicError::EvaluationError(
crate::error::EvaluationError::InvalidOffset { offset: *offset },
))?;
i64::try_from(biased).map_err(|_| {
LibmagicError::EvaluationError(crate::error::EvaluationError::InvalidOffset {
offset: *offset,
})
})?
} else {
*offset
};
resolve_absolute_offset(effective, buffer).map_err(|e| map_offset_error(&e, effective))
}
OffsetSpec::Indirect { .. } => {
indirect::resolve_indirect_offset_with_anchor(spec, buffer, Some(last_match_end))
}
OffsetSpec::Relative(_) => relative::resolve_relative_offset(spec, buffer, last_match_end),
OffsetSpec::FromEnd(offset) => {
resolve_absolute_offset(*offset, buffer).map_err(|e| map_offset_error(&e, *offset))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_offset_absolute() {
let buffer = b"Test data for offset resolution";
let spec = OffsetSpec::Absolute(5);
let result = resolve_offset(&spec, buffer).unwrap();
assert_eq!(result, 5);
}
#[test]
fn test_resolve_offset_absolute_negative() {
let buffer = b"Test data";
let spec = OffsetSpec::Absolute(-4);
let result = resolve_offset(&spec, buffer).unwrap();
assert_eq!(result, 5); }
#[test]
fn test_resolve_offset_from_end() {
let buffer = b"Test data";
let spec = OffsetSpec::FromEnd(-3);
let result = resolve_offset(&spec, buffer).unwrap();
assert_eq!(result, 6); }
#[test]
fn test_resolve_offset_absolute_out_of_bounds() {
let buffer = b"Short";
let spec = OffsetSpec::Absolute(10);
let result = resolve_offset(&spec, buffer);
assert!(result.is_err());
match result.unwrap_err() {
LibmagicError::EvaluationError(crate::error::EvaluationError::BufferOverrun {
..
}) => {
}
_ => panic!("Expected EvaluationError with BufferOverrun"),
}
}
#[test]
fn test_resolve_offset_indirect_success() {
let buffer = b"\x05TestXdata";
let spec = OffsetSpec::Indirect {
base_offset: 0,
base_relative: false,
pointer_type: crate::parser::ast::TypeKind::Byte { signed: false },
adjustment: 0,
adjustment_op: crate::parser::ast::IndirectAdjustmentOp::Add,
result_relative: false,
endian: crate::parser::ast::Endianness::Little,
};
let result = resolve_offset(&spec, buffer).unwrap();
assert_eq!(result, 5);
}
#[test]
fn test_resolve_offset_relative_via_context() {
let buffer = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(3);
let resolved = resolve_offset_with_context(&spec, buffer, 4).unwrap();
assert_eq!(resolved, 7);
}
#[test]
fn test_resolve_offset_relative_top_level_default() {
let buffer = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(5);
assert_eq!(resolve_offset(&spec, buffer).unwrap(), 5);
}
#[test]
fn test_resolve_offset_with_context_passthrough_absolute() {
let buffer = b"Test data";
let spec = OffsetSpec::Absolute(4);
assert_eq!(resolve_offset_with_context(&spec, buffer, 100).unwrap(), 4);
}
#[test]
fn test_resolve_offset_with_context_passthrough_from_end() {
let buffer = b"Test data";
let spec = OffsetSpec::FromEnd(-3);
assert_eq!(resolve_offset_with_context(&spec, buffer, 999).unwrap(), 6);
}
#[test]
fn test_resolve_offset_with_context_passthrough_indirect() {
let buffer = b"\x05TestXdata";
let spec = OffsetSpec::Indirect {
base_offset: 0,
base_relative: false,
pointer_type: crate::parser::ast::TypeKind::Byte { signed: false },
adjustment: 0,
adjustment_op: crate::parser::ast::IndirectAdjustmentOp::Add,
result_relative: false,
endian: crate::parser::ast::Endianness::Little,
};
assert_eq!(resolve_offset_with_context(&spec, buffer, 42).unwrap(), 5);
}
#[test]
fn test_resolve_offset_with_base_biases_positive_absolute() {
let buffer = b"0123456789ABCDEF";
let spec = OffsetSpec::Absolute(4);
assert_eq!(
resolve_offset_with_base(&spec, buffer, 0, 10).unwrap(),
14,
"positive Absolute must be biased by base_offset inside a subroutine"
);
}
#[test]
fn test_resolve_offset_with_base_does_not_bias_negative_absolute() {
let buffer = b"0123456789ABCDEF";
let spec = OffsetSpec::Absolute(-4);
assert_eq!(
resolve_offset_with_base(&spec, buffer, 0, 10).unwrap(),
12,
"negative Absolute must NOT be biased"
);
}
#[test]
fn test_resolve_offset_with_base_does_not_bias_from_end() {
let buffer = b"0123456789ABCDEF";
let spec = OffsetSpec::FromEnd(-4);
assert_eq!(
resolve_offset_with_base(&spec, buffer, 0, 10).unwrap(),
12,
"FromEnd must NOT be biased"
);
}
#[test]
fn test_resolve_offset_with_base_does_not_bias_relative() {
let buffer = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(3);
assert_eq!(
resolve_offset_with_base(&spec, buffer, 2, 10).unwrap(),
5,
"Relative must NOT be biased (already resolved against last_match_end)"
);
}
#[test]
fn test_resolve_offset_with_base_does_not_bias_indirect() {
let buffer = b"\x05TestXdata";
let spec = OffsetSpec::Indirect {
base_offset: 0,
base_relative: false,
pointer_type: crate::parser::ast::TypeKind::Byte { signed: false },
adjustment: 0,
adjustment_op: crate::parser::ast::IndirectAdjustmentOp::Add,
result_relative: false,
endian: crate::parser::ast::Endianness::Little,
};
assert_eq!(
resolve_offset_with_base(&spec, buffer, 0, 10).unwrap(),
5,
"Indirect must NOT be biased"
);
}
#[test]
fn test_resolve_offset_comprehensive() {
let buffer = b"0123456789ABCDEF";
let test_cases = vec![
(OffsetSpec::Absolute(0), 0),
(OffsetSpec::Absolute(8), 8),
(OffsetSpec::Absolute(15), 15),
(OffsetSpec::Absolute(-1), 15),
(OffsetSpec::Absolute(-8), 8),
(OffsetSpec::Absolute(-16), 0),
(OffsetSpec::FromEnd(-1), 15),
(OffsetSpec::FromEnd(-8), 8),
(OffsetSpec::FromEnd(-16), 0),
];
for (spec, expected) in test_cases {
let result = resolve_offset(&spec, buffer).unwrap();
assert_eq!(result, expected, "Failed for spec: {spec:?}");
}
}
#[test]
fn test_resolve_offset_with_base_overflow_yields_invalid_offset() {
let buffer = b"0123456789ABCDEF"; let base = usize::MAX - 1;
let spec = OffsetSpec::Absolute(2);
let result = resolve_offset_with_base(&spec, buffer, 0, base);
assert!(
result.is_err(),
"overflow of base_offset + absolute must fail"
);
match result.unwrap_err() {
LibmagicError::EvaluationError(crate::error::EvaluationError::InvalidOffset {
..
}) => {
}
LibmagicError::EvaluationError(crate::error::EvaluationError::BufferOverrun {
..
}) => {
panic!(
"overflow of base_offset + absolute must be InvalidOffset, not BufferOverrun"
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
}