fixed_buffer/
lib.rs

1//! # fixed-buffer
2//! [![crates.io version](https://img.shields.io/crates/v/fixed-buffer.svg)](https://crates.io/crates/fixed-buffer)
3//! [![license: Apache 2.0](https://gitlab.com/leonhard-llc/fixed-buffer-rs/-/raw/main/license-apache-2.0.svg)](http://www.apache.org/licenses/LICENSE-2.0)
4//! [![unsafe forbidden](https://gitlab.com/leonhard-llc/fixed-buffer-rs/-/raw/main/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
5//! [![pipeline status](https://gitlab.com/leonhard-llc/fixed-buffer-rs/badges/main/pipeline.svg)](https://gitlab.com/leonhard-llc/fixed-buffer-rs/-/pipelines)
6//!
7//! This is a Rust library with fixed-size buffers,
8//! useful for network protocol parsers and file parsers.
9//!
10//! # Features
11//! - `forbid(unsafe_code)`
12//! - Depends only on `std`
13//! - Write bytes to the buffer and read them back
14//! - Lives on the stack
15//! - Does not allocate
16//! - Use it to read a stream, search for a delimiter,
17//!   and save leftover bytes for the next read.
18//! - No macros
19//! - Good test coverage (100%)
20//! - Async support by enabling cargo features `async-std-feature`, `futures-io`, `smol-feature`, or `tokio`.
21//!
22//! # Limitations
23//! - Not a circular buffer.
24//!   You can call `shift()` periodically
25//!   to move unread bytes to the front of the buffer.
26//!
27//! # Examples
28//! Read and handle requests from a remote client:
29//! ```rust
30//! use fixed_buffer::{deframe_line, FixedBuf};
31//! use std::io::Error;
32//! use std::net::TcpStream;
33//!
34//! # struct Request {}
35//! # impl Request {
36//! #     pub fn parse(line_bytes: &[u8]) -> Result<Request, Error> {
37//! #         Ok(Request{})
38//! #     }
39//! # }
40//! # fn handle_request(request: Request) -> Result<(), Error> {
41//! #     Ok(())
42//! # }
43//! fn handle_conn(mut tcp_stream: TcpStream) -> Result<(), Error> {
44//!     let mut buf: FixedBuf<4096> = FixedBuf::new();
45//!     loop {
46//!         // Read a line
47//!         // and leave leftover bytes in `buf`.
48//!         let line_bytes = match buf.read_frame(
49//!             &mut tcp_stream, deframe_line)? {
50//!                 Some(line_bytes) => line_bytes,
51//!                 None => return Ok(()),
52//!             };
53//!         let request = Request::parse(line_bytes)?;
54//!         handle_request(request)?;
55//!     }
56//! }
57//! ```
58//! For a complete example, see
59//! [`tests/server.rs`](https://gitlab.com/leonhard-llc/fixed-buffer-rs/-/blob/main/fixed-buffer/tests/server.rs).
60//!
61//! Read and process records:
62//! ```rust
63//! use fixed_buffer::FixedBuf;
64//! use std::io::{Error, ErrorKind, Read};
65//! use std::net::TcpStream;
66//!
67//! fn try_process_record(b: &[u8]) -> Result<usize, Error> {
68//!     if b.len() < 2 {
69//!         return Ok(0);
70//!     }
71//!     if b.starts_with("ab".as_bytes()) {
72//!         println!("found record");
73//!         Ok(2)
74//!     } else {
75//!         Err(Error::new(ErrorKind::InvalidData, "bad record"))
76//!     }
77//! }
78//!
79//! fn read_and_process<R: Read>(mut input: R) -> Result<(), Error> {
80//!     let mut buf: FixedBuf<1024> = FixedBuf::new();
81//!     loop {
82//!         // Read a chunk into the buffer.
83//!         if buf.copy_once_from(&mut input)? == 0 {
84//!             return if buf.len() == 0 {
85//!                 // EOF at record boundary
86//!                 Ok(())
87//!             } else {
88//!                 // EOF in the middle of a record
89//!                 Err(Error::from(
90//!                     ErrorKind::UnexpectedEof))
91//!             };
92//!         }
93//!         // Process records in the buffer.
94//!         loop {
95//!             let num_to_consume =
96//!                 try_process_record(buf.readable())?;
97//!             if num_to_consume == 0 {
98//!                 break;
99//!             }
100//!             buf.try_read_exact(num_to_consume).unwrap();
101//!         }
102//!         // Shift data in the buffer to free up
103//!         // space at the end for writing.
104//!         buf.shift();
105//!     }
106//! }
107//! # fn main() {
108//! #     read_and_process(std::io::Cursor::new(b"abab")).unwrap();
109//! #     read_and_process(std::io::Cursor::new(b"ababc")).unwrap_err();
110//! # }
111//! ```
112//!
113//! The `From<&[u8; SIZE]>` implementation is useful in tests.  Example:
114//! ```
115//! # use fixed_buffer::FixedBuf;
116//! use core::convert::From;
117//! assert_eq!(3, FixedBuf::from(b"abc").len());
118//! ```
119//!
120//! # Cargo Geiger Safety Report
121//! # Alternatives
122//! - [`bytes`](https://crates.io/crates/bytes), lots of [`unsafe`](https://github.com/search?q=repo%3Atokio-rs%2Fbytes+unsafe+path%3Asrc%2F**&type=code)
123//! - [`buf_redux`](https://crates.io/crates/buf_redux), circular buffer support, updated in 2019
124//! - [`std::io::BufReader`](https://doc.rust-lang.org/std/io/struct.BufReader.html)
125//! - [`std::io::BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html)
126//! - [`static-buffer`](https://crates.io/crates/static-buffer), updated in 2016
127//! - [`block-buffer`](https://crates.io/crates/block-buffer), for processing fixed-length blocks of data, some [`unsafe`](https://github.com/search?q=repo%3ARustCrypto%2Futils+unsafe+path%3Ablock-buffer%2F**&type=code)
128//! - [`arrayvec`](https://crates.io/crates/arrayvec), vector with fixed capacity, some [`unsafe`](https://github.com/search?q=repo%3Abluss%2Farrayvec+unsafe+path%3Asrc%2F**&type=code)
129//!
130//! # Changelog
131//! - v1.0.2 2025-06-29 - Add `read_frame_tokio`.
132//! - v1.0.1 2025-05-30 - Clarify docs.
133//! - v1.0.0 2024-10-20 - `From<&[u8; SIZE]>`, `FixedBuffer::from(b"x")`
134//! - v0.5.0 2022-03-21 - Move `ReadWriteChain` and `ReadWriteTake` to new
135//!   [`read-write-ext`](https://crates.io/crates/read-write-ext) crate.
136//! - v0.4.0 2022-03-21
137//!   - `From<[u8; SIZE]>`, `FixedBuffer::from([0])`
138//!   - `write_bytes` to take `AsRef<[u8]>`
139//!   - Rename `try_read_exact` to `read_and_copy_exact`.
140//!   - Rename `try_read_bytes` to `try_read_exact`.
141//!   - Remove `empty`, `filled`, `read_byte`, `read_bytes`, `try_parse`, and `write_str`.
142//!   - `deframe` to allow consuming bytes without returning a frame
143//!   - `write_bytes` to write as many bytes as it can,
144//!     and return new `NoWritableSpace` error only when it cannot write any bytes.
145//!     Remove `NotEnoughSpaceError`.  The method now behaves like `std::io::Write::write`.
146//!
147//! <details>
148//! <summary>Older changelog entries</summary>
149//!
150//! - v0.3.1 - Implement `From<NotEnoughSpaceError>` and `From<MalformedInputError>` for `String`.
151//! - v0.3.0 - Breaking API changes:
152//!   - Change type parameter to const buffer size. Example: `FixedBuf<1024>`.
153//!   - Remove `new` arg.
154//!   - Remove `capacity`.
155//!   - Remove `Copy` impl.
156//!   - Change `writable` return type to `&mut [u8]`.
157//! - v0.2.3
158//!   - Add
159//!     [`read_byte`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.read_byte),
160//!     [`try_read_byte`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.try_read_byte),
161//!     [`try_read_bytes`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.try_read_bytes),
162//!     [`try_read_exact`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.try_read_exact),
163//!     [`try_parse`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.try_parse).
164//!   - Implement [`UnwindSafe`](https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html)
165//! - v0.2.2 - Add badges to readme
166//! - v0.2.1 - Add
167//!   [`deframe`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.deframe)
168//!   and
169//!   [`mem`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.mem),
170//!   needed by `AsyncFixedBuf::read_frame`.
171//! - v0.2.0
172//!   - Move tokio support to [`fixed_buffer_tokio`](https://crates.io/crates/fixed-buffer-tokio).
173//!   - Add
174//!     [`copy_once_from`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.copy_once_from),
175//!     [`read_block`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.read_block),
176//!     [`ReadWriteChain`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.ReadWriteChain.html),
177//!     and
178//!     [`ReadWriteTake`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.ReadWriteTake.html).
179//! - v0.1.7 - Add [`FixedBuf::escape_ascii`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.escape_ascii).
180//! - v0.1.6 - Add [`filled`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.filled)
181//!   constructor.
182//! - v0.1.5 - Change [`read_delimited`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.read_delimited)
183//!   to return `Option<&[u8]>`, for clean EOF handling.
184//! - v0.1.4 - Add [`clear()`](https://docs.rs/fixed-buffer/latest/fixed_buffer/struct.FixedBuf.html#method.clear).
185//! - v0.1.3
186//!   - Thanks to [freax13](https://gitlab.com/Freax13) for these changes:
187//!     - Support any buffer size.  Now you can make `FixedBuf<[u8; 42]>`.
188//!     - Support any `AsRef<[u8]> + AsMut<[u8]>` value for internal memory:
189//!       - `[u8; N]`
190//!       - `Box<[u8; N]>`
191//!       - `&mut [u8]`
192//!       - `Vec<u8>`
193//!   - Renamed `new_with_mem` to `new`.
194//!     Use `FixedBuf::default()` to construct any `FixedBuf<T: Default>`, which includes
195//!     [arrays of sizes up to 32](https://doc.rust-lang.org/std/primitive.array.html).
196//! - v0.1.2 - Updated documentation.
197//! - v0.1.1 - First published version
198//!
199//! </details>
200//!
201//! # TO DO
202//! - Change links in docs to standard style.  Don't link to `docs.rs`.
203//! - Idea: `buf.slice(buf.read_frame(&mut reader, deframe_crlf))`
204//! - Add an `frame_copy_iter` function.
205//!   Because of borrowing rules, this function must return non-borrowed (allocated and copied) data.
206//! - Set up CI on:
207//!   - DONE - Linux x86 64-bit
208//!   - [macOS](https://gitlab.com/gitlab-org/gitlab/-/issues/269756)
209//!   - [Windows](https://about.gitlab.com/blog/2020/01/21/windows-shared-runner-beta/)
210//!   - <https://crate-ci.github.io/pr/testing.html#travisci>
211//!   - Linux ARM 64-bit (Raspberry Pi 3 and newer)
212//!   - Linux ARM 32-bit (Raspberry Pi 2)
213//!   - RISCV & ESP32 firmware?
214//! - DONE - Try to make this crate comply with the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/).
215//! - DONE - Find out how to include Readme.md info in the crate's docs.
216//! - DONE - Make the repo public
217//! - DONE - Set up continuous integration tests and banner.
218//!   - <https://github.com/actions-rs/example>
219//!   - <https://alican.codes/rust-github-actions/>
220//! - DONE - Add some documentation tests
221//!   - <https://doc.rust-lang.org/rustdoc/documentation-tests.html>
222//!   - <https://doc.rust-lang.org/stable/rust-by-example/testing/doc_testing.html>
223//! - DONE - Set up public repository on Gitlab.com
224//!   - <https://gitlab.com/mattdark/firebase-example/blob/master/.gitlab-ci.yml>
225//!   - <https://medium.com/astraol/optimizing-ci-cd-pipeline-for-rust-projects-gitlab-docker-98df64ae3bc4>
226//!   - <https://hub.docker.com/_/rust>
227//! - DONE - Publish to crates.io
228//! - DONE - Read through <https://crate-ci.github.io/index.html>
229//! - DONE - Get a code review from an experienced rustacean
230//! - DONE - Add and update a changelog
231//!   - Update it manually
232//!   - <https://crate-ci.github.io/release/changelog.html>
233//!
234//! # Release Process
235//! 1. Edit `Cargo.toml` and bump version number.
236//! 2. Run `../release.sh`
237#![forbid(unsafe_code)]
238
239mod escape_ascii;
240pub use escape_ascii::escape_ascii;
241
242mod deframe_crlf;
243pub use deframe_crlf::deframe_crlf;
244
245mod deframe_line;
246pub use deframe_line::deframe_line;
247
248#[cfg(feature = "futures-io")]
249mod impl_futures_io;
250#[cfg(feature = "futures-io")]
251#[allow(unused_imports)]
252pub use impl_futures_io::*;
253
254#[cfg(feature = "tokio")]
255mod impl_tokio;
256#[cfg(feature = "tokio")]
257#[allow(unused_imports)]
258pub use impl_tokio::*;
259
260#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
261pub struct NoWritableSpace {}
262impl From<NoWritableSpace> for std::io::Error {
263    fn from(_: NoWritableSpace) -> Self {
264        std::io::Error::new(
265            std::io::ErrorKind::InvalidData,
266            "no writable space in buffer",
267        )
268    }
269}
270impl From<NoWritableSpace> for String {
271    fn from(_: NoWritableSpace) -> Self {
272        "no writable space in buffer".to_string()
273    }
274}
275
276#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
277pub struct MalformedInputError(pub String);
278impl MalformedInputError {
279    #[must_use]
280    pub fn new(msg: String) -> Self {
281        Self(msg)
282    }
283}
284impl From<MalformedInputError> for std::io::Error {
285    fn from(e: MalformedInputError) -> Self {
286        std::io::Error::new(
287            std::io::ErrorKind::InvalidData,
288            format!("malformed input: {}", e.0),
289        )
290    }
291}
292impl From<MalformedInputError> for String {
293    fn from(e: MalformedInputError) -> Self {
294        format!("malformed input: {}", e.0)
295    }
296}
297
298/// A fixed-length byte buffer.
299/// You can write bytes to it and then read them back.
300///
301/// It is not a circular buffer.  Call [`shift`](#method.shift) periodically to
302/// move unread bytes to the front of the buffer.
303#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
304pub struct FixedBuf<const SIZE: usize> {
305    mem: [u8; SIZE],
306    read_index: usize,
307    write_index: usize,
308}
309
310impl<const SIZE: usize> std::panic::UnwindSafe for FixedBuf<SIZE> {}
311
312impl<const SIZE: usize> FixedBuf<SIZE> {
313    /// Makes a new empty buffer with space for `SIZE` bytes.
314    ///
315    /// Be careful of stack overflows!
316    #[must_use]
317    pub const fn new() -> Self {
318        Self {
319            mem: [0_u8; SIZE],
320            write_index: 0,
321            read_index: 0,
322        }
323    }
324
325    /// Drops the struct and returns its internal array.
326    #[must_use]
327    pub fn into_inner(self) -> [u8; SIZE] {
328        self.mem
329    }
330
331    /// Returns the number of unread bytes in the buffer.
332    ///
333    /// # Example
334    /// ```
335    /// # use fixed_buffer::FixedBuf;
336    /// let mut buf: FixedBuf<16> = FixedBuf::new();
337    /// assert_eq!(0, buf.len());
338    /// buf.write_bytes("abc").unwrap();
339    /// assert_eq!(3, buf.len());
340    /// buf.try_read_exact(2).unwrap();
341    /// assert_eq!(1, buf.len());
342    /// buf.shift();
343    /// assert_eq!(1, buf.len());
344    /// buf.read_all();
345    /// assert_eq!(0, buf.len());
346    /// ```
347    #[must_use]
348    pub fn len(&self) -> usize {
349        self.write_index - self.read_index
350    }
351
352    /// Returns true if there are no unread bytes in the buffer.
353    ///
354    /// # Example
355    /// ```
356    /// # use fixed_buffer::FixedBuf;
357    /// let mut buf: FixedBuf<16> = FixedBuf::new();
358    /// assert!(buf.is_empty());
359    /// buf.write_bytes("abc").unwrap();
360    /// assert!(!buf.is_empty());
361    /// buf.read_all();
362    /// assert!(buf.is_empty());
363    /// ```
364    #[must_use]
365    pub fn is_empty(&self) -> bool {
366        self.write_index == self.read_index
367    }
368
369    /// Discards all data in the buffer.
370    pub fn clear(&mut self) {
371        self.read_index = 0;
372        self.write_index = 0;
373    }
374
375    /// Copies all readable bytes to a string.
376    /// Includes printable ASCII characters as-is.
377    /// Converts non-printable characters to strings like "\n" and "\x19".
378    ///
379    /// Uses
380    /// [`core::ascii::escape_default`](https://doc.rust-lang.org/core/ascii/fn.escape_default.html)
381    /// internally to escape each byte.
382    ///
383    /// This function is useful for printing byte slices to logs and comparing byte slices in tests.
384    ///
385    /// Example test:
386    /// ```
387    /// use fixed_buffer::FixedBuf;
388    /// let mut buf: FixedBuf<8> = FixedBuf::new();
389    /// buf.write_bytes("abc").unwrap();
390    /// buf.write_bytes("€").unwrap();
391    /// assert_eq!("abc\\xe2\\x82\\xac", buf.escape_ascii());
392    /// ```
393    #[must_use]
394    pub fn escape_ascii(&self) -> String {
395        escape_ascii(self.readable())
396    }
397
398    /// Borrows the entire internal memory buffer.
399    /// This is a low-level function.
400    #[must_use]
401    pub fn mem(&self) -> &[u8] {
402        self.mem.as_ref()
403    }
404
405    /// Returns the slice of readable bytes in the buffer.
406    /// After processing some bytes from the front of the slice,
407    /// call [`read`](#method.read) to consume the bytes.
408    ///
409    /// This is a low-level method.
410    /// You probably want to use
411    /// [`std::io::Read::read`](https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read)
412    /// or
413    /// [`tokio::io::AsyncReadExt::read`](https://docs.rs/tokio/0.3.0/tokio/io/trait.AsyncReadExt.html#method.reade).
414    #[must_use]
415    pub fn readable(&self) -> &[u8] {
416        &self.mem.as_ref()[self.read_index..self.write_index]
417    }
418
419    /// Reads a single byte from the buffer.
420    ///
421    /// Returns `None` if the buffer is empty.
422    pub fn try_read_byte(&mut self) -> Option<u8> {
423        self.try_read_exact(1).map(|bytes| bytes[0])
424    }
425
426    /// Reads bytes from the buffer.
427    ///
428    /// Returns `None` if the buffer does not contain `num_bytes` bytes.
429    pub fn try_read_exact(&mut self, num_bytes: usize) -> Option<&[u8]> {
430        let new_read_index = self.read_index + num_bytes;
431        if self.write_index < new_read_index {
432            None
433        } else {
434            let old_read_index = self.read_index;
435            self.read_index = new_read_index;
436            if self.read_index == self.write_index {
437                // All data has been read.  Reset the buffer.
438                self.write_index = 0;
439                self.read_index = 0;
440            }
441            Some(&self.mem.as_ref()[old_read_index..new_read_index])
442        }
443    }
444
445    /// Reads all the bytes from the buffer.
446    ///
447    /// The buffer becomes empty and subsequent writes can fill the whole buffer.
448    #[allow(clippy::missing_panics_doc)]
449    pub fn read_all(&mut self) -> &[u8] {
450        self.try_read_exact(self.len()).unwrap()
451    }
452
453    /// Reads bytes from the buffer and copies them into `dest`.
454    ///
455    /// Returns the number of bytes copied.
456    ///
457    /// Returns `0` when the buffer is empty or `dest` is zero-length.
458    #[allow(clippy::missing_panics_doc)]
459    pub fn read_and_copy_bytes(&mut self, dest: &mut [u8]) -> usize {
460        let readable = self.readable();
461        let len = core::cmp::min(dest.len(), readable.len());
462        if len == 0 {
463            return 0;
464        }
465        let src = &readable[..len];
466        let copy_dest = &mut dest[..len];
467        copy_dest.copy_from_slice(src);
468        self.try_read_exact(len).unwrap();
469        len
470    }
471
472    /// Reads bytes from the buffer and copies them into `dest`, filling it,
473    /// and returns `Some(())`.
474    ///
475    /// Returns `None` if the buffer does not contain enough bytes to fill `dest`.
476    ///
477    /// Returns `Some(())` if `dest` is zero-length.
478    #[allow(clippy::missing_panics_doc)]
479    pub fn read_and_copy_exact(&mut self, dest: &mut [u8]) -> Option<()> {
480        if self.len() < dest.len() {
481            return None;
482        }
483        assert_eq!(dest.len(), self.read_and_copy_bytes(dest));
484        Some(())
485    }
486
487    /// Reads from `reader` once and writes the data into the buffer.
488    ///
489    /// Use [`shift`](#method.shift) to make empty space usable for writing.
490    ///
491    /// # Errors
492    /// Returns [`InvalidData`](std::io::ErrorKind::InvalidData)
493    /// when there is no empty space in the buffer.
494    pub fn copy_once_from(
495        &mut self,
496        reader: &mut impl std::io::Read,
497    ) -> Result<usize, std::io::Error> {
498        let writable = self.writable();
499        if writable.is_empty() {
500            return Err(std::io::Error::new(
501                std::io::ErrorKind::InvalidData,
502                "no empty space in buffer",
503            ));
504        }
505        let num_read = reader.read(writable)?;
506        self.wrote(num_read);
507        Ok(num_read)
508    }
509
510    /// Tries to copy `data` into the buffer, after any unread bytes.
511    ///
512    /// Returns `Ok(num_written)`, which may be less than the length of `data`.
513    ///
514    /// Returns `Ok(0)` only when `data` is zero-length.
515    ///
516    /// Use [`shift`](#method.shift) to make empty space usable for writing.
517    ///
518    /// # Errors
519    /// Returns `NoWritableSpace` when the buffer has no free space at the end.
520    ///
521    /// # Example
522    /// ```
523    /// # use fixed_buffer::FixedBuf;
524    /// let mut buf: FixedBuf<3> = FixedBuf::new();
525    /// assert_eq!(2, buf.write_bytes("ab").unwrap());
526    /// assert_eq!(1, buf.write_bytes("cd").unwrap()); // Fills buffer, "d" not written.
527    /// assert_eq!("abc", buf.escape_ascii());
528    /// buf.write_bytes("d").unwrap_err();  // Error, buffer is full.
529    /// ```
530    pub fn write_bytes(&mut self, data: impl AsRef<[u8]>) -> Result<usize, NoWritableSpace> {
531        let data = data.as_ref();
532        if data.is_empty() {
533            return Ok(0);
534        }
535        let writable = self.writable();
536        if writable.is_empty() {
537            return Err(NoWritableSpace {});
538        }
539        let len = writable.len().min(data.len());
540        let dest = &mut writable[..len];
541        let src = &data[..len];
542        dest.copy_from_slice(src);
543        self.wrote(len);
544        Ok(len)
545    }
546
547    /// Returns the writable part of the buffer.
548    ///
549    /// To use this, first modify bytes at the beginning of the slice.
550    /// Then call [`wrote(usize)`](#method.wrote)
551    /// to commit those bytes into the buffer and make them available for reading.
552    ///
553    /// Returns an empty slice when the end of the buffer is full.
554    ///
555    /// Use [`shift`](#method.shift) to make empty space usable for writing.
556    ///
557    /// This is a low-level method.
558    /// You probably want to use
559    /// [`std::io::Write::write`](https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.write)
560    /// or
561    /// [`tokio::io::AsyncWriteExt::write`](https://docs.rs/tokio/0.3.0/tokio/io/trait.AsyncWriteExt.html#method.write).
562    ///
563    /// # Example
564    /// ```
565    /// # use fixed_buffer::{escape_ascii, FixedBuf};
566    /// let mut buf: FixedBuf<8> = FixedBuf::new();
567    /// buf.writable()[0] = 'a' as u8;
568    /// buf.writable()[1] = 'b' as u8;
569    /// buf.writable()[2] = 'c' as u8;
570    /// buf.wrote(3);
571    /// assert_eq!("abc", buf.escape_ascii());
572    /// ```
573    pub fn writable(&mut self) -> &mut [u8] {
574        &mut self.mem.as_mut()[self.write_index..]
575    }
576
577    /// Commits bytes into the buffer.
578    /// Call this after writing to the front of the
579    /// [`writable`](#method.writable) slice.
580    ///
581    /// This is a low-level method.
582    ///
583    /// See [`writable()`](#method.writable).
584    ///
585    /// # Panics
586    /// Panics when there is not `num_bytes` free at the end of the buffer.
587    pub fn wrote(&mut self, num_bytes: usize) {
588        if num_bytes == 0 {
589            return;
590        }
591        let new_write_index = self.write_index + num_bytes;
592        assert!(
593            new_write_index <= self.mem.as_mut().len(),
594            "write would overflow"
595        );
596        self.write_index = new_write_index;
597    }
598
599    /// Recovers buffer space.
600    ///
601    /// The buffer is not circular.
602    /// After you read bytes, the space at the beginning of the buffer is unused.
603    /// Call this method to move unread data to the beginning of the buffer and recover the space.
604    /// This makes the free space available for writes, which go at the end of the buffer.
605    pub fn shift(&mut self) {
606        if self.read_index == 0 {
607            return;
608        }
609        // As long as try_read_exact performs this check and is the only way to
610        // advance read_index, this block can never execute.
611        // if self.read_index == self.write_index {
612        //     self.write_index = 0;
613        //     self.read_index = 0;
614        //     return;
615        // }
616        self.mem
617            .as_mut()
618            .copy_within(self.read_index..self.write_index, 0);
619        self.write_index -= self.read_index;
620        self.read_index = 0;
621    }
622
623    /// This is a low-level function.
624    /// Use [`read_frame`](#method.read_frame) instead.
625    ///
626    /// Calls `deframer_fn` to check if the buffer contains a complete frame.
627    /// Consumes the frame bytes from the buffer
628    /// and returns the range of the frame's contents in the internal memory.
629    ///
630    /// Use [`mem`](#method.mem) to immutably borrow the internal memory and
631    /// construct the slice with `&buf.mem()[range]`.
632    /// This is necessary because `deframe` borrows `self` mutably but
633    /// `read_frame` needs to borrow it immutably and return a slice.
634    ///
635    /// Returns `None` if the buffer is empty or contains an incomplete frame.
636    ///
637    /// # Errors
638    /// Returns [`InvalidData`](std::io::ErrorKind::InvalidData)
639    /// when `deframer_fn` returns an error.
640    #[allow(clippy::missing_panics_doc)]
641    pub fn deframe<F>(
642        &mut self,
643        deframer_fn: F,
644    ) -> Result<Option<core::ops::Range<usize>>, std::io::Error>
645    where
646        F: Fn(&[u8]) -> Result<(usize, Option<core::ops::Range<usize>>), MalformedInputError>,
647    {
648        if self.is_empty() {
649            return Ok(None);
650        }
651        let (num_to_consume, opt_data_range) = deframer_fn(self.readable())?;
652        let opt_mem_range = opt_data_range.map(|data_range| {
653            let mem_start = self.read_index + data_range.start;
654            let mem_end = self.read_index + data_range.end;
655            mem_start..mem_end
656        });
657        self.try_read_exact(num_to_consume).unwrap();
658        Ok(opt_mem_range)
659    }
660
661    /// Reads from `reader` into the buffer.
662    ///
663    /// After each read, calls `deframer_fn`
664    /// to check if the buffer now contains a complete frame.
665    /// Consumes the frame bytes from the buffer
666    /// and returns a slice with the frame contents.
667    ///
668    /// Returns `None` when `reader` reaches EOF and the buffer is empty.
669    ///
670    /// Calls [`shift`](#method.shift) before reading.
671    ///
672    /// Provided deframer functions:
673    /// - [`deframe_line`](https://docs.rs/fixed-buffer/latest/fixed_buffer/fn.deframe_line.html)
674    /// - [`deframe_crlf`](https://docs.rs/fixed-buffer/latest/fixed_buffer/fn.deframe_crlf.html)
675    ///
676    /// # Errors
677    /// Returns [`UnexpectedEof`](std::io::ErrorKind::UnexpectedEof)
678    /// when `reader` reaches EOF and the buffer contains an incomplete frame.
679    ///
680    /// Returns [`InvalidData`](std::io::ErrorKind::InvalidData)
681    /// when `deframer_fn` returns an error or the buffer fills up.
682    ///
683    /// # Example
684    /// ```
685    /// # use fixed_buffer::{escape_ascii, FixedBuf, deframe_line};
686    /// let mut buf: FixedBuf<32> = FixedBuf::new();
687    /// let mut input = std::io::Cursor::new(b"aaa\r\nbbb\n\nccc\n");
688    /// # let mut output: Vec<String> = Vec::new();
689    /// loop {
690    ///   if let Some(line) = buf.read_frame(&mut input, deframe_line).unwrap() {
691    ///     println!("{}", escape_ascii(line));
692    /// #   output.push(escape_ascii(line));
693    ///   } else {
694    ///     // EOF.
695    ///     break;
696    ///   }
697    /// }
698    /// // Prints:
699    /// // aaa
700    /// // bbb
701    /// //
702    /// // ccc
703    /// # assert_eq!(
704    /// #     vec!["aaa".to_string(), "bbb".to_string(),"".to_string(), "ccc".to_string()],
705    /// #     output
706    /// # );
707    /// ```
708    ///
709    /// # Deframer Function `deframe_fn`
710    /// Checks if `data` contains an entire frame.
711    ///
712    /// Never panics.
713    ///
714    /// Returns `Ok((frame_len, Some(payload_range))`
715    /// when `data` contains a complete frame at `&data[payload_range]`.
716    /// The caller should consume `frame_len` from the beginning of the buffer
717    /// before calling `deframe` again.
718    ///
719    /// Returns `Ok((frame_len, None))` if `data` contains an incomplete frame.
720    /// The caller should consume `frame_len` from the beginning of the buffer.
721    /// The caller can read more bytes and call `deframe` again.
722    ///
723    /// Returns `Err(MalformedInputError)` if `data` contains a malformed frame.
724    ///
725    /// Popular frame formats:
726    /// - Newline-delimited: CSV, JSONL, HTTP, Server-Sent Events `text/event-stream`, and SMTP
727    /// - Hexadecimal length prefix: [HTTP chunked transfer encoding](https://tools.ietf.org/html/rfc7230#section-4.1)
728    /// - Binary length prefix: [TLS](https://tools.ietf.org/html/rfc5246#section-6.2.1)
729    ///
730    /// # Example
731    /// ```
732    /// use fixed_buffer::deframe_crlf;
733    /// assert_eq!(Ok((0, None)), deframe_crlf(b""));
734    /// assert_eq!(Ok((0, None)), deframe_crlf(b"abc"));
735    /// assert_eq!(Ok((0, None)), deframe_crlf(b"abc\r"));
736    /// assert_eq!(Ok((0, None)), deframe_crlf(b"abc\n"));
737    /// assert_eq!(Ok((5, Some(0..3))), deframe_crlf(b"abc\r\n"));
738    /// assert_eq!(Ok((5, Some(0..3))), deframe_crlf(b"abc\r\nX"));
739    /// ```
740    pub fn read_frame<R, F>(
741        &mut self,
742        reader: &mut R,
743        deframer_fn: F,
744    ) -> Result<Option<&[u8]>, std::io::Error>
745    where
746        R: std::io::Read,
747        F: Fn(&[u8]) -> Result<(usize, Option<core::ops::Range<usize>>), MalformedInputError>,
748    {
749        loop {
750            if !self.is_empty() {
751                if let Some(frame_range) = self.deframe(&deframer_fn)? {
752                    return Ok(Some(&self.mem()[frame_range]));
753                }
754                // None case falls through.
755            }
756            self.shift();
757            let writable = self.writable();
758            if writable.is_empty() {
759                return Err(std::io::Error::new(
760                    std::io::ErrorKind::InvalidData,
761                    "end of buffer full",
762                ));
763            }
764            let num_read = reader.read(writable)?;
765            if num_read == 0 {
766                if self.is_empty() {
767                    return Ok(None);
768                }
769                return Err(std::io::Error::new(
770                    std::io::ErrorKind::UnexpectedEof,
771                    "eof after reading part of a frame",
772                ));
773            }
774            self.wrote(num_read);
775        }
776    }
777
778    /// Reads from `reader` into the buffer.
779    ///
780    /// After each read, calls `deframer_fn`
781    /// to check if the buffer now contains a complete frame.
782    /// Consumes the frame bytes from the buffer
783    /// and returns a slice with the frame contents.
784    ///
785    /// Returns `None` when `reader` reaches EOF and the buffer is empty.
786    ///
787    /// Calls [`shift`](#method.shift) before reading.
788    ///
789    /// Provided deframer functions:
790    /// - [`deframe_line`](https://docs.rs/fixed-buffer/latest/fixed_buffer/fn.deframe_line.html)
791    /// - [`deframe_crlf`](https://docs.rs/fixed-buffer/latest/fixed_buffer/fn.deframe_crlf.html)
792    ///
793    /// # Errors
794    /// Returns [`UnexpectedEof`](std::io::ErrorKind::UnexpectedEof)
795    /// when `reader` reaches EOF and the buffer contains an incomplete frame.
796    ///
797    /// Returns [`InvalidData`](std::io::ErrorKind::InvalidData)
798    /// when `deframer_fn` returns an error or the buffer fills up.
799    ///
800    /// # Example
801    /// ```
802    /// # async fn f() {
803    /// # use fixed_buffer::{escape_ascii, FixedBuf, deframe_line};
804    /// let mut buf: FixedBuf<32> = FixedBuf::new();
805    /// let mut input = std::io::Cursor::new(b"aaa\r\nbbb\n\nccc\n");
806    /// # let mut output: Vec<String> = Vec::new();
807    /// while let Some(line_bytes) = buf.read_frame_tokio(&mut input, deframe_line).await.unwrap() {
808    ///   println!("{}", escape_ascii(line_bytes));
809    /// # output.push(escape_ascii(line_bytes));
810    /// }
811    /// // Prints:
812    /// // aaa
813    /// // bbb
814    /// //
815    /// // ccc
816    /// # assert_eq!(
817    /// #     vec!["aaa".to_string(), "bbb".to_string(),"".to_string(), "ccc".to_string()],
818    /// #     output
819    /// # );
820    /// # }
821    /// # tokio::runtime::Builder::new_current_thread().build().unwrap().block_on(f());
822    /// ```
823    ///
824    /// # Deframer Function `deframe_fn`
825    /// Checks if `data` contains an entire frame.
826    ///
827    /// Never panics.
828    ///
829    /// Returns `Ok((frame_len, Some(payload_range))`
830    /// when `data` contains a complete frame at `&data[payload_range]`.
831    /// The caller should consume `frame_len` from the beginning of the buffer
832    /// before calling `deframe` again.
833    ///
834    /// Returns `Ok((frame_len, None))` if `data` contains an incomplete frame.
835    /// The caller should consume `frame_len` from the beginning of the buffer.
836    /// The caller can read more bytes and call `deframe` again.
837    ///
838    /// Returns `Err(MalformedInputError)` if `data` contains a malformed frame.
839    ///
840    /// Popular frame formats:
841    /// - Newline-delimited: CSV, JSONL, HTTP, Server-Sent Events `text/event-stream`, and SMTP
842    /// - Hexadecimal length prefix: [HTTP chunked transfer encoding](https://tools.ietf.org/html/rfc7230#section-4.1)
843    /// - Binary length prefix: [TLS](https://tools.ietf.org/html/rfc5246#section-6.2.1)
844    ///
845    /// # Example
846    /// ```
847    /// use fixed_buffer::deframe_crlf;
848    /// assert_eq!(Ok((0, None)), deframe_crlf(b""));
849    /// assert_eq!(Ok((0, None)), deframe_crlf(b"abc"));
850    /// assert_eq!(Ok((0, None)), deframe_crlf(b"abc\r"));
851    /// assert_eq!(Ok((0, None)), deframe_crlf(b"abc\n"));
852    /// assert_eq!(Ok((5, Some(0..3))), deframe_crlf(b"abc\r\n"));
853    /// assert_eq!(Ok((5, Some(0..3))), deframe_crlf(b"abc\r\nX"));
854    /// ```
855    #[cfg(feature = "tokio")]
856    pub async fn read_frame_tokio<R, F>(
857        &mut self,
858        reader: &mut R,
859        deframer_fn: F,
860    ) -> Result<Option<&[u8]>, std::io::Error>
861    where
862        R: tokio::io::AsyncRead + Unpin,
863        F: Fn(&[u8]) -> Result<(usize, Option<core::ops::Range<usize>>), MalformedInputError>,
864    {
865        use tokio::io::AsyncReadExt;
866        loop {
867            if !self.is_empty() {
868                if let Some(frame_range) = self.deframe(&deframer_fn)? {
869                    return Ok(Some(&self.mem()[frame_range]));
870                }
871                // None case falls through.
872            }
873            self.shift();
874            let writable = self.writable();
875            if writable.is_empty() {
876                return Err(std::io::Error::new(
877                    std::io::ErrorKind::InvalidData,
878                    "end of buffer full",
879                ));
880            }
881            let num_read = reader.read(writable).await?;
882            if num_read == 0 {
883                if self.is_empty() {
884                    return Ok(None);
885                }
886                return Err(std::io::Error::new(
887                    std::io::ErrorKind::UnexpectedEof,
888                    "eof after reading part of a frame",
889                ));
890            }
891            self.wrote(num_read);
892        }
893    }
894}
895
896impl<const SIZE: usize> From<[u8; SIZE]> for FixedBuf<SIZE> {
897    fn from(mem: [u8; SIZE]) -> Self {
898        Self {
899            mem,
900            read_index: 0,
901            write_index: SIZE,
902        }
903    }
904}
905
906impl<const SIZE: usize> From<&[u8; SIZE]> for FixedBuf<SIZE> {
907    fn from(mem: &[u8; SIZE]) -> Self {
908        Self {
909            mem: *mem,
910            read_index: 0,
911            write_index: SIZE,
912        }
913    }
914}
915
916impl<const SIZE: usize> std::io::Write for FixedBuf<SIZE> {
917    fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
918        Ok(self.write_bytes(data)?)
919    }
920
921    fn flush(&mut self) -> std::io::Result<()> {
922        Ok(())
923    }
924}
925
926impl<const SIZE: usize> std::io::Read for FixedBuf<SIZE> {
927    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
928        Ok(self.read_and_copy_bytes(buf))
929    }
930}
931
932impl<const SIZE: usize> core::fmt::Debug for FixedBuf<SIZE> {
933    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
934        write!(
935            f,
936            "FixedBuf<{}>{{{} writable, {} readable: \"{}\"}}",
937            SIZE,
938            SIZE - self.write_index,
939            self.len(),
940            self.escape_ascii()
941        )
942    }
943}
944
945impl<const SIZE: usize> Default for FixedBuf<SIZE> {
946    fn default() -> Self {
947        Self::new()
948    }
949}