use crate::error::{corrupt, DbResult};
pub fn window(source: &[u8], offset: usize, len: usize) -> DbResult<&[u8]> {
let end = offset
.checked_add(len)
.ok_or_else(|| corrupt("byte window offset overflowed"))?;
source
.get(offset..end)
.ok_or_else(|| corrupt("byte window reaches past the end of the buffer"))
}
pub fn window_mut(target: &mut [u8], offset: usize, len: usize) -> DbResult<&mut [u8]> {
let end = offset
.checked_add(len)
.ok_or_else(|| corrupt("byte window offset overflowed"))?;
target
.get_mut(offset..end)
.ok_or_else(|| corrupt("byte window reaches past the end of the buffer"))
}
pub fn read_u8(source: &[u8], offset: usize) -> DbResult<u8> {
let bytes = window(source, offset, 1)?;
match bytes.first() {
Some(byte) => Ok(*byte),
None => Err(corrupt("one-byte window was empty")),
}
}
pub fn read_u16(source: &[u8], offset: usize) -> DbResult<u16> {
let bytes = window(source, offset, 2)?;
Ok(fold_be(bytes) as u16)
}
pub fn read_u24(source: &[u8], offset: usize) -> DbResult<u32> {
let bytes = window(source, offset, 3)?;
Ok(fold_be(bytes) as u32)
}
pub fn read_u32(source: &[u8], offset: usize) -> DbResult<u32> {
let bytes = window(source, offset, 4)?;
Ok(fold_be(bytes) as u32)
}
pub fn read_u48(source: &[u8], offset: usize) -> DbResult<u64> {
let bytes = window(source, offset, 6)?;
Ok(fold_be(bytes))
}
pub fn read_u64(source: &[u8], offset: usize) -> DbResult<u64> {
let bytes = window(source, offset, 8)?;
Ok(fold_be(bytes))
}
pub fn read_i64(source: &[u8], offset: usize) -> DbResult<i64> {
Ok(read_u64(source, offset)? as i64)
}
pub fn read_f64(source: &[u8], offset: usize) -> DbResult<f64> {
Ok(f64::from_bits(read_u64(source, offset)?))
}
fn fold_be(bytes: &[u8]) -> u64 {
let mut value: u64 = 0;
for byte in bytes.iter().take(8) {
value = value.wrapping_shl(8) | u64::from(*byte);
}
value
}
pub fn write_u8(target: &mut [u8], offset: usize, value: u8) -> DbResult<()> {
let slot = window_mut(target, offset, 1)?;
match slot.first_mut() {
Some(byte) => {
*byte = value;
Ok(())
}
None => Err(corrupt("one-byte window was empty")),
}
}
pub fn write_u16(target: &mut [u8], offset: usize, value: u16) -> DbResult<()> {
spread_be(target, offset, 2, u64::from(value))
}
pub fn write_u24(target: &mut [u8], offset: usize, value: u32) -> DbResult<()> {
if value > 0x00ff_ffff {
return Err(corrupt("value does not fit in a 24-bit field"));
}
spread_be(target, offset, 3, u64::from(value))
}
pub fn write_u32(target: &mut [u8], offset: usize, value: u32) -> DbResult<()> {
spread_be(target, offset, 4, u64::from(value))
}
pub fn write_u48(target: &mut [u8], offset: usize, value: u64) -> DbResult<()> {
if value > 0x0000_ffff_ffff_ffff {
return Err(corrupt("value does not fit in a 48-bit field"));
}
spread_be(target, offset, 6, value)
}
pub fn write_u64(target: &mut [u8], offset: usize, value: u64) -> DbResult<()> {
spread_be(target, offset, 8, value)
}
pub fn write_i64(target: &mut [u8], offset: usize, value: i64) -> DbResult<()> {
write_u64(target, offset, value as u64)
}
pub fn write_f64(target: &mut [u8], offset: usize, value: f64) -> DbResult<()> {
write_u64(target, offset, value.to_bits())
}
fn spread_be(target: &mut [u8], offset: usize, width: usize, value: u64) -> DbResult<()> {
let slot = window_mut(target, offset, width)?;
for (index, byte) in slot.iter_mut().enumerate() {
let shift = width
.checked_sub(index)
.and_then(|remaining| remaining.checked_sub(1))
.and_then(|remaining| remaining.checked_mul(8))
.ok_or_else(|| corrupt("byte shift overflowed"))?;
*byte = ((value.wrapping_shr(shift as u32)) & 0xff) as u8;
}
Ok(())
}
#[derive(Clone, Debug)]
pub struct ByteReader<'a> {
source: &'a [u8],
offset: usize,
}
impl<'a> ByteReader<'a> {
pub fn new(source: &'a [u8]) -> ByteReader<'a> {
ByteReader { source, offset: 0 }
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn remaining(&self) -> usize {
self.source.len().saturating_sub(self.offset)
}
pub fn rest(&self) -> &'a [u8] {
self.source.get(self.offset..).unwrap_or(&[])
}
pub fn take(&mut self, len: usize) -> DbResult<&'a [u8]> {
let bytes = window(self.source, self.offset, len)?;
self.offset = self
.offset
.checked_add(len)
.ok_or_else(|| corrupt("reader offset overflowed"))?;
Ok(bytes)
}
pub fn u8(&mut self) -> DbResult<u8> {
let value = read_u8(self.source, self.offset)?;
self.advance(1)?;
Ok(value)
}
pub fn u16(&mut self) -> DbResult<u16> {
let value = read_u16(self.source, self.offset)?;
self.advance(2)?;
Ok(value)
}
pub fn u32(&mut self) -> DbResult<u32> {
let value = read_u32(self.source, self.offset)?;
self.advance(4)?;
Ok(value)
}
pub fn u64(&mut self) -> DbResult<u64> {
let value = read_u64(self.source, self.offset)?;
self.advance(8)?;
Ok(value)
}
fn advance(&mut self, len: usize) -> DbResult<()> {
let next = self
.offset
.checked_add(len)
.ok_or_else(|| corrupt("reader offset overflowed"))?;
if next > self.source.len() {
return Err(corrupt("reader advanced past the end of the buffer"));
}
self.offset = next;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::PrimaryCode;
use crate::rng::Rng;
#[test]
fn widths_round_trip_at_their_boundaries() {
let mut page = [0u8; 32];
write_u8(&mut page, 0, u8::MAX).unwrap();
write_u16(&mut page, 1, u16::MAX).unwrap();
write_u24(&mut page, 3, 0x00ff_ffff).unwrap();
write_u32(&mut page, 6, u32::MAX).unwrap();
write_u48(&mut page, 10, 0x0000_ffff_ffff_ffff).unwrap();
write_u64(&mut page, 16, u64::MAX).unwrap();
assert_eq!(read_u8(&page, 0).unwrap(), u8::MAX);
assert_eq!(read_u16(&page, 1).unwrap(), u16::MAX);
assert_eq!(read_u24(&page, 3).unwrap(), 0x00ff_ffff);
assert_eq!(read_u32(&page, 6).unwrap(), u32::MAX);
assert_eq!(read_u48(&page, 10).unwrap(), 0x0000_ffff_ffff_ffff);
assert_eq!(read_u64(&page, 16).unwrap(), u64::MAX);
}
#[test]
fn narrow_widths_reject_values_that_do_not_fit() {
let mut page = [0u8; 16];
assert_eq!(
write_u24(&mut page, 0, 0x0100_0000).unwrap_err().code(),
PrimaryCode::Corrupt
);
assert_eq!(
write_u48(&mut page, 0, 0x0001_0000_0000_0000)
.unwrap_err()
.code(),
PrimaryCode::Corrupt
);
}
#[test]
fn reads_past_the_end_are_errors() {
let page = [0u8; 4];
assert_eq!(read_u64(&page, 0).unwrap_err().code(), PrimaryCode::Corrupt);
assert_eq!(read_u32(&page, 1).unwrap_err().code(), PrimaryCode::Corrupt);
assert_eq!(read_u8(&page, 4).unwrap_err().code(), PrimaryCode::Corrupt);
assert_eq!(
window(&page, usize::MAX, 1).unwrap_err().code(),
PrimaryCode::Corrupt
);
}
#[test]
fn doubles_keep_their_exact_bits() {
let cases = [
0.0_f64,
-0.0_f64,
f64::MIN_POSITIVE,
f64::MAX,
f64::NEG_INFINITY,
f64::from_bits(0x7ff8_0000_0000_0001),
];
let mut page = [0u8; 8];
for case in cases {
write_f64(&mut page, 0, case).unwrap();
assert_eq!(read_f64(&page, 0).unwrap().to_bits(), case.to_bits());
}
}
#[test]
fn reader_tracks_its_offset_and_stops_at_the_end() {
let source = [1u8, 2, 0, 3, 0, 0, 0, 4];
let mut reader = ByteReader::new(&source);
assert_eq!(reader.u8().unwrap(), 1);
assert_eq!(reader.u16().unwrap(), 0x0200);
assert_eq!(reader.offset(), 3);
assert_eq!(reader.remaining(), 5);
assert_eq!(reader.take(5).unwrap(), &[3, 0, 0, 0, 4]);
assert_eq!(reader.remaining(), 0);
assert_eq!(reader.u8().unwrap_err().code(), PrimaryCode::Corrupt);
}
#[test]
fn random_offsets_never_panic() {
let mut rng = Rng::new(0x5eed_1782);
let mut buffer = vec![0u8; 512];
for byte in buffer.iter_mut() {
*byte = rng.next_u32() as u8;
}
for _ in 0..crate::probe::sample_rounds(200_000) {
let offset = rng.next_u32() as usize % 600;
let _ = read_u8(&buffer, offset);
let _ = read_u16(&buffer, offset);
let _ = read_u24(&buffer, offset);
let _ = read_u32(&buffer, offset);
let _ = read_u48(&buffer, offset);
let _ = read_u64(&buffer, offset);
let _ = read_f64(&buffer, offset);
}
}
}