use core::fmt;
use core::mem::{align_of, size_of};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SafetyError {
BufferTooSmall {
needed: usize,
actual: usize,
},
Misaligned {
needed: usize,
actual: usize,
},
OffsetOutOfBounds {
offset: usize,
length: usize,
},
Overflow,
}
impl fmt::Display for SafetyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BufferTooSmall { needed, actual } => {
write!(
f,
"buffer too small: needed {} bytes, got {}",
needed, actual
)
}
Self::Misaligned { needed, actual } => {
write!(
f,
"pointer misaligned: needed {} byte alignment, offset is {}",
needed, actual
)
}
Self::OffsetOutOfBounds { offset, length } => {
write!(f, "offset {} out of bounds for length {}", offset, length)
}
Self::Overflow => write!(f, "integer overflow in size calculation"),
}
}
}
impl std::error::Error for SafetyError {}
#[inline(always)]
pub fn check_size<T>(slice: &[u8]) -> Result<(), SafetyError> {
let needed = size_of::<T>();
if slice.len() < needed {
Err(SafetyError::BufferTooSmall {
needed,
actual: slice.len(),
})
} else {
Ok(())
}
}
#[inline(always)]
pub fn check_bounds(offset: usize, size: usize, length: usize) -> Result<(), SafetyError> {
match offset.checked_add(size) {
Some(end) if end <= length => Ok(()),
Some(_) => Err(SafetyError::OffsetOutOfBounds { offset, length }),
None => Err(SafetyError::Overflow),
}
}
#[inline(always)]
pub fn check_slice_bounds<T>(
offset: usize,
count: usize,
length: usize,
) -> Result<(), SafetyError> {
let size = size_of::<T>()
.checked_mul(count)
.ok_or(SafetyError::Overflow)?;
check_bounds(offset, size, length)
}
#[inline(always)]
pub fn check_alignment<T>(ptr: *const u8) -> Result<(), SafetyError> {
let needed = align_of::<T>();
let actual = ptr as usize % needed;
if actual != 0 {
Err(SafetyError::Misaligned { needed, actual })
} else {
Ok(())
}
}
#[inline(always)]
pub fn check_cast<T>(slice: &[u8]) -> Result<(), SafetyError> {
check_size::<T>(slice)?;
check_alignment::<T>(slice.as_ptr())?;
Ok(())
}
#[inline]
#[allow(unsafe_code)]
pub fn safe_read<T: Copy>(slice: &[u8]) -> Result<&T, SafetyError> {
check_cast::<T>(slice)?;
Ok(unsafe { &*(slice.as_ptr() as *const T) })
}
#[inline]
#[allow(unsafe_code)]
pub fn safe_read_slice<T: Copy>(slice: &[u8], count: usize) -> Result<&[T], SafetyError> {
check_slice_bounds::<T>(0, count, slice.len())?;
check_alignment::<T>(slice.as_ptr())?;
Ok(unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const T, count) })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_size_success() {
let buffer = [0u8; 16];
assert!(check_size::<u8>(&buffer).is_ok());
assert!(check_size::<u16>(&buffer).is_ok());
assert!(check_size::<u32>(&buffer).is_ok());
assert!(check_size::<u64>(&buffer).is_ok());
assert!(check_size::<u128>(&buffer).is_ok());
}
#[test]
fn test_check_size_failure() {
let buffer = [0u8; 4];
assert!(matches!(
check_size::<u64>(&buffer),
Err(SafetyError::BufferTooSmall {
needed: 8,
actual: 4
})
));
}
#[test]
fn test_check_bounds_success() {
assert!(check_bounds(0, 4, 8).is_ok());
assert!(check_bounds(4, 4, 8).is_ok());
assert!(check_bounds(0, 8, 8).is_ok());
assert!(check_bounds(0, 0, 0).is_ok());
}
#[test]
fn test_check_bounds_failure() {
assert!(matches!(
check_bounds(5, 4, 8),
Err(SafetyError::OffsetOutOfBounds {
offset: 5,
length: 8
})
));
}
#[test]
fn test_check_bounds_overflow() {
assert!(matches!(
check_bounds(usize::MAX, 1, 100),
Err(SafetyError::Overflow)
));
}
#[test]
fn test_check_alignment_success() {
let aligned: [u64; 2] = [0, 0];
let ptr = aligned.as_ptr() as *const u8;
assert!(check_alignment::<u64>(ptr).is_ok());
assert!(check_alignment::<u32>(ptr).is_ok());
assert!(check_alignment::<u16>(ptr).is_ok());
assert!(check_alignment::<u8>(ptr).is_ok());
}
#[test]
fn test_check_alignment_failure() {
let aligned: [u64; 2] = [0, 0];
let ptr = aligned.as_ptr() as *const u8;
let misaligned = ptr.wrapping_add(1);
let result = check_alignment::<u64>(misaligned);
assert!(matches!(
result,
Err(SafetyError::Misaligned {
needed: 8,
actual: 1
})
));
}
#[test]
fn test_safe_read() {
let value: u64 = 0x1234567890ABCDEF;
let bytes = value.to_le_bytes();
let read_value: &u64 = safe_read(&bytes).unwrap();
assert_eq!(*read_value, value);
}
#[test]
fn test_safe_read_slice() {
let values: [u32; 4] = [1, 2, 3, 4];
#[repr(align(4))]
struct AlignedBytes([u8; 16]);
let mut bytes = AlignedBytes([0; 16]);
for (chunk, value) in bytes.0.chunks_exact_mut(4).zip(values) {
chunk.copy_from_slice(&value.to_ne_bytes());
}
let read_values: &[u32] = safe_read_slice(&bytes.0, 4).unwrap();
assert_eq!(read_values, &[1, 2, 3, 4]);
}
#[test]
fn test_error_display() {
let err = SafetyError::BufferTooSmall {
needed: 8,
actual: 4,
};
assert_eq!(err.to_string(), "buffer too small: needed 8 bytes, got 4");
let err = SafetyError::Misaligned {
needed: 8,
actual: 3,
};
assert_eq!(
err.to_string(),
"pointer misaligned: needed 8 byte alignment, offset is 3"
);
let err = SafetyError::OffsetOutOfBounds {
offset: 10,
length: 8,
};
assert_eq!(err.to_string(), "offset 10 out of bounds for length 8");
let err = SafetyError::Overflow;
assert_eq!(err.to_string(), "integer overflow in size calculation");
}
}