Skip to main content

lsm_tree/io/
mod.rs

1// Local I/O trait surface, mirroring `std::io::{Read, Write, Seek}`
2// so that THE BOUNDS on `fs::Fs` / `fs::FsFile` no longer carry
3// `std::io::*` directly. The signatures match `std::io` exactly, so
4// backends gated behind `#[cfg(feature = "std")]` (such as `std_fs`
5// and `io_uring_fs`) keep using `std::io::*` internally — they just
6// satisfy this crate's traits via the supertrait alias + blanket
7// impls below.
8//
9// Scope of this module's contribution to the no-std epic (see #311):
10// it removes `std::io::{Read, Write, Seek}` from the trait BOUNDS.
11// The `fs` module still uses `std::io::Result` for return types and
12// `&std::path::Path` for path arguments in `Fs` / `FsFile` method
13// signatures — those migrate to `crate::io::Result<T>` and a
14// `crate::path` equivalent in follow-up commits. Until both follow-
15// ups land, `fs::*` does NOT yet compile under
16// `--no-default-features --features alloc`.
17//
18// Why not pull in an external `core_io` / `core2` / `core3` /
19// `embedded-io` crate: those add a maintainer dependency for what
20// is ultimately three stable trait signatures plus an error type.
21// The signatures here have not meaningfully changed since Rust 1.0,
22// so maintenance is near zero, and we keep the no-std contract
23// under our own control.
24
25use alloc::boxed::Box;
26use alloc::string::String;
27use core::fmt;
28
29/// Specialised `Result` for I/O operations on this crate's traits.
30pub type Result<T> = core::result::Result<T, Error>;
31
32/// Mirrors [`std::io::ErrorKind`] for the variants this crate actually
33/// constructs. Kept in alphabetical order so additions stay easy to spot.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum ErrorKind {
37    /// Entity (file, directory, key, etc.) already exists.
38    AlreadyExists,
39    /// Broken pipe: the other end of a stream is closed.
40    BrokenPipe,
41    /// A network backend's connection was reset by the peer (`ECONNRESET`).
42    ConnectionAborted,
43    /// A network backend refused the connection (`ECONNREFUSED`).
44    ConnectionRefused,
45    /// A network backend's connection was reset mid-transfer (`ECONNRESET`).
46    ConnectionReset,
47    /// A network backend's socket is not connected (`ENOTCONN`).
48    NotConnected,
49    /// Operation attempted across distinct devices/filesystems.
50    CrossesDevices,
51    /// The remote host of a network filesystem is unreachable (`EHOSTUNREACH`).
52    HostUnreachable,
53    /// Operation was interrupted (`EINTR`-equivalent); usually retriable.
54    Interrupted,
55    /// Data being read does not match the expected format/schema.
56    InvalidData,
57    /// A function argument had an invalid value.
58    InvalidInput,
59    /// The network a filesystem depends on is down (`ENETDOWN`).
60    NetworkDown,
61    /// The network a filesystem depends on is unreachable (`ENETUNREACH`).
62    NetworkUnreachable,
63    /// Entity was not found.
64    NotFound,
65    /// Catch-all for errors that don't fit any other variant.
66    Other,
67    /// The operation ran out of memory (`ENOMEM` — an oversized read buffer
68    /// or an exhausted host); the bytes on disk are not implicated.
69    OutOfMemory,
70    /// Operation denied due to lack of permissions.
71    PermissionDenied,
72    /// The filesystem quota was exceeded (`EDQUOT`).
73    QuotaExceeded,
74    /// The filesystem is mounted read-only (`EROFS`).
75    ReadOnlyFilesystem,
76    /// A network file handle went stale (`ESTALE` — the NFS server rebooted
77    /// or re-exported); a re-open after the mount recovers clears it.
78    StaleNetworkFileHandle,
79    /// The storage device is out of space (`ENOSPC`).
80    StorageFull,
81    /// An I/O operation timed out (`ETIMEDOUT` — a slow or wedged network
82    /// filesystem); the data underneath is not implicated.
83    TimedOut,
84    /// The process or the system ran out of file descriptors (`EMFILE` /
85    /// `ENFILE`, `ERROR_TOO_MANY_OPEN_FILES`); the file itself is not
86    /// implicated.
87    TooManyOpenFiles,
88    /// Reader hit end-of-file before satisfying the request.
89    UnexpectedEof,
90    /// Operation is not supported on this platform / backend / build.
91    Unsupported,
92    /// Operation would block (`EAGAIN` / `EWOULDBLOCK`-equivalent); the caller
93    /// can retry. Mirrors [`std::io::ErrorKind::WouldBlock`].
94    WouldBlock,
95    /// `write` returned `Ok(0)` while bytes still needed to be
96    /// written. Mirrors [`std::io::ErrorKind::WriteZero`] so callers
97    /// can distinguish a stuck-writer short write from a generic
98    /// [`Other`](Self::Other) failure.
99    WriteZero,
100}
101
102impl ErrorKind {
103    /// Whether this kind is a TRANSIENT (retryable) I/O failure — one a retry
104    /// (immediately, or after the environment recovers) genuinely clears, and
105    /// which a corrupt on-disk structure can NEVER produce: the interrupted
106    /// syscalls [`Interrupted`](Self::Interrupted) (EINTR) and
107    /// [`WouldBlock`](Self::WouldBlock) (EAGAIN), plus the network-filesystem
108    /// kinds [`TimedOut`](Self::TimedOut),
109    /// [`HostUnreachable`](Self::HostUnreachable),
110    /// [`NetworkDown`](Self::NetworkDown),
111    /// [`NetworkUnreachable`](Self::NetworkUnreachable) and
112    /// [`StaleNetworkFileHandle`](Self::StaleNetworkFileHandle) an NFS / FUSE
113    /// backend surfaces when the transport (not the data) fails, and the
114    /// connection kinds [`ConnectionAborted`](Self::ConnectionAborted),
115    /// [`ConnectionRefused`](Self::ConnectionRefused),
116    /// [`ConnectionReset`](Self::ConnectionReset),
117    /// [`NotConnected`](Self::NotConnected) and [`BrokenPipe`](Self::BrokenPipe)
118    /// a remote or custom backend surfaces when its transport drops
119    /// mid-request. Everything else, including the
120    /// ambiguous [`Other`](Self::Other) that real `EIO` and platform-specific
121    /// structural errors (e.g. a Windows negative-seek on a corrupt file) both
122    /// map to, is treated as persistent / structural.
123    /// Shared so the repair and integrity-verify paths classify I/O failures
124    /// identically.
125    pub(crate) fn is_transient(self) -> bool {
126        matches!(
127            self,
128            Self::Interrupted
129                | Self::WouldBlock
130                | Self::TimedOut
131                | Self::HostUnreachable
132                | Self::NetworkDown
133                | Self::NetworkUnreachable
134                | Self::StaleNetworkFileHandle
135                // A remote or custom backend's transport dropping mid-request
136                // says nothing about the bytes it was carrying. `BrokenPipe`
137                // belongs with them: EPIPE comes from a pipe or socket whose
138                // peer went away, never from the contents of a regular file.
139                | Self::ConnectionAborted
140                | Self::ConnectionRefused
141                | Self::ConnectionReset
142                | Self::NotConnected
143                | Self::BrokenPipe
144        )
145    }
146
147    /// Whether this kind must PROPAGATE out of recovery grading instead of
148    /// condemning the file it came from: a [transient](Self::is_transient)
149    /// kind (a retry clears it), or an ENVIRONMENTAL failure that does not
150    /// implicate the bytes on disk —
151    /// [`PermissionDenied`](Self::PermissionDenied) (an ACL / ownership
152    /// mistake), and the write-side destination kinds
153    /// [`StorageFull`](Self::StorageFull),
154    /// [`QuotaExceeded`](Self::QuotaExceeded) and
155    /// [`ReadOnlyFilesystem`](Self::ReadOnlyFilesystem) (the salvage OUTPUT
156    /// failed, not the source being salvaged), plus
157    /// [`OutOfMemory`](Self::OutOfMemory) and
158    /// [`TooManyOpenFiles`](Self::TooManyOpenFiles) (the HOST ran out, not the
159    /// file's bytes). Grading such a file unreadable
160    /// commits a manifest that excludes it and then removes it, turning a
161    /// recoverable configuration / capacity problem into permanent loss of
162    /// intact data; propagating lets the operator fix the environment and
163    /// retry.
164    pub(crate) fn is_environmental(self) -> bool {
165        self.is_transient()
166            || matches!(
167                self,
168                Self::PermissionDenied
169                    | Self::StorageFull
170                    | Self::QuotaExceeded
171                    | Self::ReadOnlyFilesystem
172                    | Self::OutOfMemory
173                    | Self::TooManyOpenFiles
174            )
175    }
176
177    fn as_str(self) -> &'static str {
178        match self {
179            Self::AlreadyExists => "entity already exists",
180            Self::BrokenPipe => "broken pipe",
181            Self::ConnectionAborted => "connection aborted",
182            Self::ConnectionRefused => "connection refused",
183            Self::ConnectionReset => "connection reset",
184            Self::NotConnected => "not connected",
185            Self::CrossesDevices => "cross-device link or rename",
186            Self::HostUnreachable => "host unreachable",
187            Self::Interrupted => "operation interrupted",
188            Self::InvalidData => "invalid data",
189            Self::InvalidInput => "invalid input parameter",
190            Self::NetworkDown => "network down",
191            Self::NetworkUnreachable => "network unreachable",
192            Self::NotFound => "entity not found",
193            Self::Other => "other error",
194            Self::OutOfMemory => "out of memory",
195            Self::PermissionDenied => "permission denied",
196            Self::QuotaExceeded => "filesystem quota exceeded",
197            Self::ReadOnlyFilesystem => "read-only filesystem",
198            Self::StaleNetworkFileHandle => "stale network file handle",
199            Self::StorageFull => "storage full",
200            Self::TimedOut => "operation timed out",
201            Self::TooManyOpenFiles => "too many open files",
202            Self::UnexpectedEof => "unexpected end of file",
203            Self::Unsupported => "unsupported",
204            Self::WouldBlock => "operation would block",
205            Self::WriteZero => "write returned 0 bytes",
206        }
207    }
208}
209
210impl fmt::Display for ErrorKind {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        f.write_str(self.as_str())
213    }
214}
215
216/// I/O error mirroring [`std::io::Error`].
217///
218/// Carries an [`ErrorKind`] plus an optional message string for
219/// context. The rendered `Display` form is `"<kind>"` when no
220/// message is attached, or `"<kind>: <message>"` when one is.
221///
222/// Under `feature = "std"`, the `From<std::io::Error>` bridge
223/// below applies a tri-state message-attachment policy so the
224/// rendered text matches the information density of the input
225/// without paying a heap allocation for plain kind-only inputs:
226///
227/// 1. **std error carries context** (`raw_os_error.is_some()` OR
228///    `get_ref().is_some()` — the canonical std discriminator
229///    for "more than just a kind") — the std `Display` output is
230///    captured as the message. The original OS / errno / path
231///    text survives the conversion and appears after the kind
232///    tag.
233/// 2. **std error is plain kind-only AND we mapped the kind**
234///    (`std::io::Error::from(ErrorKind::NotFound)` etc.) — no
235///    message is attached. The kind tag already conveys the
236///    information; capturing the std `Display` output would just
237///    repeat it (`"entity not found: entity not found"`) and burn
238///    a heap allocation on the hot path.
239/// 3. **std error is plain kind-only but we did NOT map the
240///    kind** (the `#[non_exhaustive]` `std::io::ErrorKind`
241///    catch-all branch — e.g. `OutOfMemory` mapping to our
242///    `ErrorKind::Other`) — the std `Display` output IS captured
243///    so the user-visible discriminant isn't lost in the
244///    `Other` bucket. Renders as `"other error: out of memory"`
245///    rather than just `"other error"`.
246pub struct Error {
247    kind: ErrorKind,
248    message: Option<Box<str>>,
249}
250
251impl Error {
252    /// Construct an error with the given kind and a context message.
253    ///
254    /// Analogous to `std::io::Error::new` but intentionally
255    /// narrower: this constructor takes a `String`-coercible
256    /// message and stores it verbatim, where std's `new()`
257    /// accepts `E: Into<Box<dyn std::error::Error + Send + Sync>>`
258    /// and carries a chained source via `Error::source()`. This
259    /// crate's error type has no source-chaining surface (and
260    /// can't have one under `no_std + alloc` without an alloc
261    /// dyn-trait shim), so an analogous "wrap an inner error"
262    /// helper would be misleading; callers wanting the source
263    /// payload of a std error use the `From<std::io::Error>`
264    /// bridge below, which renders the std Display into the
265    /// message field.
266    pub fn new<M: Into<String>>(kind: ErrorKind, message: M) -> Self {
267        Self {
268            kind,
269            message: Some(message.into().into_boxed_str()),
270        }
271    }
272
273    /// Construct an error with only an [`ErrorKind`] (no message).
274    /// Matches [`std::io::Error::from`] for `ErrorKind`.
275    #[must_use]
276    pub const fn from_kind(kind: ErrorKind) -> Self {
277        Self {
278            kind,
279            message: None,
280        }
281    }
282
283    /// Return the [`ErrorKind`] this error carries.
284    #[must_use]
285    pub const fn kind(&self) -> ErrorKind {
286        self.kind
287    }
288
289    /// Construct an [`ErrorKind::Other`] error carrying `message`.
290    /// Mirrors `std::io::Error::other`.
291    pub fn other<M: Into<String>>(message: M) -> Self {
292        Self::new(ErrorKind::Other, message)
293    }
294}
295
296impl From<ErrorKind> for Error {
297    fn from(kind: ErrorKind) -> Self {
298        Self::from_kind(kind)
299    }
300}
301
302impl fmt::Debug for Error {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        let mut dbg = f.debug_struct("Error");
305        dbg.field("kind", &self.kind);
306        if let Some(msg) = &self.message {
307            dbg.field("message", msg);
308        }
309        dbg.finish()
310    }
311}
312
313impl fmt::Display for Error {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        match &self.message {
316            Some(msg) => write!(f, "{}: {msg}", self.kind.as_str()),
317            None => f.write_str(self.kind.as_str()),
318        }
319    }
320}
321
322// `core::error::Error` (stable since 1.81, MSRV here is 1.92) so this error
323// is usable as a trait-object source under both `std` and `no_std`. Under
324// `std`, `std::error::Error` re-exports `core::error::Error`, so this is the
325// same impl std callers have always seen.
326impl core::error::Error for Error {}
327
328/// Bridge from `std::io::Error`. Maps the std `ErrorKind` to
329/// this crate's [`ErrorKind`] when a variant exists for it, and
330/// falls back to [`ErrorKind::Other`] for any std variant we
331/// don't track (the std type is `#[non_exhaustive]`, so the
332/// catch-all is required). The `Display` message is captured via
333/// the tri-state policy documented on [`Error`]: kept for
334/// contextful std errors and for unmapped kinds (so the
335/// discriminant is preserved even in the `Other` bucket),
336/// dropped for plain kind-only errors whose kind we DO map (to
337/// avoid the redundant `"<kind>: <kind>"` render and the heap
338/// allocation). Net effect: `?` from std-backed backends
339/// propagates the original context to operators, but callers
340/// inspecting `err.kind()` after the conversion should be aware
341/// that an unknown std kind now reads as
342/// `ErrorKind::Other` rather than the originating variant.
343#[cfg(feature = "std")]
344impl From<std::io::Error> for Error {
345    fn from(err: std::io::Error) -> Self {
346        // An error of ours that crossed a `std::io` boundary comes back whole,
347        // including a kind std has no variant for.
348        if let Some(ours) = err.get_ref().and_then(|e| e.downcast_ref::<Self>()) {
349            return Self {
350                kind: ours.kind,
351                message: ours.message.clone(),
352            };
353        }
354        if let Some(kind) = err.raw_os_error().and_then(kind_from_os_error) {
355            return Self::new(kind, alloc::format!("{err}"));
356        }
357        let std_kind = err.kind();
358        let (kind, kind_is_mapped) = match std_kind {
359            std::io::ErrorKind::AlreadyExists => (ErrorKind::AlreadyExists, true),
360            std::io::ErrorKind::BrokenPipe => (ErrorKind::BrokenPipe, true),
361            std::io::ErrorKind::ConnectionAborted => (ErrorKind::ConnectionAborted, true),
362            std::io::ErrorKind::ConnectionRefused => (ErrorKind::ConnectionRefused, true),
363            std::io::ErrorKind::ConnectionReset => (ErrorKind::ConnectionReset, true),
364            std::io::ErrorKind::NotConnected => (ErrorKind::NotConnected, true),
365            std::io::ErrorKind::CrossesDevices => (ErrorKind::CrossesDevices, true),
366            std::io::ErrorKind::HostUnreachable => (ErrorKind::HostUnreachable, true),
367            std::io::ErrorKind::Interrupted => (ErrorKind::Interrupted, true),
368            std::io::ErrorKind::InvalidData => (ErrorKind::InvalidData, true),
369            std::io::ErrorKind::InvalidInput => (ErrorKind::InvalidInput, true),
370            std::io::ErrorKind::NetworkDown => (ErrorKind::NetworkDown, true),
371            std::io::ErrorKind::NetworkUnreachable => (ErrorKind::NetworkUnreachable, true),
372            std::io::ErrorKind::NotFound => (ErrorKind::NotFound, true),
373            std::io::ErrorKind::OutOfMemory => (ErrorKind::OutOfMemory, true),
374            std::io::ErrorKind::PermissionDenied => (ErrorKind::PermissionDenied, true),
375            std::io::ErrorKind::QuotaExceeded => (ErrorKind::QuotaExceeded, true),
376            std::io::ErrorKind::ReadOnlyFilesystem => (ErrorKind::ReadOnlyFilesystem, true),
377            std::io::ErrorKind::StaleNetworkFileHandle => (ErrorKind::StaleNetworkFileHandle, true),
378            std::io::ErrorKind::StorageFull => (ErrorKind::StorageFull, true),
379            std::io::ErrorKind::TimedOut => (ErrorKind::TimedOut, true),
380            std::io::ErrorKind::UnexpectedEof => (ErrorKind::UnexpectedEof, true),
381            std::io::ErrorKind::Unsupported => (ErrorKind::Unsupported, true),
382            std::io::ErrorKind::WouldBlock => (ErrorKind::WouldBlock, true),
383            std::io::ErrorKind::WriteZero => (ErrorKind::WriteZero, true),
384            // `Other` is a mapped kind: a kind-only `Other` std error
385            // would otherwise fall through to the unmapped branch and
386            // attach Display ("other error"), producing
387            // "other error: other error" on render plus a heap alloc.
388            std::io::ErrorKind::Other => (ErrorKind::Other, true),
389            _ => (ErrorKind::Other, false),
390        };
391        // Message-attachment policy:
392        //
393        // - If the std error carries actual context (an `errno`, a
394        //   path / OS message, or a custom payload, detected by
395        //   `raw_os_error.is_some() || get_ref().is_some()` — the
396        //   canonical std-side discriminator for "this error
397        //   carries more than just a kind"), preserve its Display
398        //   output as our message so the OS-level detail survives.
399        //
400        // - If the std error is a plain kind-only one
401        //   (`std::io::Error::from(ErrorKind::X)`) AND we mapped
402        //   the kind, skip the message — our `Display` already
403        //   prefixes the kind tag, so storing "entity not found"
404        //   as the message would produce
405        //   "entity not found: entity not found" on render AND
406        //   burn an unnecessary heap allocation.
407        //
408        // - If the std error is kind-only but we DIDN'T map the
409        //   kind (`std::io::ErrorKind` is `#[non_exhaustive]`, e.g.
410        //   `OutOfMemory` / `ResourceBusy`), the user-visible
411        //   discriminant is otherwise lost in our `Other` bucket.
412        //   Preserve the std `Display` text in that case so an
413        //   unmapped kind still renders something useful (e.g.
414        //   "other error: out of memory") instead of just
415        //   "other error".
416        if err.raw_os_error().is_some() || err.get_ref().is_some() {
417            Self::new(kind, alloc::format!("{err}"))
418        } else if kind_is_mapped {
419            Self::from_kind(kind)
420        } else {
421            Self::new(kind, alloc::format!("{err}"))
422        }
423    }
424}
425
426/// The kind of an OS error code std reports as uncategorized.
427#[cfg(feature = "std")]
428fn kind_from_os_error(code: i32) -> Option<ErrorKind> {
429    #[cfg(unix)]
430    let too_many_open_files = code == libc::EMFILE || code == libc::ENFILE;
431    // `ERROR_TOO_MANY_OPEN_FILES` (4) and `WSAEMFILE` (10024), Win32 system
432    // error codes.
433    #[cfg(windows)]
434    let too_many_open_files = code == 4 || code == 10024;
435    #[cfg(not(any(unix, windows)))]
436    let too_many_open_files = false;
437    too_many_open_files.then_some(ErrorKind::TooManyOpenFiles)
438}
439
440/// Bridge back to `std::io::Error` so existing call sites that consume
441/// `std::io::Result<_>` (and where `From` is invoked via `?`) keep
442/// compiling once their input switches to this module.
443#[cfg(feature = "std")]
444impl From<Error> for std::io::Error {
445    fn from(err: Error) -> Self {
446        let kind = match err.kind {
447            ErrorKind::AlreadyExists => std::io::ErrorKind::AlreadyExists,
448            ErrorKind::BrokenPipe => std::io::ErrorKind::BrokenPipe,
449            ErrorKind::ConnectionAborted => std::io::ErrorKind::ConnectionAborted,
450            ErrorKind::ConnectionRefused => std::io::ErrorKind::ConnectionRefused,
451            ErrorKind::ConnectionReset => std::io::ErrorKind::ConnectionReset,
452            ErrorKind::NotConnected => std::io::ErrorKind::NotConnected,
453            ErrorKind::CrossesDevices => std::io::ErrorKind::CrossesDevices,
454            ErrorKind::HostUnreachable => std::io::ErrorKind::HostUnreachable,
455            ErrorKind::Interrupted => std::io::ErrorKind::Interrupted,
456            ErrorKind::InvalidData => std::io::ErrorKind::InvalidData,
457            ErrorKind::InvalidInput => std::io::ErrorKind::InvalidInput,
458            ErrorKind::NetworkDown => std::io::ErrorKind::NetworkDown,
459            ErrorKind::NetworkUnreachable => std::io::ErrorKind::NetworkUnreachable,
460            ErrorKind::NotFound => std::io::ErrorKind::NotFound,
461            ErrorKind::Other => std::io::ErrorKind::Other,
462            ErrorKind::OutOfMemory => std::io::ErrorKind::OutOfMemory,
463            ErrorKind::PermissionDenied => std::io::ErrorKind::PermissionDenied,
464            ErrorKind::QuotaExceeded => std::io::ErrorKind::QuotaExceeded,
465            ErrorKind::ReadOnlyFilesystem => std::io::ErrorKind::ReadOnlyFilesystem,
466            ErrorKind::StaleNetworkFileHandle => std::io::ErrorKind::StaleNetworkFileHandle,
467            ErrorKind::StorageFull => std::io::ErrorKind::StorageFull,
468            ErrorKind::TimedOut => std::io::ErrorKind::TimedOut,
469            // std has no such kind: carry ours as the payload so the bridge
470            // back restores it instead of reading `Other`.
471            ErrorKind::TooManyOpenFiles => return Self::other(err),
472            ErrorKind::UnexpectedEof => std::io::ErrorKind::UnexpectedEof,
473            ErrorKind::Unsupported => std::io::ErrorKind::Unsupported,
474            ErrorKind::WouldBlock => std::io::ErrorKind::WouldBlock,
475            ErrorKind::WriteZero => std::io::ErrorKind::WriteZero,
476        };
477        match err.message {
478            Some(msg) => Self::new(kind, msg.into_string()),
479            None => Self::from(kind),
480        }
481    }
482}
483
484/// Seek target, mirroring [`std::io::SeekFrom`].
485#[derive(Debug, Clone, Copy, PartialEq, Eq)]
486pub enum SeekFrom {
487    /// Seek to an absolute offset from the start of the stream.
488    Start(u64),
489    /// Seek to an offset relative to the end of the stream.
490    End(i64),
491    /// Seek to an offset relative to the current cursor position.
492    Current(i64),
493}
494
495#[cfg(feature = "std")]
496impl From<SeekFrom> for std::io::SeekFrom {
497    fn from(s: SeekFrom) -> Self {
498        match s {
499            SeekFrom::Start(n) => Self::Start(n),
500            SeekFrom::End(n) => Self::End(n),
501            SeekFrom::Current(n) => Self::Current(n),
502        }
503    }
504}
505
506#[cfg(feature = "std")]
507impl From<std::io::SeekFrom> for SeekFrom {
508    fn from(s: std::io::SeekFrom) -> Self {
509        match s {
510            std::io::SeekFrom::Start(n) => Self::Start(n),
511            std::io::SeekFrom::End(n) => Self::End(n),
512            std::io::SeekFrom::Current(n) => Self::Current(n),
513        }
514    }
515}
516
517// Under `feature = "std"`, the `Read` / `Write` / `Seek` traits in
518// this module are supertrait aliases over `std::io::{Read,Write,
519// Seek}`. Any `T: std::io::Read` automatically implements
520// `crate::io::Read` via the blanket below — AND `T: crate::io::Read`
521// implies `T: std::io::Read` (because crate::io::Read is a supertrait).
522// This second direction is what lets `dyn FsFile` (bounded on
523// `crate::io::Read`) flow into `std::io::BufReader`, `byteorder`,
524// and the rest of the std ecosystem without any explicit adapter.
525//
526// Under `--no-default-features --features alloc`, std::io does not
527// exist, so the traits are standalone with their own method bodies
528// returning `crate::io::Result<T>` (the local Error type above).
529// Signatures stay identical between modes so call sites compile in
530// both builds.
531//
532// This dual-shape is the whole reason we own this module instead of
533// depending on `core2` / `core3` / `embedded-io` — none of those let
534// the std-mode trait *be* `std::io::Read` (they're meant to replace
535// std::io, not bridge to it), and bridging the other way needs an
536// adapter shim at every backend boundary.
537
538/// Read trait. Under `std` it's a supertrait alias for
539/// [`std::io::Read`]; under `no_std + alloc` it carries its own
540/// signature mirroring the std contract.
541#[cfg(feature = "std")]
542pub trait Read: std::io::Read {}
543#[cfg(feature = "std")]
544impl<R: std::io::Read + ?Sized> Read for R {}
545
546/// Write trait. Under `std` it's a supertrait alias for
547/// [`std::io::Write`]; under `no_std + alloc` it carries its own
548/// signature mirroring the std contract.
549#[cfg(feature = "std")]
550pub trait Write: std::io::Write {}
551#[cfg(feature = "std")]
552impl<W: std::io::Write + ?Sized> Write for W {}
553
554/// Seek trait. Under `std` it's a supertrait alias for
555/// [`std::io::Seek`]; under `no_std + alloc` it carries its own
556/// signature mirroring the std contract.
557#[cfg(feature = "std")]
558pub trait Seek: std::io::Seek {}
559#[cfg(feature = "std")]
560impl<S: std::io::Seek + ?Sized> Seek for S {}
561
562/// Buffered-read trait. Under `std` this is a supertrait alias of
563/// [`std::io::BufRead`] (blanket-implemented), so std readers satisfy it
564/// directly; under `no_std` it is the native trait defined below.
565#[cfg(feature = "std")]
566pub trait BufRead: std::io::BufRead {}
567#[cfg(feature = "std")]
568impl<B: std::io::BufRead + ?Sized> BufRead for B {}
569
570/// Read trait mirroring [`std::io::Read`]. Only the methods this
571/// crate depends on are surfaced; default implementations follow the
572/// std contract verbatim so behaviour matches.
573#[cfg(not(feature = "std"))]
574pub trait Read {
575    /// Read bytes into `buf`. Returns the number of bytes read. A
576    /// return value of `0` indicates the source has reached EOF.
577    ///
578    /// # Errors
579    ///
580    /// Returns any I/O error encountered by the backing reader.
581    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
582
583    /// Read exactly `buf.len()` bytes. Returns
584    /// [`ErrorKind::UnexpectedEof`] if EOF is reached before the
585    /// buffer is full.
586    ///
587    /// # Errors
588    ///
589    /// Returns any I/O error from the underlying reader, or
590    /// [`ErrorKind::UnexpectedEof`] on short read.
591    fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
592        while !buf.is_empty() {
593            match self.read(buf) {
594                Ok(0) => break,
595                Ok(n) => {
596                    // `split_at_mut(n)` keeps the crate-level
597                    // `#![deny(clippy::indexing_slicing)]` happy
598                    // under `--no-default-features --features
599                    // alloc` clippy: indexing form `buf = &mut
600                    // buf[n..]` would lint-fail on the no-std
601                    // build even though `n` is bounded by the
602                    // `Ok(n)` return contract of `read()`.
603                    let (_, rest) = buf.split_at_mut(n);
604                    buf = rest;
605                }
606                Err(e) if e.kind() == ErrorKind::Interrupted => {}
607                Err(e) => return Err(e),
608            }
609        }
610        if buf.is_empty() {
611            Ok(())
612        } else {
613            // Match the stable message text `std::io::Read::read_exact`
614            // emits on the same short-read condition — callers that
615            // grep diagnostics for "failed to fill whole buffer" keep
616            // working without a feature-conditional branch.
617            Err(Error::new(
618                ErrorKind::UnexpectedEof,
619                "failed to fill whole buffer",
620            ))
621        }
622    }
623
624    /// Adapter that reads at most `limit` bytes from this reader, mirroring
625    /// [`std::io::Read::take`]. Used to bound a parser against a forged length
626    /// prefix.
627    fn take(self, limit: u64) -> Take<Self>
628    where
629        Self: Sized,
630    {
631        Take { inner: self, limit }
632    }
633}
634
635/// Limit-bounded reader returned by [`Read::take`], mirroring
636/// [`std::io::Take`].
637#[cfg(not(feature = "std"))]
638pub struct Take<R> {
639    inner: R,
640    limit: u64,
641}
642
643#[cfg(not(feature = "std"))]
644impl<R: Read> Read for Take<R> {
645    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
646        if self.limit == 0 {
647            return Ok(0);
648        }
649        // Cap this read at the remaining limit; `min` keeps `max <= buf.len()`.
650        let max = (buf.len() as u64).min(self.limit) as usize;
651        // `split_at_mut` instead of `&mut buf[..max]` to satisfy the crate's
652        // `deny(clippy::indexing_slicing)` on the no_std build.
653        let (head, _) = buf.split_at_mut(max);
654        let n = self.inner.read(head)?;
655        self.limit -= n as u64;
656        Ok(n)
657    }
658}
659
660/// Buffered-read trait mirroring the [`std::io::BufRead`] subset the storage
661/// layer uses (`fill_buf` + `consume`). Lets a record reader peek at the next
662/// bytes — and detect a clean EOF via an empty fill — without consuming them.
663#[cfg(not(feature = "std"))]
664pub trait BufRead: Read {
665    /// Return the buffer's current contents, refilling from the underlying
666    /// reader when empty. An empty return means EOF.
667    ///
668    /// # Errors
669    ///
670    /// Returns any I/O error from the underlying reader.
671    fn fill_buf(&mut self) -> Result<&[u8]>;
672
673    /// Mark `amt` bytes from the start of the buffer as consumed so they are
674    /// not returned by the next [`fill_buf`](Self::fill_buf)/[`read`](Read::read).
675    fn consume(&mut self, amt: usize);
676}
677
678/// Write trait mirroring [`std::io::Write`].
679#[cfg(not(feature = "std"))]
680pub trait Write {
681    /// Write `buf` into the sink. Returns the number of bytes
682    /// accepted by the writer in this call.
683    ///
684    /// # Errors
685    ///
686    /// Returns any I/O error from the underlying writer.
687    fn write(&mut self, buf: &[u8]) -> Result<usize>;
688
689    /// Flush buffered output to the underlying medium.
690    ///
691    /// # Errors
692    ///
693    /// Returns any I/O error from the underlying writer.
694    fn flush(&mut self) -> Result<()>;
695
696    /// Write the entire `buf`, retrying on `Interrupted` and
697    /// returning [`ErrorKind::WriteZero`] if the writer stops
698    /// accepting bytes early. Matches the semantics of
699    /// [`std::io::Write::write_all`].
700    ///
701    /// # Errors
702    ///
703    /// Returns the underlying writer's error, or
704    /// [`ErrorKind::WriteZero`] on short write.
705    fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
706        while !buf.is_empty() {
707            match self.write(buf) {
708                Ok(0) => {
709                    return Err(Error::new(
710                        ErrorKind::WriteZero,
711                        "failed to write whole buffer",
712                    ));
713                }
714                Ok(n) => {
715                    // `split_at(n)` keeps the crate-level
716                    // `#![deny(clippy::indexing_slicing)]` happy
717                    // under `--no-default-features --features
718                    // alloc` clippy: same reasoning as the
719                    // matching `read_exact` default impl above.
720                    let (_, rest) = buf.split_at(n);
721                    buf = rest;
722                }
723                Err(e) if e.kind() == ErrorKind::Interrupted => {}
724                Err(e) => return Err(e),
725            }
726        }
727        Ok(())
728    }
729}
730
731/// Seek trait mirroring [`std::io::Seek`].
732#[cfg(not(feature = "std"))]
733pub trait Seek {
734    /// Seek the stream cursor to the offset described by `pos`.
735    /// Returns the resulting absolute byte offset from the start of
736    /// the stream.
737    ///
738    /// # Errors
739    ///
740    /// Returns any I/O error from the underlying seeker.
741    fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
742
743    /// Current stream position (offset from the start). Mirrors
744    /// [`std::io::Seek::stream_position`].
745    ///
746    /// # Errors
747    ///
748    /// Returns any I/O error from the underlying seeker.
749    fn stream_position(&mut self) -> Result<u64> {
750        self.seek(SeekFrom::Current(0))
751    }
752
753    /// Seek relative to the current position. Mirrors
754    /// [`std::io::Seek::seek_relative`].
755    ///
756    /// # Errors
757    ///
758    /// Returns any I/O error from the underlying seeker.
759    fn seek_relative(&mut self, offset: i64) -> Result<()> {
760        self.seek(SeekFrom::Current(offset))?;
761        Ok(())
762    }
763}
764
765// Blanket forwarding impls so readers/writers behind a reference or a box
766// (e.g. `&mut R`, `Box<dyn FsFile>`) satisfy the io traits — std provides the
767// equivalent blankets for its own io traits, so this only fills the no_std gap.
768#[cfg(not(feature = "std"))]
769impl<R: Read + ?Sized> Read for &mut R {
770    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
771        (**self).read(buf)
772    }
773    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
774        (**self).read_exact(buf)
775    }
776}
777
778#[cfg(not(feature = "std"))]
779impl<R: Read + ?Sized> Read for alloc::boxed::Box<R> {
780    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
781        (**self).read(buf)
782    }
783    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
784        (**self).read_exact(buf)
785    }
786}
787
788#[cfg(not(feature = "std"))]
789impl<W: Write + ?Sized> Write for &mut W {
790    fn write(&mut self, buf: &[u8]) -> Result<usize> {
791        (**self).write(buf)
792    }
793    fn flush(&mut self) -> Result<()> {
794        (**self).flush()
795    }
796    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
797        (**self).write_all(buf)
798    }
799}
800
801#[cfg(not(feature = "std"))]
802impl<W: Write + ?Sized> Write for alloc::boxed::Box<W> {
803    fn write(&mut self, buf: &[u8]) -> Result<usize> {
804        (**self).write(buf)
805    }
806    fn flush(&mut self) -> Result<()> {
807        (**self).flush()
808    }
809    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
810        (**self).write_all(buf)
811    }
812}
813
814#[cfg(not(feature = "std"))]
815impl<S: Seek + ?Sized> Seek for &mut S {
816    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
817        (**self).seek(pos)
818    }
819    fn stream_position(&mut self) -> Result<u64> {
820        (**self).stream_position()
821    }
822    fn seek_relative(&mut self, offset: i64) -> Result<()> {
823        (**self).seek_relative(offset)
824    }
825}
826
827#[cfg(not(feature = "std"))]
828impl<S: Seek + ?Sized> Seek for alloc::boxed::Box<S> {
829    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
830        (**self).seek(pos)
831    }
832    fn stream_position(&mut self) -> Result<u64> {
833        (**self).stream_position()
834    }
835    fn seek_relative(&mut self, offset: i64) -> Result<()> {
836        (**self).seek_relative(offset)
837    }
838}
839
840#[cfg(not(feature = "std"))]
841impl<B: BufRead + ?Sized> BufRead for &mut B {
842    fn fill_buf(&mut self) -> Result<&[u8]> {
843        (**self).fill_buf()
844    }
845    fn consume(&mut self, amt: usize) {
846        (**self).consume(amt);
847    }
848}
849
850#[cfg(not(feature = "std"))]
851impl<B: BufRead + ?Sized> BufRead for alloc::boxed::Box<B> {
852    fn fill_buf(&mut self) -> Result<&[u8]> {
853        (**self).fill_buf()
854    }
855    fn consume(&mut self, amt: usize) {
856        (**self).consume(amt);
857    }
858}
859
860// `varint-rs` only blankets its traits over `std::io` (under its `std`
861// feature), and a foreign trait cannot be blanket-impl'd here (orphan rule).
862// So `no_std` builds get crate-local varint extension traits with the same
863// method surface, blanketed over this module's `Read`/`Write`. The encoding is
864// canonical unsigned LEB128 — byte-identical to `varint-rs` — so frames written
865// under `std` and `no_std` interoperate. Call sites swap only the import path.
866/// Writes unsigned integers as canonical LEB128 varints over a [`Write`].
867///
868/// The `no_std` counterpart of `varint-rs`'s `VarintWriter`; the encoding is
869/// identical so frames interoperate across `std` / `no_std` builds.
870#[cfg(not(feature = "std"))]
871pub trait VarintWriter: Write {
872    /// Writes `value` as canonical unsigned LEB128.
873    ///
874    /// # Errors
875    /// Propagates the underlying writer's error.
876    fn write_u64_varint(&mut self, mut value: u64) -> Result<()> {
877        loop {
878            let mut byte = (value & 0x7f) as u8;
879            value >>= 7;
880            if value != 0 {
881                byte |= 0x80;
882            }
883            self.write_all(&[byte])?;
884            if value == 0 {
885                return Ok(());
886            }
887        }
888    }
889
890    /// Writes `value` as canonical unsigned LEB128.
891    ///
892    /// # Errors
893    /// Propagates the underlying writer's error.
894    fn write_u32_varint(&mut self, value: u32) -> Result<()> {
895        self.write_u64_varint(u64::from(value))
896    }
897
898    /// Writes `value` as canonical unsigned LEB128.
899    ///
900    /// # Errors
901    /// Propagates the underlying writer's error.
902    fn write_u16_varint(&mut self, value: u16) -> Result<()> {
903        self.write_u64_varint(u64::from(value))
904    }
905}
906
907#[cfg(not(feature = "std"))]
908impl<W: Write + ?Sized> VarintWriter for W {}
909
910/// Reads canonical LEB128 varints over a [`Read`].
911///
912/// The `no_std` counterpart of `varint-rs`'s `VarintReader`; the encoding is
913/// identical so frames interoperate across `std` / `no_std` builds.
914#[cfg(not(feature = "std"))]
915pub trait VarintReader: Read {
916    /// Reads a canonical unsigned LEB128 value.
917    ///
918    /// # Errors
919    /// [`ErrorKind::UnexpectedEof`] on a truncated value, or
920    /// [`ErrorKind::InvalidData`] on an overlong encoding; plus any underlying
921    /// reader error.
922    fn read_u64_varint(&mut self) -> Result<u64> {
923        let mut result: u64 = 0;
924        let mut shift: u32 = 0;
925        loop {
926            let mut byte = [0u8; 1];
927            self.read_exact(&mut byte)?;
928            // 10 groups of 7 bits cover a full u64; reject overlong encodings.
929            if shift >= 64 {
930                return Err(Error::new(ErrorKind::InvalidData, "varint overflows u64"));
931            }
932            // 10th byte (shift == 63): only bit 0 fits in u64. Any higher bit
933            // set is a non-canonical / overflowing encoding — reject it rather
934            // than silently shifting those bits away.
935            if shift == 63 && byte[0] & 0xFE != 0 {
936                return Err(Error::new(
937                    ErrorKind::InvalidData,
938                    "non-canonical varint overflow",
939                ));
940            }
941            result |= (u64::from(byte[0] & 0x7f)) << shift;
942            if byte[0] & 0x80 == 0 {
943                return Ok(result);
944            }
945            shift += 7;
946        }
947    }
948
949    /// Reads a canonical unsigned LEB128 value, narrowed to `u32`.
950    ///
951    /// # Errors
952    /// As [`Self::read_u64_varint`], plus [`ErrorKind::InvalidData`] if the
953    /// value does not fit `u32`.
954    fn read_u32_varint(&mut self) -> Result<u32> {
955        let v = self.read_u64_varint()?;
956        u32::try_from(v).map_err(|_| Error::new(ErrorKind::InvalidData, "varint exceeds u32"))
957    }
958
959    /// Reads a canonical unsigned LEB128 value, narrowed to `u16`.
960    ///
961    /// # Errors
962    /// As [`Self::read_u64_varint`], plus [`ErrorKind::InvalidData`] if the
963    /// value does not fit `u16`.
964    fn read_u16_varint(&mut self) -> Result<u16> {
965        let v = self.read_u64_varint()?;
966        u16::try_from(v).map_err(|_| Error::new(ErrorKind::InvalidData, "varint exceeds u16"))
967    }
968}
969
970#[cfg(not(feature = "std"))]
971impl<R: Read + ?Sized> VarintReader for R {}
972
973// ---------------------------------------------------------------------------
974// Fixed-width integer I/O — a `no_std`-capable, drop-in replacement for
975// `byteorder`'s `WriteBytesExt` / `ReadBytesExt` / `ByteOrder` over this
976// module's `Read` / `Write` (which is `std::io` under `std` and the native
977// traits under `no_std`). The API mirrors `byteorder` exactly — same
978// `w.write_u32::<LittleEndian>(x)` / `r.read_u32::<LittleEndian>()` call
979// shape — so migrating a wire-format module off `byteorder` is just an
980// import swap, and the encoding is byte-identical (`to_le_bytes` /
981// `to_be_bytes`). High-level round-trip tests cover correctness; no
982// low-level duplication here.
983// ---------------------------------------------------------------------------
984
985/// Byte-order marker for fixed-width integer encoding.
986///
987/// Mirrors `byteorder::ByteOrder`. Implemented by [`LittleEndian`] /
988/// [`BigEndian`]; methods convert between fixed-size byte arrays and integers
989/// so call sites stay free of slice indexing.
990pub trait ByteOrder {
991    /// Decode a `u16` from its 2-byte representation.
992    fn u16_from(b: [u8; 2]) -> u16;
993    /// Decode a `u32` from its 4-byte representation.
994    fn u32_from(b: [u8; 4]) -> u32;
995    /// Decode a `u64` from its 8-byte representation.
996    fn u64_from(b: [u8; 8]) -> u64;
997    /// Decode a `u128` from its 16-byte representation.
998    fn u128_from(b: [u8; 16]) -> u128;
999    /// Encode a `u16` to its 2-byte representation.
1000    fn u16_to(n: u16) -> [u8; 2];
1001    /// Encode a `u32` to its 4-byte representation.
1002    fn u32_to(n: u32) -> [u8; 4];
1003    /// Encode a `u64` to its 8-byte representation.
1004    fn u64_to(n: u64) -> [u8; 8];
1005    /// Encode a `u128` to its 16-byte representation.
1006    fn u128_to(n: u128) -> [u8; 16];
1007
1008    // Static slice helpers matching `byteorder::ByteOrder`'s API, so call
1009    // sites of the form `LittleEndian::write_u32(buf, n)` / `read_u32(buf)`
1010    // migrate unchanged. `split_at[_mut]` (not indexing) keeps the crate-level
1011    // `deny(indexing_slicing)` happy; like byteorder, they panic if `buf` is
1012    // shorter than the integer width (a caller bug, not a data condition).
1013    /// Read a `u16` from the first 2 bytes of `buf`.
1014    #[must_use]
1015    fn read_u16(buf: &[u8]) -> u16 {
1016        let (head, _) = buf.split_at(2);
1017        let mut a = [0u8; 2];
1018        a.copy_from_slice(head);
1019        Self::u16_from(a)
1020    }
1021    /// Read a `u32` from the first 4 bytes of `buf`.
1022    #[must_use]
1023    fn read_u32(buf: &[u8]) -> u32 {
1024        let (head, _) = buf.split_at(4);
1025        let mut a = [0u8; 4];
1026        a.copy_from_slice(head);
1027        Self::u32_from(a)
1028    }
1029    /// Read a `u64` from the first 8 bytes of `buf`.
1030    #[must_use]
1031    fn read_u64(buf: &[u8]) -> u64 {
1032        let (head, _) = buf.split_at(8);
1033        let mut a = [0u8; 8];
1034        a.copy_from_slice(head);
1035        Self::u64_from(a)
1036    }
1037    /// Write `n` into the first 2 bytes of `buf`.
1038    fn write_u16(buf: &mut [u8], n: u16) {
1039        let (head, _) = buf.split_at_mut(2);
1040        head.copy_from_slice(&Self::u16_to(n));
1041    }
1042    /// Write `n` into the first 4 bytes of `buf`.
1043    fn write_u32(buf: &mut [u8], n: u32) {
1044        let (head, _) = buf.split_at_mut(4);
1045        head.copy_from_slice(&Self::u32_to(n));
1046    }
1047    /// Write `n` into the first 8 bytes of `buf`.
1048    fn write_u64(buf: &mut [u8], n: u64) {
1049        let (head, _) = buf.split_at_mut(8);
1050        head.copy_from_slice(&Self::u64_to(n));
1051    }
1052}
1053
1054/// Little-endian [`ByteOrder`] (matches `byteorder::LittleEndian`).
1055#[derive(Clone, Copy, Debug)]
1056pub enum LittleEndian {}
1057/// Big-endian [`ByteOrder`] (matches `byteorder::BigEndian`).
1058#[derive(Clone, Copy, Debug)]
1059pub enum BigEndian {}
1060
1061/// Short alias for [`LittleEndian`] (matches `byteorder::LE`).
1062pub type LE = LittleEndian;
1063/// Short alias for [`BigEndian`] (matches `byteorder::BE`).
1064pub type BE = BigEndian;
1065
1066impl ByteOrder for LittleEndian {
1067    fn u16_from(b: [u8; 2]) -> u16 {
1068        u16::from_le_bytes(b)
1069    }
1070    fn u32_from(b: [u8; 4]) -> u32 {
1071        u32::from_le_bytes(b)
1072    }
1073    fn u64_from(b: [u8; 8]) -> u64 {
1074        u64::from_le_bytes(b)
1075    }
1076    fn u128_from(b: [u8; 16]) -> u128 {
1077        u128::from_le_bytes(b)
1078    }
1079    fn u16_to(n: u16) -> [u8; 2] {
1080        n.to_le_bytes()
1081    }
1082    fn u32_to(n: u32) -> [u8; 4] {
1083        n.to_le_bytes()
1084    }
1085    fn u64_to(n: u64) -> [u8; 8] {
1086        n.to_le_bytes()
1087    }
1088    fn u128_to(n: u128) -> [u8; 16] {
1089        n.to_le_bytes()
1090    }
1091}
1092
1093impl ByteOrder for BigEndian {
1094    fn u16_from(b: [u8; 2]) -> u16 {
1095        u16::from_be_bytes(b)
1096    }
1097    fn u32_from(b: [u8; 4]) -> u32 {
1098        u32::from_be_bytes(b)
1099    }
1100    fn u64_from(b: [u8; 8]) -> u64 {
1101        u64::from_be_bytes(b)
1102    }
1103    fn u128_from(b: [u8; 16]) -> u128 {
1104        u128::from_be_bytes(b)
1105    }
1106    fn u16_to(n: u16) -> [u8; 2] {
1107        n.to_be_bytes()
1108    }
1109    fn u32_to(n: u32) -> [u8; 4] {
1110        n.to_be_bytes()
1111    }
1112    fn u64_to(n: u64) -> [u8; 8] {
1113        n.to_be_bytes()
1114    }
1115    fn u128_to(n: u128) -> [u8; 16] {
1116        n.to_be_bytes()
1117    }
1118}
1119
1120/// Fixed-width integer writes over [`Write`], mirroring
1121/// `byteorder::WriteBytesExt`. Blanket-implemented for every [`Write`].
1122pub trait WriteBytesExt: Write {
1123    /// Write a single byte.
1124    ///
1125    /// # Errors
1126    /// Propagates the underlying writer's error.
1127    fn write_u8(&mut self, n: u8) -> Result<()> {
1128        self.write_all(&[n])?;
1129        Ok(())
1130    }
1131    /// Write a signed byte.
1132    ///
1133    /// # Errors
1134    /// Propagates the underlying writer's error.
1135    fn write_i8(&mut self, n: i8) -> Result<()> {
1136        self.write_all(&n.to_le_bytes())?;
1137        Ok(())
1138    }
1139    /// Write a `u16` in the byte order `T`.
1140    ///
1141    /// # Errors
1142    /// Propagates the underlying writer's error.
1143    fn write_u16<T: ByteOrder>(&mut self, n: u16) -> Result<()> {
1144        self.write_all(&T::u16_to(n))?;
1145        Ok(())
1146    }
1147    /// Write a `u32` in the byte order `T`.
1148    ///
1149    /// # Errors
1150    /// Propagates the underlying writer's error.
1151    fn write_u32<T: ByteOrder>(&mut self, n: u32) -> Result<()> {
1152        self.write_all(&T::u32_to(n))?;
1153        Ok(())
1154    }
1155    /// Write a `u64` in the byte order `T`.
1156    ///
1157    /// # Errors
1158    /// Propagates the underlying writer's error.
1159    fn write_u64<T: ByteOrder>(&mut self, n: u64) -> Result<()> {
1160        self.write_all(&T::u64_to(n))?;
1161        Ok(())
1162    }
1163    /// Write a `u128` in the byte order `T`.
1164    ///
1165    /// # Errors
1166    /// Propagates the underlying writer's error.
1167    fn write_u128<T: ByteOrder>(&mut self, n: u128) -> Result<()> {
1168        self.write_all(&T::u128_to(n))?;
1169        Ok(())
1170    }
1171    /// Write an `f32` (IEEE-754 bit pattern) in the byte order `T`.
1172    ///
1173    /// # Errors
1174    /// Propagates the underlying writer's error.
1175    fn write_f32<T: ByteOrder>(&mut self, n: f32) -> Result<()> {
1176        self.write_u32::<T>(n.to_bits())
1177    }
1178    /// Write an `f64` (IEEE-754 bit pattern) in the byte order `T`.
1179    ///
1180    /// # Errors
1181    /// Propagates the underlying writer's error.
1182    fn write_f64<T: ByteOrder>(&mut self, n: f64) -> Result<()> {
1183        self.write_u64::<T>(n.to_bits())
1184    }
1185}
1186impl<W: Write + ?Sized> WriteBytesExt for W {}
1187
1188/// Fixed-width integer reads over [`Read`], mirroring
1189/// `byteorder::ReadBytesExt`. Blanket-implemented for every [`Read`].
1190pub trait ReadBytesExt: Read {
1191    /// Read a single byte.
1192    ///
1193    /// # Errors
1194    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1195    fn read_u8(&mut self) -> Result<u8> {
1196        let mut b = [0u8; 1];
1197        self.read_exact(&mut b)?;
1198        Ok(u8::from_le_bytes(b))
1199    }
1200    /// Read a signed byte.
1201    ///
1202    /// # Errors
1203    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1204    fn read_i8(&mut self) -> Result<i8> {
1205        let mut b = [0u8; 1];
1206        self.read_exact(&mut b)?;
1207        Ok(i8::from_le_bytes(b))
1208    }
1209    /// Read a `u16` in the byte order `T`.
1210    ///
1211    /// # Errors
1212    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1213    fn read_u16<T: ByteOrder>(&mut self) -> Result<u16> {
1214        let mut b = [0u8; 2];
1215        self.read_exact(&mut b)?;
1216        Ok(T::u16_from(b))
1217    }
1218    /// Read a `u32` in the byte order `T`.
1219    ///
1220    /// # Errors
1221    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1222    fn read_u32<T: ByteOrder>(&mut self) -> Result<u32> {
1223        let mut b = [0u8; 4];
1224        self.read_exact(&mut b)?;
1225        Ok(T::u32_from(b))
1226    }
1227    /// Read a `u64` in the byte order `T`.
1228    ///
1229    /// # Errors
1230    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1231    fn read_u64<T: ByteOrder>(&mut self) -> Result<u64> {
1232        let mut b = [0u8; 8];
1233        self.read_exact(&mut b)?;
1234        Ok(T::u64_from(b))
1235    }
1236    /// Read a `u128` in the byte order `T`.
1237    ///
1238    /// # Errors
1239    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1240    fn read_u128<T: ByteOrder>(&mut self) -> Result<u128> {
1241        let mut b = [0u8; 16];
1242        self.read_exact(&mut b)?;
1243        Ok(T::u128_from(b))
1244    }
1245    /// Read an `f32` (IEEE-754 bit pattern) in the byte order `T`.
1246    ///
1247    /// # Errors
1248    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1249    fn read_f32<T: ByteOrder>(&mut self) -> Result<f32> {
1250        Ok(f32::from_bits(self.read_u32::<T>()?))
1251    }
1252    /// Read an `f64` (IEEE-754 bit pattern) in the byte order `T`.
1253    ///
1254    /// # Errors
1255    /// [`ErrorKind::UnexpectedEof`] on short read, or the reader's error.
1256    fn read_f64<T: ByteOrder>(&mut self) -> Result<f64> {
1257        Ok(f64::from_bits(self.read_u64::<T>()?))
1258    }
1259}
1260impl<R: Read + ?Sized> ReadBytesExt for R {}
1261
1262// `no_std` concrete impls so wire-format code that writes into a `Vec<u8>` or
1263// reads from a `&[u8]` keeps compiling once it moves off `byteorder` (under
1264// `std` these come from `std::io`'s own impls via the supertrait aliases).
1265#[cfg(not(feature = "std"))]
1266impl Write for alloc::vec::Vec<u8> {
1267    fn write(&mut self, buf: &[u8]) -> Result<usize> {
1268        self.extend_from_slice(buf);
1269        Ok(buf.len())
1270    }
1271    fn flush(&mut self) -> Result<()> {
1272        Ok(())
1273    }
1274    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
1275        self.extend_from_slice(buf);
1276        Ok(())
1277    }
1278}
1279
1280#[cfg(not(feature = "std"))]
1281impl Read for &[u8] {
1282    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
1283        let n = buf.len().min(self.len());
1284        let (head, rest) = self.split_at(n);
1285        let (dst, _) = buf.split_at_mut(n);
1286        dst.copy_from_slice(head);
1287        *self = rest;
1288        Ok(n)
1289    }
1290}
1291
1292// In-memory cursor, mirroring `crate::io::Cursor`. Under `std` it IS
1293// `crate::io::Cursor` (re-export); under `no_std` it's a local equivalent so
1294// wire-format code reading/seeking over a `&[u8]` or writing into a `Vec<u8>`
1295// keeps compiling. Same API surface (`new` / `position` / `set_position` /
1296// `into_inner` / `get_ref`) so call sites are identical across both builds.
1297#[cfg(feature = "std")]
1298pub use std::io::Cursor;
1299
1300/// In-memory `Read` + `Seek` (and `Write` for `Vec` inner) cursor over a
1301/// byte buffer, mirroring [`crate::io::Cursor`] for `no_std` builds.
1302#[cfg(not(feature = "std"))]
1303pub struct Cursor<T> {
1304    inner: T,
1305    pos: u64,
1306}
1307
1308#[cfg(not(feature = "std"))]
1309impl<T> Cursor<T> {
1310    /// Wraps `inner`, starting the cursor at position 0.
1311    pub const fn new(inner: T) -> Self {
1312        Self { inner, pos: 0 }
1313    }
1314    /// Current byte position.
1315    #[must_use]
1316    pub const fn position(&self) -> u64 {
1317        self.pos
1318    }
1319    /// Sets the byte position.
1320    pub const fn set_position(&mut self, pos: u64) {
1321        self.pos = pos;
1322    }
1323    /// Consumes the cursor, returning the wrapped buffer.
1324    pub fn into_inner(self) -> T {
1325        self.inner
1326    }
1327    /// Borrows the wrapped buffer.
1328    pub const fn get_ref(&self) -> &T {
1329        &self.inner
1330    }
1331    /// Mutably borrows the wrapped buffer.
1332    pub const fn get_mut(&mut self) -> &mut T {
1333        &mut self.inner
1334    }
1335}
1336
1337#[cfg(not(feature = "std"))]
1338impl<T: AsRef<[u8]>> Read for Cursor<T> {
1339    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
1340        let inner = self.inner.as_ref();
1341        // Position past end → 0 bytes (matches std).
1342        let start = (self.pos as usize).min(inner.len());
1343        let (_, remaining) = inner.split_at(start);
1344        let n = buf.len().min(remaining.len());
1345        let (src, _) = remaining.split_at(n);
1346        let (dst, _) = buf.split_at_mut(n);
1347        dst.copy_from_slice(src);
1348        self.pos += n as u64;
1349        Ok(n)
1350    }
1351}
1352
1353#[cfg(not(feature = "std"))]
1354impl<T: AsRef<[u8]>> Seek for Cursor<T> {
1355    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
1356        let len = self.inner.as_ref().len() as u64;
1357        // Clamp a caller-supplied relative seek into `[0, u64::MAX]` rather than
1358        // underflowing on a negative offset that would point before the start
1359        // (the offset is internal, not on-disk-decoded input).
1360        let new = match pos {
1361            SeekFrom::Start(n) => n,
1362            SeekFrom::End(off) => len.saturating_add_signed(off),
1363            SeekFrom::Current(off) => self.pos.saturating_add_signed(off),
1364        };
1365        self.pos = new;
1366        Ok(new)
1367    }
1368}
1369
1370#[cfg(not(feature = "std"))]
1371impl Write for Cursor<alloc::vec::Vec<u8>> {
1372    fn write(&mut self, buf: &[u8]) -> Result<usize> {
1373        // Like std: writing at `pos` overwrites in place and extends the Vec
1374        // when the write runs past the current end.
1375        let pos = self.pos as usize;
1376        if pos > self.inner.len() {
1377            self.inner.resize(pos, 0);
1378        }
1379        let end = pos + buf.len();
1380        if end > self.inner.len() {
1381            self.inner.resize(end, 0);
1382        }
1383        let (_, tail) = self.inner.split_at_mut(pos);
1384        let (dst, _) = tail.split_at_mut(buf.len());
1385        dst.copy_from_slice(buf);
1386        self.pos = end as u64;
1387        Ok(buf.len())
1388    }
1389    fn flush(&mut self) -> Result<()> {
1390        Ok(())
1391    }
1392}
1393
1394#[cfg(feature = "std")]
1395pub use std::io::BufReader;
1396
1397/// Buffering reader, mirroring the subset of [`std::io::BufReader`] the storage
1398/// layer uses (record framing reads), for `no_std` builds. Coalesces small
1399/// reads and exposes [`BufRead`] so a record parser can peek for a clean EOF.
1400#[cfg(not(feature = "std"))]
1401pub struct BufReader<R: Read> {
1402    inner: R,
1403    // `capacity`-sized; the valid (filled, not-yet-consumed) window is
1404    // `buf[pos..cap]`.
1405    buf: alloc::vec::Vec<u8>,
1406    pos: usize,
1407    cap: usize,
1408}
1409
1410#[cfg(not(feature = "std"))]
1411impl<R: Read> BufReader<R> {
1412    /// Wraps `inner` with the default 8 KiB buffer.
1413    pub fn new(inner: R) -> Self {
1414        Self::with_capacity(8 * 1024, inner)
1415    }
1416
1417    /// Wraps `inner` with a `capacity`-byte buffer.
1418    pub fn with_capacity(capacity: usize, inner: R) -> Self {
1419        Self {
1420            inner,
1421            buf: alloc::vec![0u8; capacity],
1422            pos: 0,
1423            cap: 0,
1424        }
1425    }
1426
1427    /// Mutable access to the inner reader. Bypasses the buffer — mixing direct
1428    /// inner reads with buffered ones loses buffered bytes, like std.
1429    pub fn get_mut(&mut self) -> &mut R {
1430        &mut self.inner
1431    }
1432
1433    /// Shared access to the inner reader.
1434    pub fn get_ref(&self) -> &R {
1435        &self.inner
1436    }
1437}
1438
1439#[cfg(not(feature = "std"))]
1440impl<R: Read> Read for BufReader<R> {
1441    fn read(&mut self, dest: &mut [u8]) -> Result<usize> {
1442        // A read at least as large as the buffer, with nothing buffered, goes
1443        // straight to the inner reader (buffering would just add a copy).
1444        if self.pos >= self.cap && dest.len() >= self.buf.len() {
1445            return self.inner.read(dest);
1446        }
1447        let n = {
1448            let available = self.fill_buf()?;
1449            let n = available.len().min(dest.len());
1450            let (src, _) = available.split_at(n);
1451            let (dst, _) = dest.split_at_mut(n);
1452            dst.copy_from_slice(src);
1453            n
1454        };
1455        self.consume(n);
1456        Ok(n)
1457    }
1458}
1459
1460#[cfg(not(feature = "std"))]
1461impl<R: Read> BufRead for BufReader<R> {
1462    fn fill_buf(&mut self) -> Result<&[u8]> {
1463        if self.pos >= self.cap {
1464            self.cap = self.inner.read(&mut self.buf)?;
1465            self.pos = 0;
1466        }
1467        // Return `buf[pos..cap]` via split_at to keep `deny(indexing_slicing)`.
1468        let (_, rest) = self.buf.split_at(self.pos);
1469        let (out, _) = rest.split_at(self.cap - self.pos);
1470        Ok(out)
1471    }
1472
1473    fn consume(&mut self, amt: usize) {
1474        self.pos = (self.pos + amt).min(self.cap);
1475    }
1476}
1477
1478#[cfg(not(feature = "std"))]
1479impl<R: Read + Seek> Seek for BufReader<R> {
1480    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
1481        // The buffered bytes belong to the old position; drop them.
1482        self.pos = 0;
1483        self.cap = 0;
1484        self.inner.seek(pos)
1485    }
1486}
1487
1488#[cfg(feature = "std")]
1489pub use std::io::BufWriter;
1490
1491/// Buffering writer, mirroring the subset of [`std::io::BufWriter`] the
1492/// storage layer uses, for `no_std` builds. Coalesces small writes into one
1493/// `capacity`-sized buffer and flushes it to the inner writer on overflow,
1494/// explicit [`flush`](Write::flush), [`seek`](Seek::seek), or drop.
1495#[cfg(not(feature = "std"))]
1496pub struct BufWriter<W: Write> {
1497    inner: W,
1498    buf: alloc::vec::Vec<u8>,
1499}
1500
1501#[cfg(not(feature = "std"))]
1502impl<W: Write> BufWriter<W> {
1503    /// Wraps `inner` with the default 8 KiB buffer.
1504    pub fn new(inner: W) -> Self {
1505        Self::with_capacity(8 * 1024, inner)
1506    }
1507
1508    /// Wraps `inner` with a `capacity`-byte buffer.
1509    pub fn with_capacity(capacity: usize, inner: W) -> Self {
1510        Self {
1511            inner,
1512            buf: alloc::vec::Vec::with_capacity(capacity),
1513        }
1514    }
1515
1516    /// Mutable access to the inner writer. Does NOT flush the buffer first —
1517    /// matches [`std::io::BufWriter::get_mut`].
1518    pub fn get_mut(&mut self) -> &mut W {
1519        &mut self.inner
1520    }
1521
1522    /// Shared access to the inner writer.
1523    pub fn get_ref(&self) -> &W {
1524        &self.inner
1525    }
1526
1527    fn flush_buf(&mut self) -> Result<()> {
1528        if !self.buf.is_empty() {
1529            self.inner.write_all(&self.buf)?;
1530            self.buf.clear();
1531        }
1532        Ok(())
1533    }
1534}
1535
1536#[cfg(not(feature = "std"))]
1537impl<W: Write> Write for BufWriter<W> {
1538    fn write(&mut self, data: &[u8]) -> Result<usize> {
1539        // Flush first if this write wouldn't fit alongside what's buffered.
1540        if self.buf.len() + data.len() > self.buf.capacity() {
1541            self.flush_buf()?;
1542        }
1543        // A write at least as large as the whole buffer bypasses it entirely
1544        // (buffering it would just add a copy), matching std::io::BufWriter.
1545        if data.len() >= self.buf.capacity() {
1546            self.inner.write(data)
1547        } else {
1548            self.buf.extend_from_slice(data);
1549            Ok(data.len())
1550        }
1551    }
1552
1553    fn flush(&mut self) -> Result<()> {
1554        self.flush_buf()?;
1555        self.inner.flush()
1556    }
1557}
1558
1559#[cfg(not(feature = "std"))]
1560impl<W: Write + Seek> Seek for BufWriter<W> {
1561    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
1562        // Buffered bytes belong before the seek target, so flush them out
1563        // before moving the inner cursor (matches std::io::BufWriter).
1564        self.flush_buf()?;
1565        self.inner.seek(pos)
1566    }
1567}
1568
1569#[cfg(not(feature = "std"))]
1570impl<W: Write> Drop for BufWriter<W> {
1571    fn drop(&mut self) {
1572        // Best-effort flush, like std: a drop can't surface an error, and the
1573        // storage writer always flushes explicitly before teardown anyway.
1574        let _ = self.flush_buf();
1575    }
1576}
1577
1578#[cfg(test)]
1579mod tests;