#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeekPosition {
FromStart(u64),
FromCurrent(i64),
FromEnd(i64),
}
impl SeekPosition {
#[must_use]
#[allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
pub fn to_offset(self, current: u64, file_size: u64) -> Option<u64> {
match self {
SeekPosition::FromStart(off) => {
if off <= file_size {
Some(off)
} else {
None
}
}
SeekPosition::FromCurrent(delta) => {
let result = current as i64 + delta;
if result >= 0 && result as u64 <= file_size {
Some(result as u64)
} else {
None
}
}
SeekPosition::FromEnd(delta) => {
let result = file_size as i64 + delta;
if result >= 0 && result as u64 <= file_size {
Some(result as u64)
} else {
None
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct SeekableBuffer {
data: Vec<u8>,
position: u64,
}
impl SeekableBuffer {
#[must_use]
pub fn new(data: Vec<u8>) -> Self {
Self { data, position: 0 }
}
#[must_use]
pub fn empty() -> Self {
Self::new(Vec::new())
}
pub fn seek(&mut self, pos: SeekPosition) -> Result<u64, String> {
let file_size = self.data.len() as u64;
match pos.to_offset(self.position, file_size) {
Some(off) => {
self.position = off;
Ok(off)
}
None => Err(format!(
"seek position {pos:?} is out of bounds (file_size={file_size})"
)),
}
}
#[must_use]
pub fn position(&self) -> u64 {
self.position
}
#[must_use]
pub fn bytes_remaining(&self) -> u64 {
let len = self.data.len() as u64;
len.saturating_sub(self.position)
}
#[allow(clippy::cast_possible_truncation)]
pub fn read_bytes(&mut self, buf: &mut [u8]) -> usize {
let available = self.bytes_remaining() as usize;
let to_read = buf.len().min(available);
let start = self.position as usize;
buf[..to_read].copy_from_slice(&self.data[start..start + to_read]);
self.position += to_read as u64;
to_read
}
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_buf() -> SeekableBuffer {
SeekableBuffer::new((0u8..=9u8).collect())
}
#[test]
fn test_initial_position() {
let buf = make_buf();
assert_eq!(buf.position(), 0);
}
#[test]
fn test_bytes_remaining_at_start() {
let buf = make_buf();
assert_eq!(buf.bytes_remaining(), 10);
}
#[test]
fn test_seek_from_start() {
let mut buf = make_buf();
let new_pos = buf
.seek(SeekPosition::FromStart(5))
.expect("seek should succeed");
assert_eq!(new_pos, 5);
assert_eq!(buf.position(), 5);
assert_eq!(buf.bytes_remaining(), 5);
}
#[test]
fn test_seek_from_current_forward() {
let mut buf = make_buf();
buf.seek(SeekPosition::FromStart(3))
.expect("seek should succeed");
buf.seek(SeekPosition::FromCurrent(4))
.expect("seek should succeed");
assert_eq!(buf.position(), 7);
}
#[test]
fn test_seek_from_current_backward() {
let mut buf = make_buf();
buf.seek(SeekPosition::FromStart(6))
.expect("seek should succeed");
buf.seek(SeekPosition::FromCurrent(-3))
.expect("seek should succeed");
assert_eq!(buf.position(), 3);
}
#[test]
fn test_seek_from_end() {
let mut buf = make_buf();
buf.seek(SeekPosition::FromEnd(-2))
.expect("seek should succeed");
assert_eq!(buf.position(), 8);
}
#[test]
fn test_seek_to_end_boundary() {
let mut buf = make_buf();
let pos = buf
.seek(SeekPosition::FromStart(10))
.expect("seek should succeed");
assert_eq!(pos, 10);
assert_eq!(buf.bytes_remaining(), 0);
}
#[test]
fn test_seek_out_of_bounds_returns_err() {
let mut buf = make_buf();
assert!(buf.seek(SeekPosition::FromStart(99)).is_err());
}
#[test]
fn test_seek_from_end_out_of_bounds() {
let mut buf = make_buf();
assert!(buf.seek(SeekPosition::FromEnd(-99)).is_err());
}
#[test]
fn test_read_bytes_full() {
let mut buf = make_buf();
let mut out = vec![0u8; 10];
let n = buf.read_bytes(&mut out);
assert_eq!(n, 10);
assert_eq!(out, (0u8..=9u8).collect::<Vec<_>>());
}
#[test]
fn test_read_bytes_partial() {
let mut buf = make_buf();
let mut out = vec![0u8; 3];
buf.read_bytes(&mut out);
assert_eq!(buf.position(), 3);
let mut out2 = vec![0u8; 3];
let n = buf.read_bytes(&mut out2);
assert_eq!(n, 3);
assert_eq!(out2, vec![3, 4, 5]);
}
#[test]
fn test_read_bytes_past_end() {
let mut buf = make_buf();
buf.seek(SeekPosition::FromStart(8))
.expect("seek should succeed");
let mut out = vec![0u8; 5];
let n = buf.read_bytes(&mut out);
assert_eq!(n, 2);
assert_eq!(&out[..2], &[8, 9]);
}
#[test]
fn test_empty_buffer() {
let buf = SeekableBuffer::empty();
assert!(buf.is_empty());
assert_eq!(buf.bytes_remaining(), 0);
assert_eq!(buf.len(), 0);
}
#[test]
fn test_seek_position_from_start_to_offset() {
let pos = SeekPosition::FromStart(5);
assert_eq!(pos.to_offset(0, 10), Some(5));
assert_eq!(pos.to_offset(0, 4), None);
}
#[test]
fn test_seek_position_from_end_to_offset() {
let pos = SeekPosition::FromEnd(0);
assert_eq!(pos.to_offset(0, 10), Some(10));
let pos2 = SeekPosition::FromEnd(-3);
assert_eq!(pos2.to_offset(0, 10), Some(7));
}
}