byte_chisel/lib.rs
1#![doc = include_str!("docs.md")]
2#![warn(missing_docs)]
3/* Copyright 2025 Fayti1703
4 * Licensed under the EUPL 1.2-or-later.
5 * If a copy of the EUPL was not distributed with this crate,
6 * you may obtain one at <https://interoperable-europe.ec.europa.eu/collection/eupl/eupl-text-eupl-12>.
7 */
8
9#[cfg(all(feature = "alloc", not(feature = "std")))]
10extern crate alloc;
11#[cfg(all(feature = "alloc", not(feature = "std")))]
12use alloc::vec::Vec;
13
14#[cfg(feature = "std")]
15use std::vec::Vec;
16
17pub mod prelude {
18/*! `byte-chisel` prelude
19 *
20 * This module contains reëxports for commonly used types in this crate, for easy importing:
21 * ```
22 * pub use byte_chisel::prelude::*;
23 * ```
24 */
25	pub use super::{Chisel, ChiselSource, Endianness};
26	pub use super::{ChiselError, ChiselErrorData, InfallibleFormatError};
27	pub use super::{ChiselResult, InfallibleFormatResult};
28	pub use super::{InfalliableFormatResultExt};
29}
30
31pub mod source;
32pub use source::{ChiselSource, ChiselSourceError};
33
34mod endian;
35#[doc(inline)]
36pub use endian::{ChiselLittleEndian, ChiselBigEndian};
37
38mod error;
39#[doc(inline)]
40pub use error::{ChiselError, ChiselErrorData};
41
42/**
43 * An object providing structured access to an underlying byte-stream.
44 *
45 * A `Chisel` is an abstraction over some ["source"][ChiselSource] of bytes that allows reading
46 * data in a structured manner.
47 *
48 * See the [crate documentation](self) for more information.
49 */
50pub struct Chisel<S> {
51	offset: usize,
52	source: S
53}
54
55
56/** The result type of attempting to chisel a `T` from a source `S`, with format errors described by `E`. */
57/* Can't use bounds here (see rust::#112792) */
58pub type ChiselResult<T, E, S> = Result<T, ChiselError<E, <S as ChiselSource>::Error>>;
59/**
60 * The error type of attempting to chisel a value from a source `S`; where all byte sequences produce valid values ("infallible format").
61 *
62 * Because the format is infallible, this never contains a [`ChiselErrorData::Format`].
63 */
64/* Can't use bounds here (see rust::#112792) */
65pub type InfallibleFormatError<S> = ChiselError<core::convert::Infallible, <S as ChiselSource>::Error>;
66/** The error type of attempting to chisel a `T` from a source `S`. All byte sequences produce valid `T`s ("infallible format"). */
67pub type InfallibleFormatResult<T, S> = Result<T, InfallibleFormatError<S>>;
68
69/**
70 * Used to define byte order or "endianness".
71 *
72 * This is used to define the order in which the bytes of datatypes are read.
73 */
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
75pub enum Endianness {
76	/** The lowest-order byte is read first. Subsequent bytes are in ascending order. */
77	Little,
78	/** The highest-order byte is read first. Subsequent bytes are in descending order. */
79	Big,
80}
81
82/**
83 * Used to indicate why a [`read_until_or_end`][Chisel::read_until_or_end] operation stopped.
84 */
85#[derive(Debug, Copy, Clone, PartialEq, Eq)]
86pub enum ReadUntilStopReason {
87	/** The operation found the provided delimiter byte. */
88	Delimiter,
89	/** The operation encountered an end-of-input condition. */
90	EndOfInput
91}
92
93macro_rules! decode_endian {
94	($endian: ident, $value: ident, $little: path, $big: path) => {
95		match $endian {
96			Endianness::Little => $little($value),
97			Endianness::Big => $big($value)
98		}
99	};
100}
101
102macro_rules! ty_fn {
103	($ty: ident) => {
104		#[doc =
105concat!(r"Reads a single [`", stringify!($ty), r"`].
106
107The provided `endian` specifies the byte order.
108
109## Errors
110
111If an error is returned, the chisel [breaks][self#breaking].
112")]
113		pub fn $ty(&mut self, endian: Endianness) -> InfallibleFormatResult<$ty, S> {
114			let mut buf = [0u8; core::mem::size_of::<$ty>()];
115			self.read_buf(&mut buf)?;
116			Ok(decode_endian!(endian, buf, $ty::from_le_bytes, $ty::from_be_bytes))
117		}
118	};
119}
120
121impl<'a> Chisel<&'a [u8]> {
122	/** Create a new `Chisel`, with its source being an array of bytes. */
123	pub fn from_array(buf: &'a [u8]) -> Self { Self::new(buf) }
124}
125
126#[cfg(feature = "std")]
127impl<'a, T : std::io::Read> Chisel<source::ChiselSourceRead<'a, T>> {
128	/**
129	 * Create a new `Chisel`, with its source being a `Read` implementation.
130	 *
131	 * Note that typical chiseling operations involve _a lot_ of individual calls to `read`.
132	 * As such, it is recommended to instead use [`from_buf_read`][Chisel::from_buf_read]
133	 * whenever possible.
134	 *
135	 * Only use this constructor in the rare circumstance in which it is absolutely necessary 
136	 * to perform direct, unbuffered reads from an I/O source.
137	 */
138	pub fn from_read(read: &'a mut T) -> Self { Self::new(source::ChiselSourceRead(read)) }
139}
140
141#[cfg(feature = "std")]
142impl<'a, T : std::io::BufRead> Chisel<source::ChiselSourceBufRead<'a, T>> {
143	/** Create a new `Chisel`, with its source being a `BufRead` implementation. */
144	pub fn from_buf_read(read: &'a mut T) -> Self { Self::new(source::ChiselSourceBufRead(read)) }
145}
146
147impl<S : ChiselSource> Chisel<S> {
148	/** Creates a new [Chisel] from an arbitrary source. */
149	pub fn new(source: S) -> Self {
150		Self { offset: 0, source }
151	}
152
153	/** Returns the current byte offset of the Chisel. */
154	pub fn offset(&self) -> usize { self.offset }
155
156	/**
157	 * Returns a format error that occurred `backstep` bytes ago.
158	 *
159	 * This is a convenience method for manually determining the appropriate byte offset and calling [ChiselError::format].
160	 */
161	pub fn error<E>(&self, err: E, backstep: usize) -> ChiselError<E, S::Error> { ChiselError::format(self.offset - backstep, err) }
162
163	/** Returns an [`Endianness::Little`]-wrapper around this chisel. The wrapper reads values in the little-endian byte order. */
164	#[inline]
165	pub fn little_endian(&mut self) -> ChiselLittleEndian<S> { ChiselLittleEndian(self) }
166	/** Returns an [`Endianness::Big`]-wrapper around this chisel. The wrapper reads values in the big-endian byte order. */
167	#[inline]
168	pub fn big_endian(&mut self) -> ChiselBigEndian<S> { ChiselBigEndian(self) }
169
170	/**
171	 * Fills a provided buffer with bytes from the source.
172	 *
173	 * ## Errors
174	 * This function returns an [`ChiselErrorData::EndOfInput`] if the end of the input is encountered
175	 * before the buffer is completely filled.
176	 *
177	 * If an error is returned, the contents of `buf` are unspecified and the chisel [breaks][self#breaking].
178	 */
179	pub fn read_buf(&mut self, buf: &mut [u8]) -> InfallibleFormatResult<(), S> {
180		match self.source.read_exact(buf) {
181			Ok(()) => {
182				self.offset += buf.len();
183				Ok(())
184			},
185			Err(x) => Err(ChiselError::from_source(self.offset, x)),
186		}
187	}
188
189	/**
190	 * Reads a single byte [`u8`].
191	 *
192	 * As this function only reads a single byte, no endianness is required.
193	 */
194	pub fn u8(&mut self) -> InfallibleFormatResult<u8, S> {
195		let mut buf = [0u8; 1];
196		self.read_buf(&mut buf)?;
197		Ok(buf[0])
198	}
199	ty_fn!(u16);
200	ty_fn!(u32);
201	ty_fn!(u64);
202
203	/**
204	* Reads a single _signed_ byte [`i8`].
205	*
206	* As this function only reads a single byte, no endianness is required.
207	*/
208	pub fn i8(&mut self) -> InfallibleFormatResult<i8, S> {
209		let mut buf = [0u8; 1];
210		self.read_buf(&mut buf)?;
211		Ok(buf[0] as i8)
212	}
213	ty_fn!(i16);
214	ty_fn!(i32);
215	ty_fn!(i64);
216
217	ty_fn!(f32);
218	ty_fn!(f64);
219
220	/**
221	 * Skips `how_many` bytes of the underlying source.
222	 *
223	 * This is provided for optimization reasons. This hint is forwarded to the source.
224	 *
225	 * Exactly `how_many` bytes are skipped, as-if [`read_buf`][Self::read_buf] were called with a buffer
226	 * of appropriate size, with the buffer being discarded.
227	 *
228	 * ## Errors
229	 *
230	 * If an end-of-input condition occurs before `how_many` bytes are skipped, an [`ChiselErrorData::EndOfInput`] error is returned.
231	 *
232	 * If an error is returned, the chisel [breaks][self#breaking].
233	 */
234	pub fn skip(&mut self, how_many: usize) -> InfallibleFormatResult<(), S> {
235		match self.source.skip(how_many) {
236			Ok(()) => {
237				self.offset += how_many;
238				Ok(())
239			},
240			Err(x) => Err(ChiselError::from_source(self.offset, x))
241		}
242	}
243
244	#[cfg(feature = "alloc")]
245	/**
246	 * Reads bytes into the provided `dest` Vec until the `delimiter` byte is encountered.
247	 *
248	 * ## Errors
249	 * If the end of the input is encountered before the delimiter is found, [`ChiselErrorData::EndOfInput`] is returned.
250	 *
251	 * If an error is returned, the chisel [breaks][self#breaking].
252	 */
253	pub fn read_until(&mut self, delimiter: u8, dest: &mut Vec<u8>) -> InfallibleFormatResult<(), S> {
254		let offset = self.offset; /* backup offset before we advance it in case of EOF */
255		match self.read_until_or_end(delimiter, dest) {
256			Ok(x) => match x {
257				ReadUntilStopReason::EndOfInput => Err(ChiselError::end_of_input(offset)),
258				ReadUntilStopReason::Delimiter => Ok(())
259			}
260			Err(x) => Err(x)
261		}
262	}
263
264
265	#[cfg(feature = "alloc")]
266	/**
267	* Reads bytes into the provided `dest` Vec until the `delimiter` byte or end-of-input is encountered.
268	*
269	* ## Errors
270	* If an error is returned, the chisel [breaks][self#breaking].
271	*/
272	pub fn read_until_or_end(&mut self, delimiter: u8, dest: &mut Vec<u8>) -> InfallibleFormatResult<ReadUntilStopReason, S> {
273		match self.source.read_until(delimiter, dest) {
274			Ok(x) => {
275				self.offset += match x {
276					ReadUntilStopReason::EndOfInput => dest.len(),
277					ReadUntilStopReason::Delimiter => dest.len() + 1
278				};
279				Ok(x)
280			}
281			Err(x) => Err(ChiselError::source(self.offset, x))
282		}
283	}
284}
285
286/** Extension trait for `InfallibleFormatResult`. */
287pub trait InfalliableFormatResultExt<T, S> {
288	/**
289	 * Converts an [`InfallibleFormatResult`] into an arbitrary-format [`ChiselResult`].
290	 *
291	 * This is basically a small hack to allow easy error propagation,
292	 * as `ChiselError<F, S>` cannot implement `From<ChiselError<Infallible, S>>`.
293	 */
294	fn forward<F>(self) -> Result<T, ChiselError<F, S>>;
295}
296
297impl<T, S> InfalliableFormatResultExt<T, S> for Result<T, ChiselError<core::convert::Infallible, S>> {
298	fn forward<F>(self) -> Result<T, ChiselError<F, S>> {
299		self.map_err(|x| x.forward())
300	}
301}