oct 0.26.0

Octonary transcodings.
Documentation
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.

//! The [`Cursor`] type.

use crate::io::{
	ErrorKind,
	Read,
	Result,
	Seek,
	SeekFrom,
	Write,
};

use core::mem::{MaybeUninit, offset_of};

#[cfg(feature = "alloc")]
use alloc::boxed::Box;

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

// NOTE: Field order is important to `PartialEq`
// and `Eq`.
/// A cursor over an octet buffer.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Cursor<T> {
	/// The contained buffer.
	buf: T,

	/// The current position.
	position: u64,
}

impl<T> Cursor<T> {
	/// Constructs a new cursor.
	#[inline]
	#[must_use]
	pub const fn new(buf: T) -> Self {
		Self { buf, position: 0 }
	}

	/// Retrieves the current position of the cursor.
	#[inline]
	#[must_use]
	pub const fn position(&self) -> u64 {
		self.position
	}

	/// Moves the position of the curser.
	#[inline]
	pub const fn set_position(&mut self, position: u64) {
		self.position = position;
	}

	/// Borrows the contained buffer.
	#[inline]
	#[must_use]
	pub const fn get_ref(&self) -> &T {
		&self.buf
	}

	/// Mutably-borrows the contained buffer.
	#[inline]
	#[must_use]
	pub const fn get_mut(&mut self) -> &mut T {
		&mut self.buf
	}

	/// Returns the contained buffer.
	#[inline]
	#[must_use]
	pub const fn into_inner(self) -> T {
		let this = MaybeUninit::new(self);

		// SAFETY: `offset_of` guarantees that the returned
		// position is within bounds.
		let p = unsafe { this.as_ptr().byte_add(offset_of!(Self, buf)) as *const T };

		unsafe { p.read() }
	}
}

impl<T: AsRef<[u8]>> Read for Cursor<T> {
	#[inline]
	fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
		let count = read_from_slice(self.buf.as_ref(), buf, self.position)?;
		self.position += count as u64;
		Ok(count)
	}

	#[inline]
	fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
		read_exact_from_slice(self.buf.as_ref(), buf, self.position)?;
		self.position += buf.len() as u64;
		Ok(())
	}
}

impl<T: Default> Default for Cursor<T> {
	#[inline]
	fn default() -> Self {
		Self::new(Default::default())
	}
}

impl<T: AsRef<[u8]>> Seek for Cursor<T> {
	fn seek(&mut self, mode: SeekFrom) -> Result<u64> {
		let position = match mode {
			SeekFrom::Start(offset) => {
				offset
			}

			SeekFrom::End(offset) => {
				let buf_len = self.buf.as_ref().len() as u64;

				let Some(position) = buf_len.checked_add_signed(offset) else {
					return Err(ErrorKind::InvalidInput.into());
				};

				position
			}

			SeekFrom::Current(offset) => {
				let Some(position) = self.position.checked_add_signed(offset) else {
					return Err(ErrorKind::InvalidInput.into());
				};

				position
			}
		};

		self.position = position;
		Ok(position)
	}

	#[inline]
	fn rewind(&mut self) -> Result<()> {
		self.position = 0;
		Ok(())
	}

	#[inline]
	fn seek_relative(&mut self, offset: i64) -> Result<()> {
		let Some(position) = self.position.checked_add_signed(offset) else {
			return Err(ErrorKind::InvalidInput.into());
		};

		self.position = position;
		Ok(())
	}

	#[inline]
	fn stream_position(&mut self) -> Result<u64> {
		Ok(self.position)
	}
}

// SORT: array
impl<const N: usize> Write for Cursor<[u8; N]> {
	#[inline]
	fn write(&mut self, buf: &[u8]) -> Result<usize> {
		let count = write_to_slice(buf, &mut self.buf, self.position)?;
		self.position += count as u64;
		Ok(count)
	}

	#[inline]
	fn write_all(&mut self, buf: &[u8]) -> Result<()> {
		write_all_to_slice(buf, &mut self.buf, self.position)?;
		self.position += buf.len() as u64;
		Ok(())
	}

	#[inline(always)]
	fn flush(&mut self) -> Result<()> {
		Ok(())
	}
}

#[cfg(feature = "alloc")]
impl Write for Cursor<Box<[u8]>> {
	#[inline]
	fn write(&mut self, buf: &[u8]) -> Result<usize> {
		let count = write_to_slice(buf, self.buf.as_mut(), self.position)?;
		self.position += count as u64;
		Ok(count)
	}

	#[inline]
	fn write_all(&mut self, buf: &[u8]) -> Result<()> {
		write_all_to_slice(buf, self.buf.as_mut(), self.position)?;
		self.position += buf.len() as u64;
		Ok(())
	}

	#[inline(always)]
	fn flush(&mut self) -> Result<()> {
		Ok(())
	}
}

impl Write for Cursor<&mut [u8]> {
	#[inline]
	fn write(&mut self, buf: &[u8]) -> Result<usize> {
		let count = write_to_slice(buf, self.buf, self.position)?;
		self.position += count as u64;
		Ok(count)
	}

	#[inline]
	fn write_all(&mut self, buf: &[u8]) -> Result<()> {
		write_all_to_slice(buf, self.buf, self.position)?;
		self.position += buf.len() as u64;
		Ok(())
	}

	#[inline(always)]
	fn flush(&mut self) -> Result<()> {
		Ok(())
	}
}

#[cfg(feature = "alloc")]
impl Write for Cursor<Vec<u8>> {
	#[inline]
	fn write(&mut self, buf: &[u8]) -> Result<usize> {
		let count = write_to_vec(buf, &mut self.buf, self.position)?;
		self.position += count as u64;
		Ok(count)
	}

	#[inline]
	fn write_all(&mut self, buf: &[u8]) -> Result<()> {
		write_all_to_vec(buf, &mut self.buf, self.position)?;
		self.position += buf.len() as u64;
		Ok(())
	}

	#[inline(always)]
	fn flush(&mut self) -> Result<()> {
		Ok(())
	}
}

#[cfg(feature = "alloc")]
impl Write for Cursor<&mut Vec<u8>> {
	#[inline]
	fn write(&mut self, buf: &[u8]) -> Result<usize> {
		let count = write_to_vec(buf, self.buf, self.position)?;
		self.position += count as u64;
		Ok(count)
	}

	#[inline]
	fn write_all(&mut self, buf: &[u8]) -> Result<()> {
		write_all_to_vec(buf, self.buf, self.position)?;
		self.position += buf.len() as u64;
		Ok(())
	}

	#[inline(always)]
	fn flush(&mut self) -> Result<()> {
		Ok(())
	}
}

/// Equivalent to
/// [`Cursor::<&[u8]>::read`](Read::read)
#[expect(clippy::unnecessary_wraps)]
fn read_from_slice(src: &[u8], dst: &mut [u8], position: u64) -> Result<usize> {
	let position = position as usize;

	let Some(src) = src.get(position..) else {
		return Ok(0);
	};

	let count = dst.len().min(src.len());

	let src = &src[..count];
	let dst = &mut dst[..count];

	dst.copy_from_slice(src);
	Ok(count)
}

/// Equivalent to
/// [`Cursor::<&[u8]>::read_exact`](Read::read_exact)
fn read_exact_from_slice(src: &[u8], dst: &mut [u8], position: u64) -> Result<()> {
	let start = position as usize;
	let end   = start + dst.len();

	let Some(src) = src.get(start..end) else {
		return Err(ErrorKind::UnexpectedEof.into());
	};

	dst.copy_from_slice(src);
	Ok(())
}

/// Equivalent to
/// [`Cursor::<&mut [u8]>::write`](Write::write).
#[expect(clippy::unnecessary_wraps)]
fn write_to_slice(src: &[u8], dst: &mut [u8], position: u64) -> Result<usize> {
	let position = position as usize;

	let Some(src) = src.get(position..) else {
		return Ok(0);
	};

	let count = src.len().min(dst.len());

	let src = &src[..count];
	let dst = &mut dst[..count];

	dst.copy_from_slice(src);
	Ok(count)
}

/// Equivalent to
/// [`Cursor::<&mut [u8]>::write_all`](Write::write_all).
fn write_all_to_slice(src: &[u8], dst: &mut [u8], position: u64) -> Result<()> {
	let start = position as usize;
	let end   = start + src.len();

	let Some(dst) = dst.get_mut(start..end) else {
		return Err(ErrorKind::WriteZero.into());
	};

	dst.copy_from_slice(src);
	Ok(())
}

/// Equivalent to
/// [`Cursor::<Vec<u8>>::write`](Write::write).
#[expect(clippy::unnecessary_wraps)]
#[cfg(feature = "alloc")]
fn write_to_vec(src: &[u8], dst: &mut Vec<u8>, position: u64) -> Result<usize> {
	let position = position as usize;

	if position > dst.len() {
		return Ok(0);
	}

	let count = src.len();

	let new_len = (dst.len() - position + count).min(dst.len());
	dst.resize(new_len, 0);

	let dst = &mut dst[position..position + count];
	dst.copy_from_slice(src);

	Ok(count)
}

/// Equivalent to
/// [`Cursor::<Vec<u8>>::write_all`](Write::write_all).
#[cfg(feature = "alloc")]
fn write_all_to_vec(src: &[u8], dst: &mut Vec<u8>, position: u64) -> Result<()> {
	let position = position as usize;

	if position > dst.len() {
		return Err(ErrorKind::WriteZero.into());
	}

	let count = src.len();

	let new_len = (dst.len() - position + count).min(dst.len());
	dst.resize(new_len, 0);

	let dst = &mut dst[position..position + count];
	dst.copy_from_slice(src);

	Ok(())
}