├── .gitignore ├── Cargo.toml ├── CHANGELOG.md ├── LICENSE ├── benches └── read.rs ├── README.md ├── src ├── bytes.rs └── lib.rs └── .github └── workflows └── rust.yml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | release.txt 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "circbuf" 3 | version = "0.2.1" 4 | authors = ["Jerome Froelich "] 5 | description = "A growable circular buffer for working with bytes" 6 | homepage = "https://github.com/jeromefroe/circbuf-rs" 7 | repository = "https://github.com/jeromefroe/circbuf-rs.git" 8 | documentation = "https://docs.rs/circbuf/" 9 | readme = "README.md" 10 | license = "MIT" 11 | keywords = ["buffer", "bytes", "circular", "ring"] 12 | edition = "2021" 13 | rust-version = "1.56" 14 | 15 | [features] 16 | bytes = ["dep:bytes"] 17 | 18 | 19 | [dependencies] 20 | bytes = { version = "1.4.0", optional = true } 21 | 22 | [dev-dependencies] 23 | vecio = "0.1.0" 24 | tempfile = "3.6.0" 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.2.1](https://github.com/jeromefroe/circbuf-rs/tree/0.2.1) - 2023-07-20 4 | 5 | - Correct unused `Result` in test. 6 | - Update dependencies, including configuration of `bytes` feature on the `bytes` crate. 7 | 8 | ## [v0.2.0](https://github.com/jeromefroe/circbuf-rs/tree/0.2.0) - 2020-06-29 9 | 10 | - Implement `bytes::Buf` and `bytes::BufMut` for `CircBuf`. 11 | 12 | ## [v0.1.4](https://github.com/jeromefroe/circbuf-rs/tree/0.1.4) - 2018-03-26 13 | 14 | - Check for empty writes to fix panic. 15 | 16 | ## [v0.1.3](https://github.com/jeromefroe/circbuf-rs/tree/0.1.3) - 2016-11-13 17 | 18 | - Make vecio a dev-dependency and add cfg attributes to only run benchmarks on nightly. 19 | 20 | ## [v0.1.2](https://github.com/jeromefroe/circbuf-rs/tree/0.1.2) - 2016-11-12 21 | 22 | - Commented out benchmarks so we can build on stable. 23 | 24 | ## [v0.1.1](https://github.com/jeromefroe/circbuf-rs/tree/0.1.1) - 2016-11-12 25 | 26 | - Added documentation metadata to Cargo.toml. 27 | 28 | ## [v0.1.0](https://github.com/jeromefroe/circbuf-rs/tree/0.1.0) - 2016-11-12 29 | 30 | - Initial release. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jerome Froelich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /benches/read.rs: -------------------------------------------------------------------------------- 1 | #![feature(can_vector)] 2 | #![feature(test)] 3 | extern crate test; 4 | 5 | use circbuf::CircBuf; 6 | use test::Bencher; 7 | 8 | use std::{ 9 | fs::File, 10 | io::{copy, IoSliceMut, Read, Seek, Write}, 11 | }; 12 | 13 | const EXPECTED_N: usize = 12; 14 | 15 | fn prepare_file() -> File { 16 | let mut file = tempfile::tempfile().unwrap(); 17 | assert_eq!(file.write(b"foo\nbar\nbaz\n").unwrap(), EXPECTED_N); 18 | file 19 | } 20 | 21 | fn prepare_circbuf() -> CircBuf { 22 | let mut c = CircBuf::with_capacity(16).unwrap(); 23 | c.advance_write_raw(8); 24 | c.advance_read_raw(8); 25 | c 26 | } 27 | 28 | #[bench] 29 | fn normal_read(b: &mut Bencher) { 30 | let mut file = prepare_file(); 31 | let mut c = prepare_circbuf(); 32 | 33 | b.iter(|| { 34 | file.rewind().unwrap(); 35 | let [mut a, mut b] = c.get_avail(); 36 | let n_a = file.read(&mut a).unwrap(); 37 | let n_b = file.read(&mut b).unwrap(); 38 | let n = n_a + n_b; 39 | assert_eq!(n, EXPECTED_N); 40 | 41 | c.advance_write_raw(n); 42 | c.advance_read_raw(n); 43 | }) 44 | } 45 | 46 | #[bench] 47 | fn writer_read(b: &mut Bencher) { 48 | let mut file = prepare_file(); 49 | let mut c = prepare_circbuf(); 50 | 51 | b.iter(|| { 52 | file.rewind().unwrap(); 53 | let n = copy(&mut file, &mut c).unwrap() as usize; 54 | assert_eq!(n, EXPECTED_N); 55 | c.advance_read_raw(n); 56 | }) 57 | } 58 | 59 | #[bench] 60 | #[ignore = "Test need OS with vectored read feature"] 61 | fn vector_read(b: &mut Bencher) { 62 | let mut file = prepare_file(); 63 | assert!(file.is_read_vectored()); 64 | let mut c = prepare_circbuf(); 65 | 66 | b.iter(|| { 67 | file.rewind().unwrap(); 68 | let mut bufs = { 69 | let [a, b] = c.get_avail(); 70 | [IoSliceMut::new(a), IoSliceMut::new(b)] 71 | }; 72 | let n = file.read_vectored(bufs.as_mut_slice()).unwrap(); 73 | assert_eq!(n, EXPECTED_N); 74 | c.advance_write_raw(n); 75 | c.advance_read_raw(n); 76 | }) 77 | } 78 | 79 | #[bench] 80 | // we call seek in iter in both benches above so lets get a base time for it 81 | fn seek_base(b: &mut Bencher) { 82 | let mut file = prepare_file(); 83 | 84 | b.iter(|| { 85 | file.rewind().unwrap(); 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CircBuf 2 | [![build status](https://github.com/jeromefroe/circbuf-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/jeromefroe/circbuf-rs/actions/workflows/rust.yml) 3 | [![codecov](https://codecov.io/gh/jeromefroe/circbuf-rs/branch/master/graph/badge.svg?token=BVS0QOKZ7W)](https://codecov.io/gh/jeromefroe/circbuf-rs) 4 | [![crates.io](https://img.shields.io/crates/v/circbuf.svg)](https://crates.io/crates/circbuf/) 5 | [![docs.rs](https://docs.rs/circbuf/badge.svg)](https://docs.rs/circbuf/) 6 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/jeromefroe/circbuf-rs/master/LICENSE) 7 | 8 | [Documentation](https://docs.rs/circbuf/) 9 | 10 | An implementation of a growable circular buffer of bytes. The `CircBuf` struct 11 | manages a buffer of bytes allocated on the heap. The buffer can be grown when needed 12 | and can return slices into its internal buffer that can be used for both normal IO 13 | (`read` and `write`) as well as vector IO (`readv` and `writev`). 14 | 15 | ## `bytes` support 16 | 17 | If the `bytes` feature flag is enabled, then the [bytes](https://github.com/tokio-rs/bytes) 18 | crate will be added as a dependency and the `Buf` and `BufMut` traits implemented for 19 | `CircBuf`. The optional vectored read/write functions are implemented, allowing you to use 20 | the `CircBuf` for efficient vectored IO operations with libraries such as `tokio`. See 21 | for example the [read_buf](https://docs.rs/tokio/0.2.21/tokio/io/trait.AsyncReadExt.html#method.read_buf) 22 | and [write_buf](https://docs.rs/tokio/0.2.21/tokio/io/trait.AsyncWriteExt.html#method.write_buf) 23 | methods, which can accept a `CircBuf` when the `bytes` feature flag is enabled. 24 | 25 | ## Example 26 | 27 | Below is a simple example of a server which makes use of a `CircBuf` to read messages 28 | from a client. It uses the `vecio` crate to call `readv` on the socket. Messages are seperated by a 29 | vertical bar `|` and the server returns to the client the number of bytes in each message it receives. 30 | 31 | ```rust 32 | extern crate vecio; 33 | extern crate circbuf; 34 | 35 | use std::thread; 36 | use std::net::{TcpListener, TcpStream}; 37 | use std::io::Write; 38 | use vecio::Rawv; 39 | use circbuf::CircBuf; 40 | 41 | fn handle_client(mut stream: TcpStream) { 42 | let mut buf = CircBuf::new(); 43 | let mut num_messages = 0; // number of messages from the client 44 | let mut num_bytes = 0; // number of bytes read since last '|' 45 | 46 | loop { 47 | // grow the buffer if it is less than half full 48 | if buf.len() > buf.avail() { 49 | buf.grow().unwrap(); 50 | } 51 | 52 | let n; 53 | { 54 | n = match stream.readv(&buf.get_avail()) { 55 | Ok(n) => { 56 | if n == 0 { 57 | // EOF 58 | println!("client closed connection"); 59 | break; 60 | } 61 | n 62 | } 63 | Err(e) => panic!("got an error reading from a connection: {}", e), 64 | }; 65 | } 66 | 67 | println!("read {} bytes from the client", n); 68 | 69 | // update write cursor 70 | buf.advance_write(n); 71 | 72 | // parse request from client for messages seperated by '|' 73 | loop { 74 | match buf.find_from_index(b'|', num_bytes) { 75 | Some(i) => { 76 | let response = format!("Message {} contained {} bytes\n", num_messages, num_bytes + i - 1); // don't include '|' in number of bytes 77 | match stream.write(&response.as_bytes()) { 78 | Ok(n) => { 79 | println!("wrote {} bytes to the client", n); 80 | 81 | // update read cursor past '|' and reset num_bytes since last '|' 82 | buf.advance_read(i + 1); 83 | num_bytes = 0; 84 | num_messages += 1; 85 | } 86 | Err(e) => panic!("got an error writing to connection: {}", e), 87 | } 88 | } 89 | None => break, 90 | } 91 | } 92 | } 93 | } 94 | 95 | fn main() { 96 | let listener = TcpListener::bind("127.0.0.1:8888").unwrap(); 97 | for stream in listener.incoming() { 98 | match stream { 99 | Ok(stream) => { 100 | thread::spawn(move || handle_client(stream)); 101 | } 102 | Err(e) => panic!("got an error accepting connection: {}", e), 103 | } 104 | } 105 | } 106 | ``` 107 | -------------------------------------------------------------------------------- /src/bytes.rs: -------------------------------------------------------------------------------- 1 | use std::io::IoSlice; 2 | 3 | use crate::CircBuf; 4 | 5 | use bytes::{buf::UninitSlice, Buf, BufMut}; 6 | 7 | impl Buf for CircBuf { 8 | fn advance(&mut self, count: usize) { 9 | assert!(count == 0 || count <= self.remaining()); 10 | self.advance_read_raw(count); 11 | } 12 | 13 | fn chunk(&self) -> &[u8] { 14 | let [left, right] = self.get_bytes(); 15 | match (left.is_empty(), right.is_empty()) { 16 | (true, true) => left, 17 | (true, false) => right, 18 | (false, true) => left, 19 | (false, false) => left, 20 | } 21 | } 22 | 23 | fn remaining(&self) -> usize { 24 | self.len() 25 | } 26 | 27 | fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { 28 | let [left, right] = self.get_bytes(); 29 | let mut count = 0; 30 | if let Some(slice) = dst.get_mut(0) { 31 | count += 1; 32 | *slice = IoSlice::new(left); 33 | } 34 | if let Some(slice) = dst.get_mut(1) { 35 | count += 1; 36 | *slice = IoSlice::new(right); 37 | } 38 | count 39 | } 40 | } 41 | 42 | unsafe impl BufMut for CircBuf { 43 | unsafe fn advance_mut(&mut self, count: usize) { 44 | assert!(count == 0 || count <= self.remaining_mut()); 45 | self.advance_write_raw(count); 46 | } 47 | 48 | fn chunk_mut(&mut self) -> &mut UninitSlice { 49 | let [left, right] = self.get_avail(); 50 | let slice = match (left.is_empty(), right.is_empty()) { 51 | (true, true) => left, 52 | (true, false) => right, 53 | (false, true) => left, 54 | (false, false) => left, 55 | }; 56 | // https://docs.rs/bytes/latest/bytes/buf/struct.UninitSlice.html#method.from_raw_parts_mut 57 | unsafe { UninitSlice::from_raw_parts_mut(slice.as_mut_ptr(), slice.len()) } 58 | } 59 | 60 | fn remaining_mut(&self) -> usize { 61 | self.avail() 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use crate::CircBuf; 68 | use bytes::{Buf, BufMut}; 69 | use std::io::IoSlice; 70 | 71 | #[test] 72 | fn bytes_buf_and_bufmut() { 73 | let mut c = CircBuf::with_capacity(4).unwrap(); 74 | 75 | assert_eq!(c.remaining(), 0); 76 | assert_eq!(c.remaining_mut(), 3); 77 | unsafe { 78 | c.advance_mut(2); 79 | } 80 | assert_eq!(c.remaining(), 2); 81 | assert_eq!(c.remaining_mut(), 1); 82 | c.advance(1); 83 | assert_eq!(c.remaining(), 1); 84 | assert_eq!(c.remaining_mut(), 2); 85 | unsafe { 86 | c.advance_mut(1); 87 | } 88 | assert_eq!(c.remaining(), 2); 89 | assert_eq!(c.remaining_mut(), 1); 90 | 91 | assert_eq!(::chunk(&c).len(), 2); 92 | assert_eq!(c.chunk_mut().len(), 1); 93 | 94 | let mut dst = [IoSlice::new(&[]); 2]; 95 | assert_eq!(c.chunks_vectored(&mut dst[..]), 2); 96 | 97 | assert_eq!(dst[0].len(), 2); 98 | assert_eq!(dst[1].len(), 0); 99 | } 100 | 101 | #[test] 102 | fn bytes_buf_remaining() { 103 | use bytes::{Buf, BufMut}; 104 | 105 | let mut c = CircBuf::with_capacity(4).unwrap(); 106 | 107 | assert_eq!(c.remaining(), 0); 108 | assert_eq!(c.remaining_mut(), 3); 109 | assert!(!c.has_remaining()); 110 | assert!(c.has_remaining_mut()); 111 | 112 | unsafe { 113 | c.advance_mut(3); 114 | } 115 | 116 | assert_eq!(c.remaining(), 3); 117 | assert_eq!(c.remaining_mut(), 0); 118 | assert!(c.has_remaining()); 119 | assert!(!c.has_remaining_mut()); 120 | 121 | c.advance(2); 122 | 123 | assert_eq!(c.remaining(), 1); 124 | assert_eq!(c.remaining_mut(), 2); 125 | assert!(c.has_remaining()); 126 | assert!(c.has_remaining_mut()); 127 | 128 | c.advance(1); 129 | 130 | assert_eq!(c.remaining(), 0); 131 | assert_eq!(c.remaining_mut(), 3); 132 | assert!(!c.has_remaining()); 133 | assert!(c.has_remaining_mut()); 134 | } 135 | 136 | #[cfg(feature = "bytes")] 137 | #[test] 138 | fn bytes_bufmut_hello() { 139 | use bytes::BufMut; 140 | 141 | let mut c = CircBuf::with_capacity(16).unwrap(); 142 | 143 | unsafe { 144 | c.chunk_mut().write_byte(0, b'h'); 145 | c.chunk_mut().write_byte(1, b'e'); 146 | 147 | c.advance_mut(2); 148 | 149 | c.chunk_mut().write_byte(0, b'l'); 150 | c.chunk_mut().write_byte(1, b'l'); 151 | c.chunk_mut().write_byte(2, b'o'); 152 | 153 | c.advance_mut(3); 154 | } 155 | 156 | assert_eq!(c.get_bytes()[0], b"hello"); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | minimal-rustc-version: 14 | name: minimal-rustc-version 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Install latest nightly 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: nightly 22 | override: true 23 | 24 | - uses: Swatinem/rust-cache@v1 25 | - name: Install cargo-hack 26 | run: cargo install cargo-hack 27 | - name: "check --all-features -Z minimal-versions" 28 | run: | 29 | # Remove dev-dependencies from Cargo.toml to prevent the next `cargo update` 30 | # from determining minimal versions based on dev-dependencies. 31 | cargo hack --remove-dev-deps --workspace 32 | # Update Cargo.lock to minimal version dependencies. 33 | cargo update -Z minimal-versions 34 | cargo hack check --all-features --ignore-private 35 | - name: "check --all-features --unstable -Z minimal-versions" 36 | run: | 37 | # Remove dev-dependencies from Cargo.toml to prevent the next `cargo update` 38 | # from determining minimal versions based on dev-dependencies. 39 | cargo hack --remove-dev-deps --workspace 40 | # Update Cargo.lock to minimal version dependencies. 41 | cargo update -Z minimal-versions 42 | cargo hack check --all-features --ignore-private 43 | 44 | doc: 45 | name: doc 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v2 49 | - name: Install Rust nightly 50 | uses: actions-rs/toolchain@v1 51 | with: 52 | toolchain: nightly 53 | override: true 54 | 55 | - uses: Swatinem/rust-cache@v1 56 | - name: "doc --lib --all-features" 57 | run: cargo doc --lib --no-deps --all-features --document-private-items 58 | env: 59 | RUSTFLAGS: --cfg docsrs 60 | RUSTDOCFLAGS: --cfg docsrs -Dwarnings 61 | 62 | fmt: 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Checkout repository 66 | uses: actions/checkout@v2 67 | 68 | - name: Install latest stable 69 | uses: actions-rs/toolchain@v1 70 | with: 71 | toolchain: stable 72 | override: true 73 | components: rustfmt 74 | 75 | - name: Check code format 76 | run: cargo fmt --all -- --check 77 | 78 | clippy: 79 | runs-on: ubuntu-latest 80 | steps: 81 | - name: Checkout repository 82 | uses: actions/checkout@v2 83 | 84 | - name: Install latest stable 85 | uses: actions-rs/toolchain@v1 86 | with: 87 | toolchain: stable 88 | override: true 89 | components: clippy 90 | 91 | - name: Check lints 92 | run: cargo clippy --all --tests --all-features 93 | 94 | stable-build: 95 | runs-on: ubuntu-latest 96 | steps: 97 | - name: Checkout repository 98 | uses: actions/checkout@v2 99 | 100 | - name: Install latest stable 101 | uses: actions-rs/toolchain@v1 102 | with: 103 | toolchain: stable 104 | override: true 105 | components: rustfmt, clippy 106 | 107 | - name: Build debug 108 | run: cargo build --verbose 109 | 110 | - name: Build release 111 | run: cargo build --release --verbose 112 | 113 | - uses: actions/upload-artifact@master 114 | with: 115 | name: cargo-artefacts 116 | path: target 117 | 118 | nightly-build: 119 | runs-on: ubuntu-latest 120 | steps: 121 | - uses: actions/checkout@v2 122 | - name: Install latest nightly 123 | uses: actions-rs/toolchain@v1 124 | with: 125 | toolchain: nightly 126 | override: true 127 | components: rustfmt, clippy 128 | 129 | - name: Build debug 130 | run: cargo build --verbose 131 | 132 | - name: Build release 133 | run: cargo build --release --verbose 134 | 135 | - uses: actions/upload-artifact@master 136 | with: 137 | name: nightly-cargo-artefacts 138 | path: target 139 | 140 | beta-build: 141 | runs-on: ubuntu-latest 142 | steps: 143 | - uses: actions/checkout@v2 144 | - name: Install latest beta 145 | uses: actions-rs/toolchain@v1 146 | with: 147 | toolchain: beta 148 | override: true 149 | components: rustfmt, clippy 150 | 151 | - name: Build debug 152 | run: cargo build --verbose 153 | 154 | - name: Build release 155 | run: cargo build --release --verbose 156 | 157 | - uses: actions/upload-artifact@master 158 | with: 159 | name: nightly-cargo-artefacts 160 | path: target 161 | 162 | stable-tests: 163 | runs-on: ubuntu-latest 164 | needs: ['stable-build'] 165 | steps: 166 | - name: Checkout repository 167 | uses: actions/checkout@v2 168 | 169 | - name: Install latest stable 170 | uses: actions-rs/toolchain@v1 171 | with: 172 | toolchain: stable 173 | override: true 174 | components: rustfmt, clippy 175 | 176 | - uses: actions/download-artifact@master 177 | with: 178 | name: cargo-artefacts 179 | path: target 180 | 181 | - name: Cargo test debug 182 | run: cargo test --all-features 183 | 184 | - name: Cargo test release 185 | run: cargo test --release --all-features 186 | 187 | nightly-tests: 188 | runs-on: ubuntu-latest 189 | needs: ['nightly-build'] 190 | steps: 191 | - name: Checkout repository 192 | uses: actions/checkout@v2 193 | 194 | - name: Install latest nightly 195 | uses: actions-rs/toolchain@v1 196 | with: 197 | toolchain: nightly 198 | override: true 199 | components: rustfmt, clippy 200 | 201 | - uses: actions/download-artifact@master 202 | with: 203 | name: nightly-cargo-artefacts 204 | path: target 205 | 206 | - name: Cargo test debug 207 | run: cargo test --all-features 208 | 209 | - name: Cargo test release 210 | run: cargo test --release --all-features 211 | 212 | beta-tests: 213 | runs-on: ubuntu-latest 214 | needs: ['beta-build'] 215 | steps: 216 | - name: Checkout repository 217 | uses: actions/checkout@v2 218 | 219 | - name: Install latest nightly 220 | uses: actions-rs/toolchain@v1 221 | with: 222 | toolchain: beta 223 | override: true 224 | components: rustfmt, clippy 225 | 226 | - uses: actions/download-artifact@master 227 | with: 228 | name: nightly-cargo-artefacts 229 | path: target 230 | 231 | - name: Cargo test debug 232 | run: cargo test --all-features 233 | 234 | - name: Cargo test release 235 | run: cargo test --release --all-features 236 | 237 | bench: 238 | runs-on: ubuntu-latest 239 | needs: ["stable-tests"] 240 | steps: 241 | - name: Checkout repository 242 | uses: actions/checkout@v2 243 | 244 | - uses: actions/download-artifact@master 245 | with: 246 | name: nightly-cargo-artefacts 247 | path: target 248 | 249 | - name: Install latest nightly 250 | uses: actions-rs/toolchain@v1 251 | with: 252 | toolchain: nightly 253 | override: true 254 | 255 | - name: Run benchmark 256 | run: cargo +nightly bench --all-features -- --include-ignored | tee bench.txt 257 | 258 | - name: Archive code coverage results 259 | uses: actions/upload-artifact@v1 260 | with: 261 | name: benchmarks 262 | path: bench.txt 263 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2016 Jerome Froelich 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | //! An implementation of a growable circular buffer of bytes. The `CircBuf` struct 24 | //! manages a buffer of bytes allocated on the heap. The buffer can be grown when needed 25 | //! and can return slices into its internal buffer that can be used for both normal IO 26 | //! (e.g. `read` and `write`) as well as vector IO (`readv` and `writev`). 27 | //! 28 | //! ## Feature flags 29 | //! 30 | //! - `bytes`: Adds a dependency on the `bytes` crate and implements the `Buf` and `BufMut` 31 | //! traits from that crate. 32 | //! 33 | //! ## Example 34 | //! 35 | //! Below is a simple example of a server which makes use of a `CircBuf` to read messages 36 | //! from a client. It uses the `vecio` crate to call `readv` and `writev` on the socket. 37 | //! Messages are seperated by a vertical bar `|` and the server returns to the client 38 | //! the number of bytes in each message it receives. 39 | //! 40 | //! ``` rust,no_run 41 | //! extern crate vecio; 42 | //! extern crate circbuf; 43 | //! 44 | //! use std::thread; 45 | //! use std::net::{TcpListener, TcpStream}; 46 | //! use std::io::Write; 47 | //! use vecio::Rawv; 48 | //! use circbuf::CircBuf; 49 | //! 50 | //! fn handle_client(mut stream: TcpStream) { 51 | //! let mut buf = CircBuf::new(); 52 | //! let mut num_messages = 0; // number of messages from the client 53 | //! let mut num_bytes = 0; // number of bytes read since last '|' 54 | //! 55 | //! loop { 56 | //! // grow the buffer if it is less than half full 57 | //! if buf.len() > buf.avail() { 58 | //! buf.grow().unwrap(); 59 | //! } 60 | //! 61 | //! let n; 62 | //! { 63 | //! n = match stream.readv(&buf.get_avail()) { 64 | //! Ok(n) => { 65 | //! if n == 0 { 66 | //! // EOF 67 | //! println!("client closed connection"); 68 | //! break; 69 | //! } 70 | //! n 71 | //! } 72 | //! Err(e) => panic!("got an error reading from a connection: {}", e), 73 | //! }; 74 | //! } 75 | //! 76 | //! println!("read {} bytes from the client", n); 77 | //! 78 | //! // update write cursor 79 | //! buf.advance_write(n); 80 | //! 81 | //! // parse request from client for messages seperated by '|' 82 | //! loop { 83 | //! match buf.find_from_index(b'|', num_bytes) { 84 | //! Some(i) => { 85 | //! // update read cursor past '|' and reset num_bytes since last '|' 86 | //! buf.advance_read(i + 1); 87 | //! num_bytes = 0; 88 | //! num_messages += 1; 89 | //! 90 | //! let response = format!("Message {} contained {} bytes\n", num_messages, i - 1); // don't inclue '|' in num_bytes 91 | //! match stream.write(&response.as_bytes()) { 92 | //! Ok(n) => { 93 | //! println!("wrote {} bytes to the client", n); 94 | //! } 95 | //! Err(e) => panic!("got an error writing to connection: {}", e), 96 | //! } 97 | //! } 98 | //! None => break, 99 | //! } 100 | //! } 101 | //! } 102 | //! } 103 | //! 104 | //! fn main() { 105 | //! let listener = TcpListener::bind("127.0.0.1:8888").unwrap(); 106 | //! for stream in listener.incoming() { 107 | //! match stream { 108 | //! Ok(stream) => { 109 | //! thread::spawn(move || handle_client(stream)); 110 | //! } 111 | //! Err(e) => panic!("got an error accepting connection: {}", e), 112 | //! } 113 | //! } 114 | //! } 115 | //! ``` 116 | 117 | #[cfg(feature = "bytes")] 118 | mod bytes; 119 | 120 | use std::boxed::Box; 121 | use std::error; 122 | use std::fmt; 123 | use std::io; 124 | use std::ptr::copy_nonoverlapping; 125 | use std::slice::from_raw_parts; 126 | use std::slice::from_raw_parts_mut; 127 | 128 | /// Default size of the circular buffer is 1 page 129 | pub const DEFAULT_CAPACITY: usize = 4096; 130 | 131 | /// Double the size of the buffer by default when growing it 132 | pub const DEFAULT_SIZE_MULTIPLIER: usize = 2; 133 | 134 | /// Circular Buffer Error 135 | /// 136 | /// The errors that are used with `CircBuf`. Namely, that the buffer is empty, full, 137 | /// or recieved a `usize` argument which could cause an overflow. 138 | #[derive(Debug, Clone)] 139 | pub enum CircBufError { 140 | BufEmpty, 141 | BufFull, 142 | Overflow, 143 | NotEnoughData, 144 | NotEnoughPlace, 145 | } 146 | 147 | impl fmt::Display for CircBufError { 148 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 149 | match *self { 150 | CircBufError::BufEmpty => write!(f, "CircBuf is full"), 151 | CircBufError::BufFull => write!(f, "CircBuf is empty"), 152 | CircBufError::Overflow => write!(f, "Value would overflow usize"), 153 | CircBufError::NotEnoughData => { 154 | write!(f, "Doesn't have enough data to advance read cursor") 155 | } 156 | CircBufError::NotEnoughPlace => { 157 | write!(f, "Doesn't have enough place to advance write cursor") 158 | } 159 | } 160 | } 161 | } 162 | 163 | impl error::Error for CircBufError {} 164 | 165 | /// Circular Buffer 166 | /// 167 | /// A growable circular buffer for use with bytes. 168 | #[derive(Debug, Default)] 169 | pub struct CircBuf { 170 | buf: Box<[u8]>, 171 | write_cursor: usize, 172 | read_cursor: usize, 173 | } 174 | 175 | // TODO: make note that we cant let write_cursor ever equal read_cursor, but we can do the opposite 176 | 177 | impl CircBuf { 178 | /// Create a new CircBuf. The default size of the buffer is `DEFAULT_CAPACITY` bytes. 179 | pub fn new() -> Self { 180 | CircBuf { 181 | buf: Box::new([0; DEFAULT_CAPACITY]), 182 | write_cursor: 0, 183 | read_cursor: 0, 184 | } 185 | } 186 | 187 | /// Create a new CircBuf with a size of `cap` bytes. The capacity will be rounded up 188 | /// to the nearest power of two greater than or equal to `cap`. If the nearest power 189 | /// of two overflows `usize`, return `CircBufError::Overflow`, else return the buffer. 190 | pub fn with_capacity(cap: usize) -> Result { 191 | let capacity = match cap.checked_next_power_of_two() { 192 | Some(capacity) => capacity, 193 | None => return Err(CircBufError::Overflow), 194 | }; 195 | 196 | Ok(CircBuf { 197 | buf: vec![0; capacity].into_boxed_slice(), 198 | write_cursor: 0, 199 | read_cursor: 0, 200 | }) 201 | } 202 | 203 | /// Get the capacity of the buffer. This value is equal to one less than the length 204 | /// of the underlying buffer because we cannot let the write cursor to ever circle 205 | /// back to being equal with the read cursor. 206 | pub fn cap(&self) -> usize { 207 | self.buf.len() - 1 208 | } 209 | 210 | fn cacl_len(read_cursor: usize, write_cursor: usize, len: usize) -> usize { 211 | if write_cursor < read_cursor { 212 | len - read_cursor + write_cursor 213 | } else { 214 | write_cursor - read_cursor 215 | } 216 | } 217 | 218 | /// Get the number of bytes stored in the buffer. 219 | pub fn len(&self) -> usize { 220 | Self::cacl_len(self.read_cursor, self.write_cursor, self.buf.len()) 221 | } 222 | 223 | /// Get the number of bytes available in the buffer. 224 | pub fn avail(&self) -> usize { 225 | self.cap() - self.len() 226 | } 227 | 228 | /// Get a `bool` indicating whether the buffer is empty or not. 229 | pub fn is_empty(&self) -> bool { 230 | self.read_cursor == self.write_cursor 231 | } 232 | 233 | /// Get a `bool` indicating whether the buffer is full or not. 234 | pub fn is_full(&self) -> bool { 235 | self.avail() == 0 236 | } 237 | 238 | /// Find the first occurence of `val` in the buffer starting from `index`. If `val` 239 | /// exists in the buffer return the index of the first occurence of `val` else return 240 | /// `None`. 241 | pub fn find_from_index(&self, val: u8, index: usize) -> Option { 242 | if index >= self.len() { 243 | return None; 244 | } 245 | 246 | if self.write_cursor < self.read_cursor { 247 | if self.read_cursor + index < self.buf.len() { 248 | for (i, b) in self.buf[self.read_cursor + index..].iter().enumerate() { 249 | if *b == val { 250 | return Some(i + index); 251 | } 252 | } 253 | } 254 | 255 | for (i, b) in self.buf[..self.write_cursor].iter().enumerate() { 256 | if *b == val { 257 | return Some(i + self.buf.len() - self.read_cursor); 258 | } 259 | } 260 | 261 | None 262 | } else { 263 | for (i, b) in self.buf[self.read_cursor + index..self.write_cursor] 264 | .iter() 265 | .enumerate() 266 | { 267 | if *b == val { 268 | return Some(i + index); 269 | } 270 | } 271 | 272 | None 273 | } 274 | } 275 | 276 | /// Find the first occurence of `val` in the buffer. If `val` exists in the buffer 277 | /// return the index of the first occurence of `val` else return `None`. A convenience 278 | /// method for `find_from_index` with 0 as the index. 279 | pub fn find(&self, val: u8) -> Option { 280 | self.find_from_index(val, 0) 281 | } 282 | 283 | /// Get the next byte to be read from the buffer without removing it from it the buffer. 284 | /// Returns the byte if the buffer is not empty, else returns a `BufEmpty` error. 285 | pub fn peek(&self) -> Result { 286 | if self.is_empty() { 287 | return Err(CircBufError::BufEmpty); 288 | } 289 | 290 | Ok(self.buf[self.read_cursor]) 291 | } 292 | 293 | /// Get the next byte to be read from the buffer and remove it from it the buffer. 294 | /// Returns the byte if the buffer is not empty, else returns a `BufEmpty` error. 295 | pub fn get(&mut self) -> Result { 296 | if self.is_empty() { 297 | return Err(CircBufError::BufEmpty); 298 | } 299 | 300 | let val = self.buf[self.read_cursor]; 301 | self.advance_read_raw(1); 302 | 303 | Ok(val) 304 | } 305 | 306 | /// Put `val` into the buffer. Returns a `BufFull` error if the buffer is full, else 307 | /// returns an empty tuple `()`. 308 | pub fn put(&mut self, val: u8) -> Result<(), CircBufError> { 309 | if self.avail() == 0 { 310 | return Err(CircBufError::BufFull); 311 | } 312 | 313 | self.buf[self.write_cursor] = val; 314 | self.advance_write_raw(1); 315 | 316 | Ok(()) 317 | } 318 | 319 | fn cacl_read(read_cursor: usize, num: usize, len: usize) -> usize { 320 | (read_cursor + num) % len 321 | } 322 | 323 | /// Advance the buffer's read cursor `num` bytes. 324 | /// # Warning 325 | /// There is no check perform on internal write cursor. This can lead to data lost. 326 | pub fn advance_read_raw(&mut self, num: usize) { 327 | self.read_cursor = Self::cacl_read(self.read_cursor, num, self.buf.len()); 328 | } 329 | 330 | /// Advance the buffer's read cursor `num` bytes. 331 | pub fn advance_read(&mut self, num: usize) -> Result<(), CircBufError> { 332 | if num > self.len() { 333 | Err(CircBufError::NotEnoughData) 334 | } else { 335 | self.advance_read_raw(num); 336 | 337 | Ok(()) 338 | } 339 | } 340 | 341 | /// Advance the buffer's write cursor `num` bytes. 342 | /// # Warning 343 | /// There is no check perform on internal read cursor. This can lead to data lost. 344 | pub fn advance_write_raw(&mut self, num: usize) { 345 | self.write_cursor = (self.write_cursor + num) % self.buf.len(); 346 | } 347 | 348 | /// Advance the buffer's write cursor `num` bytes. 349 | pub fn advance_write(&mut self, num: usize) -> Result<(), CircBufError> { 350 | if num > self.avail() { 351 | Err(CircBufError::NotEnoughPlace) 352 | } else { 353 | self.advance_write_raw(num); 354 | 355 | Ok(()) 356 | } 357 | } 358 | 359 | /// Clear the buffer. 360 | pub fn clear(&mut self) { 361 | self.write_cursor = 0; 362 | self.read_cursor = 0; 363 | } 364 | 365 | /// Grow the size of the buffer by `factor`. The size of the buffer will be rounded 366 | /// up to the nearest power of two that is greater than or equal to the the current 367 | /// size of the buffer multiplied by `factor`. If the size of the buffer will overflow 368 | /// `usize` then `CircBufError::Overflow` will be returned else an empty tuple `()` will 369 | /// be returned. 370 | pub fn grow_with_factor(&mut self, factor: usize) -> Result<(), CircBufError> { 371 | let cap = match self.buf.len().checked_mul(factor) { 372 | Some(cap) => cap, 373 | None => return Err(CircBufError::Overflow), 374 | }; 375 | 376 | let cap_checked = match cap.checked_next_power_of_two() { 377 | Some(cap) => cap, 378 | None => return Err(CircBufError::Overflow), 379 | }; 380 | 381 | let mut new_buf = vec![0; cap_checked].into_boxed_slice(); 382 | let mut bytes_written = 0; 383 | 384 | // copy the readable bytes from the old buffer to the new buffer 385 | if self.write_cursor < self.read_cursor { 386 | let num_to_end = self.buf.len() - self.read_cursor; 387 | unsafe { copy_nonoverlapping(&self.buf[self.read_cursor], &mut new_buf[0], num_to_end) } 388 | 389 | bytes_written += num_to_end; 390 | 391 | unsafe { 392 | copy_nonoverlapping(&self.buf[0], &mut new_buf[bytes_written], self.write_cursor) 393 | } 394 | 395 | bytes_written += self.write_cursor; 396 | } else { 397 | let num_to_copy = self.write_cursor - self.read_cursor; 398 | unsafe { 399 | copy_nonoverlapping(&self.buf[self.read_cursor], &mut new_buf[0], num_to_copy) 400 | } 401 | bytes_written += num_to_copy; 402 | } 403 | 404 | self.buf = new_buf; 405 | self.write_cursor = bytes_written; 406 | self.read_cursor = 0; 407 | 408 | Ok(()) 409 | } 410 | 411 | /// Grow the size of the buffer. The buffer will be expanded by a factor of 412 | /// `DEFAULT_SIZE_MULTIPLIER`. If the size of the buffer will overflow `usize` 413 | /// then `CircBufError::Overflow` will be returned else an empty tuple `()` will 414 | /// be returned. 415 | pub fn grow(&mut self) -> Result<(), CircBufError> { 416 | self.grow_with_factor(DEFAULT_SIZE_MULTIPLIER) 417 | } 418 | 419 | /// Return an array that contains two mutable slices which point to the available 420 | /// bytes in the buffer. The combined lengths of the slices will be the minimum 421 | /// of `size` and `self.avail()`. If the available bytes in the buffer are contiguous 422 | /// then the second slice will be of size zero. Otherwise, the first slice will point 423 | /// to the bytes available at the end of the buffer and the second slice will point 424 | /// to the bytes available at the start of the buffer. The array can be used for 425 | /// vector IO. 426 | pub fn get_avail_upto_size(&mut self, size: usize) -> [&mut [u8]; 2] { 427 | let first_buf; 428 | let second_buf; 429 | 430 | let min = if self.avail() < size { 431 | self.avail() 432 | } else { 433 | size 434 | }; 435 | 436 | if self.write_cursor >= self.read_cursor && min > self.buf.len() - self.write_cursor { 437 | // the min available bytes wrap around the buffer, so we need to two slices to access 438 | // the available bytes at the end and the start of the buffer 439 | unsafe { 440 | first_buf = from_raw_parts_mut( 441 | &mut self.buf[self.write_cursor], 442 | self.buf.len() - self.write_cursor, 443 | ); 444 | second_buf = from_raw_parts_mut( 445 | &mut self.buf[0], 446 | min - (self.buf.len() - self.write_cursor), 447 | ); 448 | } 449 | } else { 450 | // the min available bytes are contiguous so our second buffer will have size zero 451 | unsafe { 452 | first_buf = from_raw_parts_mut(&mut self.buf[self.write_cursor], min); 453 | second_buf = from_raw_parts_mut(&mut self.buf[self.write_cursor], 0); 454 | } 455 | } 456 | 457 | [first_buf, second_buf] 458 | } 459 | 460 | /// Return an array that contains two slices which point to the bytes that have been 461 | /// written to the buffer. The combined lengths of the slices will be the minimum 462 | /// of `size` and `self.len()`. If the bytes are contiguous then the second slice will be 463 | /// of size zero. Otherwise, the first slice will point to the bytes at the end of the 464 | /// buffer and the second slice will point to the bytes available at the start of the 465 | /// buffer. 466 | pub fn get_bytes_upto_size(&self, size: usize) -> [&[u8]; 2] { 467 | let first_buf; 468 | let second_buf; 469 | 470 | let min = if self.len() < size { self.len() } else { size }; 471 | 472 | if self.write_cursor < self.read_cursor && min > self.buf.len() - self.read_cursor { 473 | // the min bytes to be read wrap around the buffer so we need two slices 474 | unsafe { 475 | first_buf = from_raw_parts( 476 | &self.buf[self.read_cursor], 477 | self.buf.len() - self.read_cursor, 478 | ); 479 | second_buf = 480 | from_raw_parts(&self.buf[0], min - (self.buf.len() - self.read_cursor)); 481 | } 482 | } else { 483 | // the min bytes to be read are contiguous so our second buffer will be of size zero 484 | unsafe { 485 | first_buf = from_raw_parts(&self.buf[self.read_cursor], min); 486 | second_buf = from_raw_parts(&self.buf[self.read_cursor], 0); 487 | } 488 | } 489 | 490 | [first_buf, second_buf] 491 | } 492 | 493 | /// Return an array that contains two slices which point to the bytes that are available 494 | /// in the buffer. A convenience method for `get_avail_upto_size` with `size` equal to 495 | /// `self.avail()` so all bytes written available in buffer will be returned. 496 | pub fn get_avail(&mut self) -> [&mut [u8]; 2] { 497 | let avail = self.avail(); 498 | self.get_avail_upto_size(avail) 499 | } 500 | 501 | /// Return an array that contains two slices which point to the bytes that have been 502 | /// written to the buffer. A convenience method for `get_bytes_upto_size` with `size` 503 | /// equal to `self.len()` so all bytes written to the buffer will be returned. 504 | pub fn get_bytes(&self) -> [&[u8]; 2] { 505 | let len = self.len(); 506 | self.get_bytes_upto_size(len) 507 | } 508 | 509 | /// Return a reader that doesn't advance the read cursor. 510 | pub fn reader_peek(&self) -> CircBufPeekReader { 511 | CircBufPeekReader { 512 | inner: self, 513 | peek_cursor: self.read_cursor, 514 | } 515 | } 516 | 517 | /// len is the len of data available in src starting at read_cursor 518 | unsafe fn read_peek( 519 | src: &[u8], 520 | dest: &mut [u8], 521 | len: usize, 522 | read_cursor: usize, 523 | ) -> io::Result { 524 | // either we have enough data to copy or we don't 525 | let num_to_read = if len < dest.len() { len } else { dest.len() }; 526 | let num_to_end = src.len() - read_cursor; 527 | 528 | // check if we need to wrap around the buffer to read num_to_read bytes 529 | if num_to_read > num_to_end { 530 | copy_nonoverlapping(&src[read_cursor], &mut dest[0], num_to_end); 531 | copy_nonoverlapping(&src[0], &mut dest[num_to_end], num_to_read - num_to_end); 532 | } else { 533 | copy_nonoverlapping(&src[read_cursor], &mut dest[0], num_to_read); 534 | } 535 | 536 | Ok(num_to_read) 537 | } 538 | } 539 | 540 | pub struct CircBufPeekReader<'a> { 541 | inner: &'a CircBuf, 542 | peek_cursor: usize, 543 | } 544 | 545 | impl<'a> CircBufPeekReader<'a> { 546 | /// Return the remaining number of bytes to peek 547 | pub fn len(&self) -> usize { 548 | CircBuf::cacl_len( 549 | self.peek_cursor, 550 | self.inner.write_cursor, 551 | self.inner.buf.len(), 552 | ) 553 | } 554 | 555 | pub fn is_empty(&self) -> bool { 556 | self.peek_cursor == self.inner.write_cursor 557 | } 558 | 559 | /// Reset the peek cursor to the original read cursor of the inner `CircBuf` 560 | pub fn reset(&mut self) { 561 | self.peek_cursor = self.inner.read_cursor; 562 | } 563 | 564 | /// Return the number of bytes currently peeked. Useful to use for `CircBuf::advance_read`. 565 | pub fn count_peek(&self) -> usize { 566 | self.inner.len() - self.len() 567 | } 568 | } 569 | 570 | impl<'a> io::Read for CircBufPeekReader<'a> { 571 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 572 | let len = self.len(); 573 | 574 | if len == 0 { 575 | Ok(0) 576 | } else { 577 | let readed = 578 | unsafe { CircBuf::read_peek(&self.inner.buf, buf, len, self.peek_cursor)? }; 579 | 580 | self.peek_cursor = CircBuf::cacl_read(self.peek_cursor, readed, self.inner.buf.len()); 581 | 582 | Ok(readed) 583 | } 584 | } 585 | } 586 | 587 | impl io::Read for CircBuf { 588 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 589 | let len = self.len(); 590 | 591 | if len == 0 { 592 | Ok(0) 593 | } else { 594 | let readed = unsafe { CircBuf::read_peek(&self.buf, buf, len, self.read_cursor)? }; 595 | 596 | self.advance_read_raw(readed); 597 | 598 | Ok(readed) 599 | } 600 | } 601 | } 602 | 603 | impl io::Write for CircBuf { 604 | fn write(&mut self, buf: &[u8]) -> io::Result { 605 | let avail = self.avail(); 606 | 607 | if avail == 0 || buf.is_empty() { 608 | return Ok(0); 609 | } 610 | 611 | let num_to_write = if avail < buf.len() { avail } else { buf.len() }; 612 | 613 | if self.write_cursor < self.read_cursor { 614 | unsafe { copy_nonoverlapping(&buf[0], &mut self.buf[self.write_cursor], num_to_write) }; 615 | } else { 616 | // check if we need to wrap around the buffer to write num_to_write bytes 617 | let num_to_end = self.buf.len() - self.write_cursor; 618 | let min = if num_to_write < num_to_end { 619 | num_to_write 620 | } else { 621 | num_to_end 622 | }; 623 | 624 | unsafe { copy_nonoverlapping(&buf[0], &mut self.buf[self.write_cursor], min) }; 625 | 626 | if min != num_to_write { 627 | unsafe { copy_nonoverlapping(&buf[min], &mut self.buf[0], num_to_write - min) }; 628 | } 629 | } 630 | 631 | self.advance_write_raw(num_to_write); 632 | 633 | Ok(num_to_write) 634 | } 635 | 636 | /// Do nothing 637 | fn flush(&mut self) -> io::Result<()> { 638 | Ok(()) 639 | } 640 | } 641 | 642 | #[cfg(test)] 643 | mod tests { 644 | use super::{CircBuf, CircBufError, DEFAULT_CAPACITY}; 645 | use std::io::{Read, Write}; 646 | 647 | #[test] 648 | fn create_circbuf() { 649 | let c = CircBuf::new(); 650 | 651 | assert_eq!(c.cap(), DEFAULT_CAPACITY - 1); 652 | assert_eq!(c.avail(), DEFAULT_CAPACITY - 1); 653 | assert_eq!(c.len(), 0); 654 | assert!(c.is_empty()); 655 | } 656 | 657 | #[test] 658 | fn create_circbuf_with_capacity() { 659 | let c = CircBuf::with_capacity(49).unwrap(); 660 | 661 | assert_eq!(c.cap(), 64 - 1); 662 | assert_eq!(c.avail(), 64 - 1); 663 | assert_eq!(c.len(), 0); 664 | assert!(c.is_empty()); 665 | } 666 | 667 | #[test] 668 | fn put_peek_and_get_bytes() { 669 | let mut c = CircBuf::with_capacity(4).unwrap(); 670 | 671 | c.put(1).unwrap(); 672 | c.put(2).unwrap(); 673 | c.put(3).unwrap(); 674 | 675 | assert_eq!( 676 | c.put(4).unwrap_err().to_string(), 677 | CircBufError::BufFull.to_string() 678 | ); 679 | assert_eq!(c.avail(), 0); 680 | assert_eq!(c.len(), 3); 681 | assert!(c.is_full()); 682 | 683 | assert_eq!(c.peek().unwrap(), 1); 684 | assert_eq!(c.get().unwrap(), 1); 685 | assert_eq!(c.peek().unwrap(), 2); 686 | assert_eq!(c.get().unwrap(), 2); 687 | assert_eq!(c.peek().unwrap(), 3); 688 | assert_eq!(c.get().unwrap(), 3); 689 | assert_eq!( 690 | c.get().unwrap_err().to_string(), 691 | CircBufError::BufEmpty.to_string() 692 | ); 693 | 694 | assert_eq!(c.avail(), 3); 695 | assert_eq!(c.len(), 0); 696 | assert!(c.is_empty()); 697 | 698 | // check that everything continues to work when we wrap around the buffer 699 | c.put(4).unwrap(); 700 | c.put(5).unwrap(); 701 | c.put(6).unwrap(); 702 | 703 | assert_eq!(c.peek().unwrap(), 4); 704 | assert_eq!(c.get().unwrap(), 4); 705 | assert_eq!(c.peek().unwrap(), 5); 706 | assert_eq!(c.get().unwrap(), 5); 707 | assert_eq!(c.peek().unwrap(), 6); 708 | assert_eq!(c.get().unwrap(), 6); 709 | } 710 | 711 | #[test] 712 | fn find_bytes() { 713 | let mut c = CircBuf::with_capacity(8).unwrap(); 714 | 715 | c.put(7).unwrap(); 716 | c.put(6).unwrap(); 717 | c.put(5).unwrap(); 718 | c.put(4).unwrap(); 719 | c.put(3).unwrap(); 720 | c.put(2).unwrap(); 721 | c.put(1).unwrap(); 722 | 723 | assert_eq!(c.find(4).unwrap(), 3); 724 | assert_eq!(c.find_from_index(3, 2).unwrap(), 4); 725 | assert!(c.find_from_index(6, 2).is_none()); 726 | 727 | c.advance_read(4).unwrap(); 728 | 729 | assert_eq!(c.find(1).unwrap(), 2); 730 | assert!(c.find(5).is_none()); 731 | 732 | // wrap around the buffer 733 | c.put(10).unwrap(); 734 | c.put(11).unwrap(); 735 | c.put(12).unwrap(); 736 | 737 | assert_eq!(c.find(12).unwrap(), 5); 738 | assert_eq!(c.find_from_index(12, 4).unwrap(), 5); 739 | } 740 | 741 | #[test] 742 | fn grow_buffer() { 743 | let mut c = CircBuf::with_capacity(4).unwrap(); 744 | 745 | c.put(1).unwrap(); 746 | c.put(2).unwrap(); 747 | c.put(3).unwrap(); 748 | 749 | c.grow().unwrap(); 750 | 751 | assert!(c.cap() == 8 - 1); 752 | assert!(c.avail() == 4); 753 | assert!(c.len() == 3); 754 | 755 | assert_eq!(c.get().unwrap(), 1); 756 | assert_eq!(c.get().unwrap(), 2); 757 | assert_eq!(c.get().unwrap(), 3); 758 | 759 | // grow a buffer that wraps around 760 | c.advance_read_raw(7); 761 | c.advance_write_raw(7); 762 | 763 | c.put(1).unwrap(); 764 | c.put(2).unwrap(); 765 | c.put(3).unwrap(); 766 | 767 | c.grow().unwrap(); 768 | 769 | assert_eq!(c.get().unwrap(), 1); 770 | assert_eq!(c.get().unwrap(), 2); 771 | assert_eq!(c.get().unwrap(), 3); 772 | } 773 | 774 | #[test] 775 | fn read_and_write_bytes() { 776 | let mut c = CircBuf::with_capacity(8).unwrap(); 777 | 778 | assert_eq!(c.write(b"foo").unwrap(), 3); 779 | assert_eq!(c.write(b"bar").unwrap(), 3); 780 | 781 | // ignore empty writes 782 | assert_eq!(c.write(b"").unwrap(), 0); 783 | 784 | assert_eq!(c.cap(), 7); 785 | assert_eq!(c.avail(), 1); 786 | assert_eq!(c.len(), 6); 787 | 788 | let mut buf = [0; 6]; 789 | 790 | assert_eq!(c.read(&mut buf).unwrap(), 6); 791 | assert!(b"foobar".iter().zip(buf.iter()).all(|(a, b)| a == b)); 792 | 793 | assert_eq!(c.cap(), 7); 794 | assert_eq!(c.avail(), 7); 795 | assert_eq!(c.len(), 0); 796 | 797 | // test read and write on a buffer that wraps around 798 | c.advance_read_raw(7); 799 | c.advance_write_raw(7); 800 | 801 | assert_eq!(c.write(b"foo").unwrap(), 3); 802 | assert_eq!(c.write(b"bar").unwrap(), 3); 803 | 804 | let mut buf = [0; 6]; 805 | 806 | assert_eq!(c.read(&mut buf).unwrap(), 6); 807 | assert!(b"foobar".iter().zip(buf.iter()).all(|(a, b)| a == b)); 808 | } 809 | 810 | #[test] 811 | fn get_bytes_and_avail() { 812 | let mut c = CircBuf::with_capacity(16).unwrap(); 813 | 814 | assert_eq!(c.write(b"funkytowns").unwrap(), 10); 815 | assert_eq!(c.len(), 10); 816 | assert_eq!(c.avail(), 5); 817 | 818 | { 819 | let bufs = c.get_avail_upto_size(4); 820 | assert_eq!(bufs.len(), 2); 821 | assert_eq!(bufs[0].len(), 4); 822 | assert_eq!(bufs[1].len(), 0); 823 | } 824 | 825 | { 826 | let bufs = c.get_bytes_upto_size(5); 827 | assert_eq!(bufs.len(), 2); 828 | assert_eq!(bufs[0].len(), 5); 829 | assert_eq!(bufs[1].len(), 0); 830 | assert!(b"funky".iter().zip(bufs[0].iter()).all(|(a, b)| a == b)); 831 | } 832 | 833 | // test when size is greate than `c.avail()` and `c.len()` respectively 834 | { 835 | let bufs = c.get_avail_upto_size(10); 836 | assert_eq!(bufs.len(), 2); 837 | assert_eq!(bufs[0].len(), 5); 838 | assert_eq!(bufs[1].len(), 0); 839 | } 840 | 841 | { 842 | let bufs = c.get_bytes_upto_size(12); 843 | assert_eq!(bufs.len(), 2); 844 | assert_eq!(bufs[0].len(), 10); 845 | assert_eq!(bufs[1].len(), 0); 846 | assert!(b"funky".iter().zip(bufs[0].iter()).all(|(a, b)| a == b)); 847 | } 848 | 849 | { 850 | let bufs = c.get_avail(); 851 | assert_eq!(bufs.len(), 2); 852 | assert_eq!(bufs[0].len(), 5); 853 | assert_eq!(bufs[1].len(), 0); 854 | } 855 | 856 | { 857 | let bufs = c.get_bytes(); 858 | assert_eq!(bufs.len(), 2); 859 | assert_eq!(bufs[0].len(), 10); 860 | assert_eq!(bufs[1].len(), 0); 861 | assert!(b"funkytowns" 862 | .iter() 863 | .zip(bufs[0].iter()) 864 | .all(|(a, b)| a == b)); 865 | } 866 | 867 | // test when the buffer wraps around 868 | c.advance_read(10).unwrap(); 869 | assert_eq!(c.write(b"brickhouse").unwrap(), 10); 870 | assert_eq!(c.len(), 10); 871 | assert_eq!(c.avail(), 5); 872 | 873 | { 874 | let bufs = c.get_avail_upto_size(4); 875 | assert_eq!(bufs.len(), 2); 876 | assert_eq!(bufs[0].len(), 4); 877 | assert_eq!(bufs[1].len(), 0); 878 | } 879 | 880 | { 881 | let bufs = c.get_bytes_upto_size(8); 882 | assert_eq!(bufs.len(), 2); 883 | assert_eq!(bufs[0].len(), 6); 884 | assert_eq!(bufs[1].len(), 2); 885 | assert!(b"brickh".iter().zip(bufs[0].iter()).all(|(a, b)| a == b)); 886 | assert!(b"ou".iter().zip(bufs[1].iter()).all(|(a, b)| a == b)); 887 | } 888 | 889 | // test when size is greate than `c.avail()` and `c.len()` respectively 890 | { 891 | let bufs = c.get_avail_upto_size(17); 892 | assert_eq!(bufs.len(), 2); 893 | assert_eq!(bufs[0].len(), 5); 894 | assert_eq!(bufs[1].len(), 0); 895 | } 896 | 897 | { 898 | let bufs = c.get_bytes_upto_size(12); 899 | assert_eq!(bufs.len(), 2); 900 | assert_eq!(bufs[0].len(), 6); 901 | assert_eq!(bufs[1].len(), 4); 902 | assert!(b"brickh".iter().zip(bufs[0].iter()).all(|(a, b)| a == b)); 903 | assert!(b"ouse".iter().zip(bufs[1].iter()).all(|(a, b)| a == b)); 904 | } 905 | 906 | { 907 | let bufs = c.get_avail(); 908 | assert_eq!(bufs.len(), 2); 909 | assert_eq!(bufs[0].len(), 5); 910 | assert_eq!(bufs[1].len(), 0); 911 | } 912 | 913 | { 914 | let bufs = c.get_bytes(); 915 | assert_eq!(bufs.len(), 2); 916 | assert_eq!(bufs[0].len(), 6); 917 | assert_eq!(bufs[1].len(), 4); 918 | assert!(b"brickh".iter().zip(bufs[0].iter()).all(|(a, b)| a == b)); 919 | assert!(b"ouse".iter().zip(bufs[1].iter()).all(|(a, b)| a == b)); 920 | } 921 | } 922 | 923 | #[test] 924 | fn reader_peek() { 925 | let data = &b"foo\nbar\nbaz\n"[..]; 926 | let mut c = CircBuf::with_capacity(data.len()).unwrap(); 927 | 928 | for _ in 0..42 { 929 | c.write_all(data).unwrap(); 930 | 931 | let read_cursor = c.read_cursor; 932 | let mut v = Vec::new(); 933 | let mut peek_reader = c.reader_peek(); 934 | let readed = std::io::copy(&mut peek_reader, &mut v).unwrap() as usize; 935 | assert!(peek_reader.is_empty()); 936 | assert_eq!(peek_reader.count_peek(), data.len()); 937 | assert_eq!(peek_reader.len(), 0); 938 | assert_eq!(v, data); 939 | assert_eq!(c.read_cursor, read_cursor); 940 | 941 | c.advance_read(readed).unwrap(); 942 | } 943 | } 944 | 945 | #[test] 946 | #[cfg(unix)] 947 | fn vecio() { 948 | use std::io::{Seek, SeekFrom}; 949 | use vecio::Rawv; 950 | 951 | let mut c = CircBuf::with_capacity(16).unwrap(); 952 | 953 | let mut file = tempfile::tempfile().unwrap(); 954 | assert_eq!(file.write(b"foo\nbar\nbaz\n").unwrap(), 12); 955 | file.seek(SeekFrom::Current(-12)).unwrap(); 956 | 957 | { 958 | let mut bufs = c.get_avail(); 959 | assert_eq!(file.readv(&mut bufs).unwrap(), 12); 960 | } 961 | 962 | // advance the write cursor since we just wrote 12 bytes to the buffer 963 | c.advance_write(12).unwrap(); 964 | 965 | // wrap around the buffer 966 | c.advance_read(12).unwrap(); 967 | assert_eq!(c.write(b"fizzbuzz").unwrap(), 8); 968 | 969 | let mut s = String::new(); 970 | assert_eq!(c.read_to_string(&mut s).unwrap(), 8); 971 | assert_eq!(s, "fizzbuzz"); 972 | } 973 | } 974 | --------------------------------------------------------------------------------