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;