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