use crate::LibmagicError;
use crate::parser::ast::OffsetSpec;
pub fn resolve_relative_offset(
spec: &OffsetSpec,
buffer: &[u8],
last_match_end: usize,
) -> Result<usize, LibmagicError> {
debug_assert!(
matches!(spec, OffsetSpec::Relative(_)),
"resolve_relative_offset called with non-relative spec"
);
let OffsetSpec::Relative(delta) = spec else {
return Err(LibmagicError::EvaluationError(
crate::error::EvaluationError::internal_error(
"resolve_relative_offset called with non-relative spec",
),
));
};
let delta = *delta;
let delta_isize = isize::try_from(delta).map_err(|_| {
LibmagicError::EvaluationError(crate::error::EvaluationError::InvalidOffset {
offset: delta,
})
})?;
let target =
last_match_end
.checked_add_signed(delta_isize)
.ok_or(LibmagicError::EvaluationError(
crate::error::EvaluationError::InvalidOffset { offset: delta },
))?;
if target >= buffer.len() {
return Err(LibmagicError::EvaluationError(
crate::error::EvaluationError::BufferOverrun { offset: target },
));
}
Ok(target)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::EvaluationError;
fn unwrap_eval_err(err: LibmagicError) -> EvaluationError {
match err {
LibmagicError::EvaluationError(e) => e,
other => panic!("expected EvaluationError, got {other:?}"),
}
}
#[test]
fn positive_delta_resolves() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(3);
assert_eq!(resolve_relative_offset(&spec, buf, 4).unwrap(), 7);
}
#[test]
fn negative_delta_resolves() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(-3);
assert_eq!(resolve_relative_offset(&spec, buf, 8).unwrap(), 5);
}
#[test]
fn zero_delta_resolves_to_anchor() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(0);
assert_eq!(resolve_relative_offset(&spec, buf, 4).unwrap(), 4);
}
#[test]
fn top_level_anchor_zero_resolves_from_start() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(5);
assert_eq!(resolve_relative_offset(&spec, buf, 0).unwrap(), 5);
}
#[test]
fn top_level_anchor_zero_with_zero_delta_is_zero() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(0);
assert_eq!(resolve_relative_offset(&spec, buf, 0).unwrap(), 0);
}
#[test]
fn last_valid_index_resolves() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(0);
assert_eq!(resolve_relative_offset(&spec, buf, 15).unwrap(), 15);
}
#[test]
fn negative_delta_underflows_to_invalid_offset() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(-5);
let err = unwrap_eval_err(resolve_relative_offset(&spec, buf, 2).unwrap_err());
assert!(
matches!(err, EvaluationError::InvalidOffset { offset: -5 }),
"got {err:?}"
);
}
#[test]
fn positive_delta_overflows_to_invalid_offset() {
let buf = b"0123456789ABCDEF";
let spec = OffsetSpec::Relative(1);
let err = unwrap_eval_err(resolve_relative_offset(&spec, buf, usize::MAX).unwrap_err());
assert!(
matches!(err, EvaluationError::InvalidOffset { offset: 1 }),
"got {err:?}"
);
}
#[test]
fn target_past_buffer_end_returns_buffer_overrun() {
let buf = b"0123456789ABCDEF"; let spec = OffsetSpec::Relative(50);
let err = unwrap_eval_err(resolve_relative_offset(&spec, buf, 10).unwrap_err());
assert!(
matches!(err, EvaluationError::BufferOverrun { offset: 60 }),
"got {err:?}"
);
}
#[test]
fn target_equal_to_buffer_len_is_overrun() {
let buf = b"0123456789ABCDEF"; let spec = OffsetSpec::Relative(1);
let err = unwrap_eval_err(resolve_relative_offset(&spec, buf, 15).unwrap_err());
assert!(matches!(err, EvaluationError::BufferOverrun { offset: 16 }));
}
}