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}