buf-list-1.1.2/.cargo/config.toml000064400000000000000000000001261046102023000146640ustar 00000000000000[alias] xfmt = "fmt -- --config imports_granularity=Crate --config group_imports=One" buf-list-1.1.2/.cargo_vcs_info.json0000644000000001360000000000100125620ustar { "git": { "sha1": "885d4149ff38618b8b5ca8aa7a6c558ccb802f1b" }, "path_in_vcs": "" }buf-list-1.1.2/.claude/settings.local.json000064400000000000000000000002741046102023000165140ustar 00000000000000{ "permissions": { "allow": [ "Bash(cargo test)", "Bash(cargo test:*)", "Bash(cargo clippy:*)", "Bash(cargo fix:*)" ], "deny": [], "ask": [] } }buf-list-1.1.2/.github/codecov.yml000064400000000000000000000001711046102023000150560ustar 00000000000000coverage: status: project: default: informational: true patch: default: target: 0% buf-list-1.1.2/.github/workflows/ci.yml000064400000000000000000000040721046102023000160700ustar 00000000000000on: push: branches: - main pull_request: branches: - main name: CI jobs: lint: name: Lint runs-on: ubuntu-latest env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 - name: Lint (rustfmt) run: cargo xfmt --check - name: Update Cargo.lock run: | cargo update git config --global user.email "test-user@example.com" git config --global user.name "Test User" git commit -am "Update Cargo.lock for clippy" - name: Lint (clippy) run: cargo hack clippy --all-targets --feature-powerset - name: Install cargo readme uses: taiki-e/install-action@v2 with: tool: cargo-readme - name: Run cargo readme run: ./scripts/regenerate-readmes.sh - name: Check for differences run: git diff --exit-code build: name: Build and test runs-on: ${{ matrix.os }} strategy: matrix: os: [ ubuntu-latest ] # 1.70 is the MSRV rust-version: - "1.70" - stable fail-fast: false env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust-version }} components: rustfmt, clippy - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 - name: Build run: | cargo hack build --feature-powerset - name: Test # Dev dependencies have an MSRV > 1.70. if: ${{ matrix.rust-version.version == 'stable' }} run: cargo hack test --feature-powerset - name: Test with updated Cargo.lock # Dev dependencies have an MSRV > 1.70. if: ${{ matrix.rust-version.version == 'stable' }} run: | cargo update cargo hack test --feature-powerset buf-list-1.1.2/.github/workflows/coverage.yml000064400000000000000000000017531046102023000172730ustar 00000000000000on: push: branches: [ main, auto, canary ] pull_request: branches: - main name: Test coverage env: CARGO_TERM_COLOR: always jobs: coverage: name: Collect test coverage runs-on: ubuntu-latest # nightly rust might break from time to time continue-on-error: true env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: llvm-tools-preview - uses: Swatinem/rust-cache@v2 - name: Install latest nextest release uses: taiki-e/install-action@nextest - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Update Cargo.lock run: cargo update - name: Collect coverage data run: cargo llvm-cov nextest --all-features --lcov --output-path lcov.info - name: Upload coverage data to codecov uses: codecov/codecov-action@v3 with: files: lcov.info buf-list-1.1.2/.github/workflows/docs.yml000064400000000000000000000015641046102023000164300ustar 00000000000000on: push: branches: - main name: Docs jobs: docs: name: Build and deploy documentation concurrency: ci-${{ github.ref }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - name: Update Cargo.lock run: cargo update - uses: Swatinem/rust-cache@v2 - name: Build rustdoc run: cargo doc --all-features env: RUSTC_BOOTSTRAP: 1 RUSTDOCFLAGS: --cfg=doc_cfg - name: Organize run: | rm -rf target/gh-pages mkdir target/gh-pages mv target/doc target/gh-pages/rustdoc touch target/gh-pages/.nojekyll - name: Deploy uses: JamesIves/github-pages-deploy-action@releases/v4 with: branch: gh-pages folder: target/gh-pages single-commit: true buf-list-1.1.2/.github/workflows/release.yml000064400000000000000000000013611046102023000171130ustar 00000000000000# adapted from https://github.com/taiki-e/cargo-hack/blob/main/.github/workflows/release.yml name: crates.io release on: push: tags: - '*' jobs: create-release: if: github.repository_owner == 'sunshowers-code' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: persist-credentials: false - name: Install Rust uses: dtolnay/rust-toolchain@stable - run: cargo publish env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - uses: taiki-e/create-gh-release-action@v1 with: changelog: CHANGELOG.md title: buf-list $version branch: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} buf-list-1.1.2/.gitignore000064400000000000000000000001021046102023000133330ustar 00000000000000/target # Cargo.lock is checked in to support older Rust versions buf-list-1.1.2/CHANGELOG.md000064400000000000000000000072761046102023000131770ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org). ## [1.1.2] - 2025-10-02 Simplify `Buf::chunks_vectored` implementations for `BufList` and `Cursor`. ## [1.1.1] - 2025-10-01 ### Added `bytes::Buf` implementations for `Cursor` and `Cursor<&BufList>`, to go with the existing `bytes::Buf` implementation for `BufList` itself. This way, the same `BufList` can have multiple cursors over it that all use the `bytes::Buf` implementation. ## [1.1.0] - 2025-09-29 ### Changed - With `Cursor::read_exact`, if there aren't enough bytes remaining, the position will be set to the end of the buffer. This mirrors the same behavior change in `std::io::Cursor` introduced in Rust 1.80 (see [rust-lang/rust#125404]). - MSRV updated to Rust 1.70. - Internal improvement: computed start positions for chunks are now stored in a `OnceLock` on the `BufList` rather than being recomputed each time a `Cursor` is constructed. In the case where there might be many `Cursor` instances on the same `&BufList`, this allows for start positions to be shared. Thanks to [inanna-malick](https://github.com/inanna-malick) for your first cntribution! ### Fixed - Replaced obsolete `doc_auto_cfg` with `doc_cfg`, to fix Rust nightly builds with the `doc_cfg` flag enabled. [rust-lang/rust#125404]: https://github.com/rust-lang/rust/pull/125404 ## [1.0.3] - 2023-04-09 - Documentation improvements. ## [1.0.2] - 2023-04-09 ### Added - A new type `Cursor` which wraps a `BufList` or `&BufList`, and implements `Seek`, `Read` and `BufRead`. - `BufList` implements `From` for any `T` that can be converted to `Bytes`. This creates a `BufList` with a single chunk. - `BufList::get_chunk` returns the chunk at the provided index. - New optional features: - `tokio1`: makes `Cursor` implement tokio's `AsyncSeek`, `AsyncRead` and `AsyncBufRead` - `futures03`: makes `Cursor` implement futures's `AsyncSeek`, `AsyncRead` and `AsyncBufRead`. ## [1.0.1] - 2023-02-16 ### Added - Add recipes for converting a `BufList` into a `Stream` or a `TryStream`. ## [1.0.0] - 2023-01-06 ### Added - `BufList` now implements `Extend`. This means you can now collect a stream of `Bytes`, or other `Buf` chunks, directly into a `BufList` via [`StreamExt::collect`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html#method.collect). - Collecting a fallible stream is also possible, via [`TryStreamExt::try_collect`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html#method.try_collect). ### Changed - `push_chunk` now has a type parameter `B: Buf` rather than `impl Buf`. ## [0.1.3] - 2022-12-11 - Fix license indication in README: this crate is Apache-2.0 only, not MIT OR Apache-2.0. ## [0.1.2] - 2022-12-10 - Fix intradoc links. ## [0.1.1] - 2022-12-10 - Fixes to README. - Add MSRV policy. ## [0.1.0] - 2022-12-10 - Initial release. [1.1.2]: https://github.com/sunshowers-code/buf-list/releases/tag/1.1.2 [1.1.1]: https://github.com/sunshowers-code/buf-list/releases/tag/1.1.1 [1.1.0]: https://github.com/sunshowers-code/buf-list/releases/tag/1.1.0 [1.0.3]: https://github.com/sunshowers-code/buf-list/releases/tag/1.0.3 [1.0.2]: https://github.com/sunshowers-code/buf-list/releases/tag/1.0.2 [1.0.1]: https://github.com/sunshowers-code/buf-list/releases/tag/1.0.1 [1.0.0]: https://github.com/sunshowers-code/buf-list/releases/tag/1.0.0 [0.1.3]: https://github.com/sunshowers-code/buf-list/releases/tag/0.1.3 [0.1.2]: https://github.com/sunshowers-code/buf-list/releases/tag/0.1.2 [0.1.1]: https://github.com/sunshowers-code/buf-list/releases/tag/0.1.1 [0.1.0]: https://github.com/sunshowers-code/buf-list/releases/tag/0.1.0 buf-list-1.1.2/Cargo.lock0000644000000437520000000000100105500ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anyhow" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "buf-list" version = "1.1.2" dependencies = [ "anyhow", "bytes", "dummy-waker", "futures", "futures-io", "proptest", "test-strategy", "tokio", ] [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "dummy-waker" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea6672d73216c05740850c789368d371ca226dc8104d5f2e30c74252d5d6e5e" [[package]] name = "errno" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-lifetimes" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi", "libc", "windows-sys 0.48.0", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libm" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "linux-raw-sys" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", "libm", ] [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", "quick-error 2.0.1", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", "unarray", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rand_xorshift" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rustix" version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] [[package]] name = "rusty-fork" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", "quick-error 1.2.3", "tempfile", "wait-timeout", ] [[package]] name = "slab" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" dependencies = [ "autocfg", ] [[package]] name = "structmeta" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "104842d6278bf64aa9d2f182ba4bde31e8aec7a131d29b7f444bb9b344a09e2a" dependencies = [ "proc-macro2", "quote", "structmeta-derive", "syn", ] [[package]] name = "structmeta-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24420be405b590e2d746d83b01f09af673270cf80e9b003a5fa7b651c58c7d93" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", "windows-sys 0.45.0", ] [[package]] name = "test-strategy" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61348cf95c0dfa12f0b6155f5b564d2806f518620a28263280ae357b41c96b4a" dependencies = [ "proc-macro2", "quote", "structmeta", "syn", ] [[package]] name = "tokio" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a38d31d7831c6ed7aad00aa4c12d9375fd225a6dd77da1d25b707346319a975" dependencies = [ "autocfg", "bytes", "memchr", "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.0", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" buf-list-1.1.2/Cargo.toml0000644000000034570000000000100105710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "buf-list" version = "1.1.2" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A list of buffers that implements the bytes::Buf trait" readme = "README.md" keywords = [ "buffers", "zero-copy", "io", ] categories = [ "network-programming", "data-structures", ] license = "Apache-2.0" repository = "https://github.com/sunshowers-code/buf-list" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg=doc_cfg"] [features] futures03 = ["futures-io-03"] tokio1 = ["tokio"] [lib] name = "buf_list" path = "src/lib.rs" [[test]] name = "tests" path = "tests/tests.rs" [dependencies.bytes] version = "1.3.0" [dependencies.futures-io-03] version = "0.3.25" optional = true package = "futures-io" [dependencies.tokio] version = "1.0.0" features = ["io-std"] optional = true [dev-dependencies.anyhow] version = "1.0.70" [dev-dependencies.dummy-waker] version = "1.1.0" [dev-dependencies.futures] version = "0.3.25" [dev-dependencies.proptest] version = "1.1.0" [dev-dependencies.test-strategy] version = "0.3.0" [dev-dependencies.tokio] version = "1.0.0" features = [ "io-std", "io-util", "macros", "rt", ] [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(doc_cfg)"] buf-list-1.1.2/Cargo.toml.orig000064400000000000000000000017041046102023000142430ustar 00000000000000[package] name = "buf-list" version = "1.1.2" edition = "2021" description = "A list of buffers that implements the bytes::Buf trait" categories = ["network-programming", "data-structures"] keywords = ["buffers", "zero-copy", "io"] license = "Apache-2.0" readme = "README.md" repository = "https://github.com/sunshowers-code/buf-list" rust-version = "1.70" [lints] rust.unexpected_cfgs = { level = "warn", check-cfg = ["cfg(doc_cfg)"] } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg=doc_cfg"] [dependencies] bytes = "1.3.0" futures-io-03 = { package = "futures-io", version = "0.3.25", optional = true } tokio = { version = "1.0.0", features = ["io-std"], optional = true } [dev-dependencies] anyhow = "1.0.70" dummy-waker = "1.1.0" futures = "0.3.25" proptest = "1.1.0" test-strategy = "0.3.0" tokio = { version = "1.0.0", features = ["io-std", "io-util", "macros", "rt"] } [features] futures03 = ["futures-io-03"] tokio1 = ["tokio"] buf-list-1.1.2/LICENSE000064400000000000000000000251371046102023000123670ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. buf-list-1.1.2/README.md000064400000000000000000000120101046102023000126230ustar 00000000000000# buf-list [![buf-list on crates.io](https://img.shields.io/crates/v/buf-list)](https://crates.io/crates/buf-list) [![Documentation (latest release)](https://docs.rs/buf-list/badge.svg)](https://docs.rs/buf-list/) [![Documentation (main)](https://img.shields.io/badge/docs-main-brightgreen)](https://sunshowers-code.github.io/buf-list/rustdoc/buf_list/) [![License](https://img.shields.io/badge/license-Apache-green.svg)](LICENSE) A segmented list of `bytes::Bytes` chunks. ## Overview This crate provides a `BufList` type that is a list of [`Bytes`](bytes::Bytes) chunks. The type implements `bytes::Buf`, so it can be used in any APIs that use `Buf`. The main use case for `BufList` is to buffer data received as a stream of chunks without having to copy them into a single contiguous chunk of memory. The `BufList` can then be passed into any APIs that accept `Buf`. If you've ever wanted a `Vec` or a `VecDeque`, this type is for you. ## Cursors This crate also provides `Cursor`, which is a cursor type around a `BufList`. Similar to similar to `std::io::Cursor`, a `Cursor` around a `BufList` implements * [`Seek`](std::io::Seek), [`Read`](std::io::Read), and [`BufRead`](std::io::BufRead) * `bytes::Buf` as well (in other words, both `BufList`s and `Cursor`s over them can be passed into any APIs that accept `Buf`). ## Examples Gather chunks into a `BufList`, then write them all out to standard error in one go: ```rust use buf_list::BufList; use tokio::io::AsyncWriteExt; let mut buf_list = BufList::new(); buf_list.push_chunk(&b"hello "[..]); buf_list.push_chunk(&b"world"[..]); buf_list.push_chunk(&b"!"[..]); let mut stderr = tokio::io::stderr(); stderr.write_all_buf(&mut buf_list).await?; ``` Collect a fallible stream of `Bytes` into a `BufList`: ```rust use buf_list::BufList; use bytes::Bytes; use futures::TryStreamExt; // A common example is a stream of bytes read over HTTP. let stream = futures::stream::iter( vec![ Ok(Bytes::from_static(&b"laputa, "[..])), Ok(Bytes::from_static(&b"castle "[..])), Ok(Bytes::from_static(&b"in the sky"[..])) ], ); let buf_list = stream.try_collect::().await?; assert_eq!(buf_list.num_chunks(), 3); ``` ### Converting to `Stream`s A `BufList` can be converted into a `futures::Stream`, or a `TryStream`, of `Bytes` chunks. Use this recipe to do so: (This will be exposed as an API on `BufList` once `Stream` and/or `TryStream` become part of stable Rust.) ```rust use buf_list::BufList; use bytes::Bytes; use futures::{Stream, TryStream}; fn into_stream(buf_list: BufList) -> impl Stream { futures::stream::iter(buf_list) } fn into_try_stream(buf_list: BufList) -> impl TryStream { futures::stream::iter(buf_list.into_iter().map(Ok)) } ``` ## Optional features * `tokio1`: With this feature enabled, `Cursor` implements the `tokio` crate's [`AsyncSeek`](tokio::io::AsyncSeek), [`AsyncRead`](tokio::io::AsyncRead) and [`AsyncBufRead`](tokio::io::AsyncBufRead). * `futures03`: With this feature enabled, `Cursor` implements the `futures` crate's [`AsyncSeek`](futures_io_03::AsyncSeek), [`AsyncRead`](futures_io_03::AsyncRead) and [`AsyncBufRead`](futures_io_03::AsyncBufRead). Note that supporting `futures03` means exporting 0.x types as a public interface. **This violates the [C-STABLE](https://rust-lang.github.io/api-guidelines/necessities.html#public-dependencies-of-a-stable-crate-are-stable-c-stable) guideline.** However, the maintainer of `buf-list` considers that acceptable since `futures03` is an optional feature and not critical to `buf-list`. As newer versions of the `futures` crate are released, `buf-list` will support their versions of the async traits as well. ## Minimum supported Rust version The minimum supported Rust version (MSRV) is **1.70**. Optional features may cause a bump in the MSRV. `buf-list` has a conservative MSRV policy. MSRV bumps will be sparing, and if so, they will be accompanied by a minor version bump. ## Contributing Pull requests are welcome! Please follow the [code of conduct](https://github.com/sunshowers-code/.github/blob/main/CODE_OF_CONDUCT.md). ## License buf-list is copyright 2022 The buf-list Contributors. All rights reserved. Copied and adapted from linkerd2-proxy; [original code](https://github.com/linkerd/linkerd2-proxy/blob/d36e3a75ef428453945eedaa230a32982c17d30d/linkerd/http-retry/src/replay.rs#L421-L492) written by Eliza Weisman. linkerd2-proxy is copyright 2018 the linkerd2-proxy authors. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. buf-list-1.1.2/README.tpl000064400000000000000000000027501046102023000130340ustar 00000000000000# {{crate}} [![buf-list on crates.io](https://img.shields.io/crates/v/buf-list)](https://crates.io/crates/buf-list) [![Documentation (latest release)](https://docs.rs/buf-list/badge.svg)](https://docs.rs/buf-list/) [![Documentation (main)](https://img.shields.io/badge/docs-main-brightgreen)](https://sunshowers-code.github.io/buf-list/rustdoc/buf_list/) [![License](https://img.shields.io/badge/license-Apache-green.svg)](LICENSE) {{readme}} ## Contributing Pull requests are welcome! Please follow the [code of conduct](https://github.com/sunshowers-code/.github/blob/main/CODE_OF_CONDUCT.md). ## License buf-list is copyright 2022 The buf-list Contributors. All rights reserved. Copied and adapted from linkerd2-proxy; [original code](https://github.com/linkerd/linkerd2-proxy/blob/d36e3a75ef428453945eedaa230a32982c17d30d/linkerd/http-retry/src/replay.rs#L421-L492) written by Eliza Weisman. linkerd2-proxy is copyright 2018 the linkerd2-proxy authors. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. buf-list-1.1.2/proptest-regressions/cursor/tests.txt000064400000000000000000000001041046102023000210060ustar 00000000000000cc bf8cfd35fe51927d4570039a090a23c4f78c40cf88e8e6ac80f6fe66f02e1a4b buf-list-1.1.2/release.toml000064400000000000000000000004231046102023000136660ustar 00000000000000sign-tag = true # Required for templates below to work consolidate-commits = false pre-release-commit-message = "[{{crate_name}}] version {{version}}" tag-message = "[{{crate_name}}] version {{version}}" tag-name = "{{version}}" publish = false dependent-version = "upgrade" buf-list-1.1.2/rustfmt.toml000064400000000000000000000000271046102023000137520ustar 00000000000000style_edition = "2024" buf-list-1.1.2/scripts/fix-readmes.awk000064400000000000000000000010571046102023000157540ustar 00000000000000# Fix up readmes: # * Replace ## with # in code blocks. # * Remove [] without a following () from output. BEGIN { true = 1 false = 0 in_block = false } { if (!in_block && $0 ~ /^```/) { in_block = true } else if (in_block && $0 ~ /^```$/) { in_block = false } if (in_block) { sub(/## /, "# ") print $0 } else { # Strip [] without a () that immediately follows them from # the output. subbed = gensub(/\[([^\[]+)]([^\(]|$)/, "\\1\\2", "g") print subbed } } buf-list-1.1.2/scripts/regenerate-readmes.sh000075500000000000000000000007241046102023000171420ustar 00000000000000#!/usr/bin/env bash # Copyright (c) The cargo-guppy Contributors # SPDX-License-Identifier: MIT OR Apache-2.0 # Regenerate readme files in this repository. set -eo pipefail cd "$(git rev-parse --show-toplevel)" git ls-files | grep README.tpl$ | while read -r readme; do dir=$(dirname "$readme") cargo readme --project-root "$dir" > "$dir/README.md.tmp" gawk -f "scripts/fix-readmes.awk" "$dir/README.md.tmp" > "$dir/README.md" rm "$dir/README.md.tmp" done buf-list-1.1.2/src/cursor/futures_imp.rs000064400000000000000000000025001046102023000163630ustar 00000000000000// Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 use crate::{BufList, Cursor}; use futures_io_03::{AsyncBufRead, AsyncRead, AsyncSeek}; use std::{ io::{self, IoSliceMut, SeekFrom}, pin::Pin, task::{Context, Poll}, }; impl + Unpin> AsyncSeek for Cursor { fn poll_seek( mut self: Pin<&mut Self>, _: &mut Context<'_>, pos: SeekFrom, ) -> Poll> { Poll::Ready(io::Seek::seek(&mut *self, pos)) } } impl + Unpin> AsyncRead for Cursor { fn poll_read( mut self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { Poll::Ready(io::Read::read(&mut *self, buf)) } fn poll_read_vectored( mut self: Pin<&mut Self>, _: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], ) -> Poll> { Poll::Ready(io::Read::read_vectored(&mut *self, bufs)) } } impl + Unpin> AsyncBufRead for Cursor { fn poll_fill_buf(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(io::BufRead::fill_buf(self.get_mut())) } fn consume(mut self: Pin<&mut Self>, amt: usize) { io::BufRead::consume(&mut *self, amt) } } buf-list-1.1.2/src/cursor/mod.rs000064400000000000000000000374061046102023000146150ustar 00000000000000// Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 #[cfg(feature = "futures03")] mod futures_imp; #[cfg(test)] mod tests; #[cfg(feature = "tokio1")] mod tokio_imp; use crate::{BufList, errors::ReadExactError}; use bytes::{Buf, Bytes}; use std::{ cmp::Ordering, io::{self, IoSlice, IoSliceMut, SeekFrom}, }; /// A `Cursor` wraps an in-memory `BufList` and provides it with a [`Seek`] implementation. /// /// `Cursor`s allow `BufList`s to implement [`Read`] and [`BufRead`], allowing a `BufList` to be /// used anywhere you might use a reader or writer that does actual I/O. /// /// The cursor may either own or borrow a `BufList`: both `Cursor` and `Cursor<&BufList>` /// are supported. /// /// # Optional features /// /// * `tokio1`: With this feature enabled, [`Cursor`] implements the `tokio` crate's /// [`AsyncSeek`](tokio::io::AsyncSeek), [`AsyncRead`](tokio::io::AsyncRead) and /// [`AsyncBufRead`](tokio::io::AsyncBufRead). /// * `futures03`: With this feature enabled, [`Cursor`] implements the `futures` crate's /// [`AsyncSeek`](futures_io_03::AsyncSeek), [`AsyncRead`](futures_io_03::AsyncRead) and /// [`AsyncBufRead`](futures_io_03::AsyncBufRead). /// /// [`Read`]: std::io::Read /// [`BufRead`]: std::io::BufRead /// [`Seek`]: std::io::Seek pub struct Cursor { inner: T, /// Data associated with the cursor. data: CursorData, } impl> Cursor { /// Creates a new cursor wrapping the provided `BufList`. /// /// # Examples /// /// ``` /// use buf_list::{BufList, Cursor}; /// /// let cursor = Cursor::new(BufList::new()); /// ``` pub fn new(inner: T) -> Cursor { let data = CursorData::new(); Cursor { inner, data } } /// Consumes this cursor, returning the underlying value. /// /// # Examples /// /// ``` /// use buf_list::{BufList, Cursor}; /// /// let cursor = Cursor::new(BufList::new()); /// /// let vec = cursor.into_inner(); /// ``` pub fn into_inner(self) -> T { self.inner } /// Gets a reference to the underlying value in this cursor. /// /// # Examples /// /// ``` /// use buf_list::{BufList, Cursor}; /// /// let cursor = Cursor::new(BufList::new()); /// /// let reference = cursor.get_ref(); /// ``` pub const fn get_ref(&self) -> &T { &self.inner } /// Returns the current position of this cursor. /// /// # Examples /// /// ``` /// use buf_list::{BufList, Cursor}; /// use std::io::prelude::*; /// use std::io::SeekFrom; /// /// let mut cursor = Cursor::new(BufList::from(&[1, 2, 3, 4, 5][..])); /// /// assert_eq!(cursor.position(), 0); /// /// cursor.seek(SeekFrom::Current(2)).unwrap(); /// assert_eq!(cursor.position(), 2); /// /// cursor.seek(SeekFrom::Current(-1)).unwrap(); /// assert_eq!(cursor.position(), 1); /// ``` pub const fn position(&self) -> u64 { self.data.pos } /// Sets the position of this cursor. /// /// # Examples /// /// ``` /// use buf_list::{BufList, Cursor}; /// /// let mut cursor = Cursor::new(BufList::from(&[1, 2, 3, 4, 5][..])); /// /// assert_eq!(cursor.position(), 0); /// /// cursor.set_position(2); /// assert_eq!(cursor.position(), 2); /// /// cursor.set_position(4); /// assert_eq!(cursor.position(), 4); /// ``` pub fn set_position(&mut self, pos: u64) { self.data.set_pos(self.inner.as_ref(), pos); } // --- // Helper methods // --- #[cfg(test)] fn assert_invariants(&self) -> anyhow::Result<()> { self.data.assert_invariants(self.inner.as_ref()) } } impl Clone for Cursor where T: Clone, { #[inline] fn clone(&self) -> Self { Cursor { inner: self.inner.clone(), data: self.data.clone(), } } #[inline] fn clone_from(&mut self, other: &Self) { self.inner.clone_from(&other.inner); self.data = other.data.clone(); } } impl> io::Seek for Cursor { fn seek(&mut self, style: SeekFrom) -> io::Result { self.data.seek_impl(self.inner.as_ref(), style) } fn stream_position(&mut self) -> io::Result { Ok(self.data.pos) } } impl> io::Read for Cursor { fn read(&mut self, buf: &mut [u8]) -> io::Result { Ok(self.data.read_impl(self.inner.as_ref(), buf)) } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { Ok(self.data.read_vectored_impl(self.inner.as_ref(), bufs)) } // TODO: is_read_vectored once that's available on stable Rust. fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { self.data.read_exact_impl(self.inner.as_ref(), buf) } } impl> io::BufRead for Cursor { fn fill_buf(&mut self) -> io::Result<&[u8]> { Ok(self.data.fill_buf_impl(self.inner.as_ref())) } fn consume(&mut self, amt: usize) { self.data.consume_impl(self.inner.as_ref(), amt); } } impl> Buf for Cursor { fn remaining(&self) -> usize { let total = self.data.num_bytes(self.inner.as_ref()); total.saturating_sub(self.data.pos) as usize } fn has_remaining(&self) -> bool { self.data.num_bytes(self.inner.as_ref()) > self.data.pos } fn chunk(&self) -> &[u8] { self.data.fill_buf_impl(self.inner.as_ref()) } fn advance(&mut self, amt: usize) { self.data.consume_impl(self.inner.as_ref(), amt); } fn chunks_vectored<'iovs>(&'iovs self, iovs: &mut [IoSlice<'iovs>]) -> usize { let list = self.inner.as_ref(); if iovs.is_empty() || !self.has_remaining() { return 0; } let current_chunk = self.data.chunk; let chunk_start_pos = list.get_start_pos()[current_chunk]; let offset_in_chunk = (self.data.pos - chunk_start_pos) as usize; iovs[0] = IoSlice::new( &list.get_chunk(current_chunk).expect("chunk is in range")[offset_in_chunk..], ); // Fill up the remaining iovs with as many slices as possible. let to_fill = (iovs.len()).min(list.num_chunks() - current_chunk); for (i, iov) in iovs.iter_mut().enumerate().take(to_fill).skip(1) { *iov = IoSlice::new( list.get_chunk(current_chunk + i) .expect("chunk is in range"), ); } to_fill } } #[derive(Clone, Debug)] struct CursorData { /// The chunk number the cursor is pointing to. Kept in sync with pos. /// /// This is within the range [0, self.start_pos.len()). It is self.start_pos.len() - 1 iff pos /// is greater than list.num_bytes(). chunk: usize, /// The overall position in the stream. Kept in sync with chunk. pos: u64, } impl CursorData { fn new() -> Self { Self { chunk: 0, pos: 0 } } #[cfg(test)] fn assert_invariants(&self, list: &BufList) -> anyhow::Result<()> { use anyhow::ensure; ensure!( self.pos >= list.get_start_pos()[self.chunk], "invariant failed: current position {} >= start position {} (chunk = {})", self.pos, list.get_start_pos()[self.chunk], self.chunk ); let next_pos = list.get_start_pos().get(self.chunk + 1).copied().into(); ensure!( Offset::Value(self.pos) < next_pos, "invariant failed: next start position {:?} > current position {} (chunk = {})", next_pos, self.pos, self.chunk ); Ok(()) } fn seek_impl(&mut self, list: &BufList, style: SeekFrom) -> io::Result { let (base_pos, offset) = match style { SeekFrom::Start(n) => { self.set_pos(list, n); return Ok(n); } SeekFrom::End(n) => (self.num_bytes(list), n), SeekFrom::Current(n) => (self.pos, n), }; // Can't use checked_add_signed since it was only stabilized in Rust 1.66. This is adapted // from // https://github.com/rust-lang/rust/blame/ed937594d3/library/std/src/io/cursor.rs#L295-L299. let new_pos = if offset >= 0 { base_pos.checked_add(offset as u64) } else { base_pos.checked_sub(offset.wrapping_neg() as u64) }; match new_pos { Some(n) => { self.set_pos(list, n); Ok(self.pos) } None => Err(io::Error::new( io::ErrorKind::InvalidInput, "invalid seek to a negative or overflowing position", )), } } fn read_impl(&mut self, list: &BufList, buf: &mut [u8]) -> usize { // Read as much as possible until we fill up the buffer. let mut buf_pos = 0; while buf_pos < buf.len() { let (chunk, chunk_pos) = match self.get_chunk_and_pos(list) { Some(value) => value, None => break, }; // The number of bytes to copy is the smaller of the two: // - the length of the chunk - the position in it. // - the number of bytes remaining, which is buf.len() - buf_pos. let n_to_copy = (chunk.len() - chunk_pos).min(buf.len() - buf_pos); let chunk_bytes = chunk.as_ref(); let bytes_to_copy = &chunk_bytes[chunk_pos..(chunk_pos + n_to_copy)]; let dest = &mut buf[buf_pos..(buf_pos + n_to_copy)]; dest.copy_from_slice(bytes_to_copy); buf_pos += n_to_copy; // Increment the position. self.pos += n_to_copy as u64; // If we've finished reading through the chunk, move to the next chunk. if n_to_copy == chunk.len() - chunk_pos { self.chunk += 1; } } buf_pos } fn read_vectored_impl(&mut self, list: &BufList, bufs: &mut [IoSliceMut<'_>]) -> usize { let mut nread = 0; for buf in bufs { // Copy data from the buffer until we run out of bytes to copy. let n = self.read_impl(list, buf); nread += n; if n < buf.len() { break; } } nread } fn read_exact_impl(&mut self, list: &BufList, buf: &mut [u8]) -> io::Result<()> { // This is the same as read_impl as long as there's enough space. let total = self.num_bytes(list); let remaining = total.saturating_sub(self.pos); let buf_len = buf.len(); if remaining < buf_len as u64 { // Rust 1.80 and above will cause the position to be set to the end // of the buffer, due to (apparently) // https://github.com/rust-lang/rust/pull/125404. Follow that // behavior. self.set_pos(list, total); return Err(io::Error::new( io::ErrorKind::UnexpectedEof, ReadExactError { remaining, buf_len }, )); } self.read_impl(list, buf); Ok(()) } fn fill_buf_impl<'a>(&'a self, list: &'a BufList) -> &'a [u8] { const EMPTY_SLICE: &[u8] = &[]; match self.get_chunk_and_pos(list) { Some((chunk, chunk_pos)) => &chunk.as_ref()[chunk_pos..], // An empty return value means the end of the buffer has been reached. None => EMPTY_SLICE, } } fn consume_impl(&mut self, list: &BufList, amt: usize) { self.set_pos(list, self.pos + amt as u64); } fn set_pos(&mut self, list: &BufList, new_pos: u64) { match new_pos.cmp(&self.pos) { Ordering::Greater => { let start_pos = list.get_start_pos(); let next_start = start_pos.get(self.chunk + 1).copied().into(); if Offset::Value(new_pos) < next_start { // Within the same chunk. } else { // The above check ensures that we're not currently pointing to the last index // (since it would have returned Eof, which is greater than Offset(n) for any // n). // // Do a binary search for this element. match start_pos[self.chunk + 1..].binary_search(&new_pos) { // We're starting the search from self.chunk + 1, which means that the value // returned from binary_search is 1 less than the actual delta. Ok(delta_minus_one) => { // Exactly at the start point of a chunk. self.chunk += 1 + delta_minus_one; } // The value returned in the error case (not at the start point of a chunk) // is (delta - 1) + 1, so just delta. Err(delta) => { debug_assert!( delta > 0, "delta must be at least 1 since we already \ checked the same chunk (self.chunk = {})", self.chunk, ); self.chunk += delta; } } } } Ordering::Equal => {} Ordering::Less => { let start_pos = list.get_start_pos(); if start_pos.get(self.chunk).copied() <= Some(new_pos) { // Within the same chunk. } else { match start_pos[..self.chunk].binary_search(&new_pos) { Ok(chunk) => { // Exactly at the start point of a chunk. self.chunk = chunk; } Err(chunk_plus_1) => { debug_assert!( chunk_plus_1 > 0, "chunk_plus_1 must be at least 1 since self.start_pos[0] is 0 \ (self.chunk = {})", self.chunk, ); self.chunk = chunk_plus_1 - 1; } } } } } self.pos = new_pos; } #[inline] fn get_chunk_and_pos<'b>(&self, list: &'b BufList) -> Option<(&'b Bytes, usize)> { match list.get_chunk(self.chunk) { Some(chunk) => { // This guarantees that pos is not past the end of the list. debug_assert!( self.pos < self.num_bytes(list), "self.pos ({}) is less than num_bytes ({})", self.pos, self.num_bytes(list) ); Some(( chunk, (self.pos - list.get_start_pos()[self.chunk]) as usize, )) } None => { // pos is past the end of the list. None } } } fn num_bytes(&self, list: &BufList) -> u64 { *list .get_start_pos() .last() .expect("start_pos always has at least one element") } } /// This is the same as Option except Offset and Eof are reversed in ordering, i.e. Eof > /// Offset(T) for any T. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] enum Offset { Value(T), Eof, } impl From> for Offset { fn from(value: Option) -> Self { match value { Some(v) => Self::Value(v), None => Self::Eof, } } } buf-list-1.1.2/src/cursor/tests.rs000064400000000000000000000613741046102023000152010ustar 00000000000000// Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 // Property-based tests for Cursor. use crate::BufList; use anyhow::{Context, Result, bail, ensure}; use bytes::{Buf, Bytes}; use proptest::prelude::*; use std::{ fmt, io::{self, BufRead, IoSliceMut, Read, Seek, SeekFrom}, }; use test_strategy::{Arbitrary, proptest}; /// Assert that buf_list's cursor behaves identically to std::io::Cursor. #[proptest] fn proptest_cursor_ops( #[strategy(buf_list_strategy())] buf_list: BufList, #[strategy(cursor_ops_strategy())] ops: Vec, ) { let bytes = buf_list.clone().copy_to_bytes(buf_list.remaining()); let mut buf_list_cursor = crate::Cursor::new(&buf_list); let mut oracle_cursor = io::Cursor::new(bytes.as_ref()); eprintln!("\n**** start!"); for (index, cursor_op) in ops.into_iter().enumerate() { // apply_and_compare prints out the rest of the line. eprint!("** index {}, operation {:?}: ", index, cursor_op); cursor_op .apply_and_compare(&mut buf_list_cursor, &mut oracle_cursor) .with_context(|| format!("for index {}", index)) .unwrap(); } eprintln!("**** success"); } fn buf_list_strategy() -> impl Strategy { prop::collection::vec(prop::collection::vec(any::(), 1..128), 0..32) .prop_map(|chunks| chunks.into_iter().map(Bytes::from).collect()) } #[derive(Arbitrary, Clone, Debug)] enum CursorOp { SetPosition(prop::sample::Index), SeekStart(prop::sample::Index), SeekEnd(prop::sample::Index), SeekCurrent(prop::sample::Index), Read(prop::sample::Index), ReadVectored( #[strategy(prop::collection::vec(any::(), 0..8))] Vec, ), ReadExact(prop::sample::Index), // fill_buf can't be tested here because oracle is a contiguous block. Instead, we check its // return value separately. Consume(prop::sample::Index), // Buf trait operations BufChunk, BufAdvance(prop::sample::Index), BufChunksVectored(prop::sample::Index), BufCopyToBytes(prop::sample::Index), BufGetU8, BufGetU64, BufGetU64Le, // No need to test futures03 imps since they're simple wrappers around the main imps. #[cfg(feature = "tokio1")] PollRead { capacity: prop::sample::Index, filled: prop::sample::Index, }, } impl CursorOp { fn apply_and_compare( self, // The "mut" here is used in the branches corresponding to optional features. #[allow(unused_mut)] mut buf_list: &mut crate::Cursor<&BufList>, #[allow(unused_mut)] mut oracle: &mut io::Cursor<&[u8]>, ) -> Result<()> { let num_bytes = buf_list.get_ref().num_bytes(); match self { Self::SetPosition(index) => { // Allow going past the end of the list a bit. let index = index.index(1 + num_bytes * 5 / 4) as u64; eprintln!("set position: {}", index); buf_list.set_position(index); oracle.set_position(index); } Self::SeekStart(index) => { // Allow going past the end of the list a bit. let style = SeekFrom::Start(index.index(1 + num_bytes * 5 / 4) as u64); eprintln!("style: {:?}", style); let buf_list_res = buf_list.seek(style); let oracle_res = oracle.seek(style); Self::assert_io_result_eq(buf_list_res, oracle_res) .context("operation result didn't match")?; } Self::SeekEnd(index) => { // Allow going past the beginning and end of the list a bit. let index = index.index(1 + num_bytes * 3 / 2) as i64; let style = SeekFrom::End(index - (1 + num_bytes * 5 / 4) as i64); eprintln!("style: {:?}", style); let buf_list_res = buf_list.seek(style); let oracle_res = oracle.seek(style); Self::assert_io_result_eq(buf_list_res, oracle_res) .context("operation result didn't match")?; } Self::SeekCurrent(index) => { let index = index.index(1 + num_bytes * 3 / 2) as i64; // Center the index at roughly 0. let style = SeekFrom::Current(index - (num_bytes * 3 / 4) as i64); eprintln!("style: {:?}", style); let buf_list_res = buf_list.seek(style); let oracle_res = oracle.seek(style); Self::assert_io_result_eq(buf_list_res, oracle_res) .context("operation result didn't match")?; } Self::Read(index) => { let buf_size = index.index(1 + num_bytes * 5 / 4); eprintln!("buf_size: {}", buf_size); // Must initialize the whole vec here so &mut returns the whole buffer -- can't use // with_capacity! let mut buf_list_buf = vec![0u8; buf_size]; let mut oracle_buf = vec![0u8; buf_size]; let buf_list_res = buf_list.read(&mut buf_list_buf); let oracle_res = oracle.read(&mut oracle_buf); Self::assert_io_result_eq(buf_list_res, oracle_res) .context("operation result didn't match")?; ensure!(buf_list_buf == oracle_buf, "read buffer matches"); } Self::ReadVectored(indexes) => { // Build a bunch of IoSliceMuts. let mut buf_list_vecs: Vec<_> = indexes .into_iter() .map(|index| { // Must initialize the whole vec here so &mut returns the whole buffer -- can't // use with_capacity! let buf_size = index.index(1 + num_bytes); vec![0u8; buf_size] }) .collect(); let mut oracle_vecs = buf_list_vecs.clone(); let mut buf_list_slices: Vec<_> = buf_list_vecs .iter_mut() .map(|v| IoSliceMut::new(v)) .collect(); let mut oracle_slices: Vec<_> = oracle_vecs.iter_mut().map(|v| IoSliceMut::new(v)).collect(); let buf_list_res = buf_list.read_vectored(&mut buf_list_slices); let oracle_res = oracle.read_vectored(&mut oracle_slices); Self::assert_io_result_eq(buf_list_res, oracle_res) .context("operation result didn't match")?; // Also check that the slices read match exactly. ensure!( buf_list_vecs == oracle_vecs, "read vecs didn't match: buf_list: {:?} == oracle: {:?}", buf_list_vecs, oracle_vecs ); } Self::ReadExact(index) => { let buf_size = index.index(1 + num_bytes * 5 / 4); eprintln!("buf_size: {}", buf_size); // Must initialize the whole vec here so &mut returns the whole buffer -- can't use // with_capacity! let mut buf_list_buf = vec![0u8; buf_size]; let mut oracle_buf = vec![0u8; buf_size]; let buf_list_res = buf_list.read_exact(&mut buf_list_buf); let oracle_res = oracle.read_exact(&mut oracle_buf); Self::assert_io_result_eq(buf_list_res, oracle_res) .context("operation result didn't match")?; ensure!(buf_list_buf == oracle_buf, "read buffer matches"); } Self::Consume(index) => { let amt = index.index(1 + num_bytes * 5 / 4); eprintln!("amt: {}", amt); buf_list.consume(amt); oracle.consume(amt); } Self::BufChunk => { eprintln!("buf_chunk"); let buf_list_chunk = buf_list.chunk(); let oracle_chunk = oracle.chunk(); // We can't directly compare chunks because BufList returns one // segment at a time while oracle returns the entire remaining // buffer. Instead, verify that: // // 1. is_empty matches for both chunks. // 2. Both start with the same data (buf_list's chunk is a prefix of oracle's) ensure!( buf_list_chunk.is_empty() == oracle_chunk.is_empty(), "chunk emptiness didn't match: buf_list is_empty {} == oracle is_empty {}", buf_list_chunk.is_empty(), oracle_chunk.is_empty() ); if !buf_list_chunk.is_empty() { // Verify buf_list's chunk is a prefix of oracle's chunk ensure!( oracle_chunk.starts_with(buf_list_chunk), "buf_list chunk is not a prefix of oracle chunk" ); } } Self::BufAdvance(index) => { let amt = index.index(1 + num_bytes * 5 / 4); eprintln!("buf_advance: {}", amt); // Skip if already past the end, as the oracle's Buf impl has a debug assertion // that checks position even when advancing by 0 if buf_list.remaining() > 0 || amt == 0 && oracle.remaining() > 0 { // Cap the advance amount to the remaining bytes to avoid // hitting the debug assertion in std::io::Cursor's Buf // impl. While the Buf trait doesn't require this, the // oracle has a debug_assert that panics if we try to // advance past the end. let amt = amt.min(buf_list.remaining()); buf_list.advance(amt); oracle.advance(amt); } else { eprintln!(" skipping: cursor past end"); } } Self::BufChunksVectored(index) => { let num_iovs = index.index(1 + num_bytes); eprintln!("buf_chunks_vectored: {} iovs", num_iovs); // First verify remaining() matches let buf_list_remaining = buf_list.remaining(); let oracle_remaining = oracle.remaining(); ensure!( buf_list_remaining == oracle_remaining, "chunks_vectored: remaining didn't match before \ calling chunks_vectored: buf_list {} == oracle {}", buf_list_remaining, oracle_remaining ); let mut buf_list_iovs = vec![io::IoSlice::new(&[]); num_iovs]; let mut oracle_iovs = vec![io::IoSlice::new(&[]); num_iovs]; let buf_list_filled = buf_list.chunks_vectored(&mut buf_list_iovs); let oracle_filled = oracle.chunks_vectored(&mut oracle_iovs); // We can't directly compare filled counts or total bytes // because BufList may have multiple chunks while the oracle // (std::io::Cursor) is contiguous. When there are fewer iovs // than chunks, BufList will only fill what it can, while oracle // fills everything into one iov. // // Instead, we verify that: // 1. Both returned at least some data if there are bytes // remaining // 2. The data that was returned matches (buf_list's data is a // prefix of oracle's data) let buf_list_bytes: Vec = buf_list_iovs[..buf_list_filled] .iter() .flat_map(|iov| iov.as_ref().iter().copied()) .collect(); let oracle_bytes: Vec = oracle_iovs[..oracle_filled] .iter() .flat_map(|iov| iov.as_ref().iter().copied()) .collect(); if buf_list_remaining > 0 && num_iovs > 0 { // If there are bytes remaining and iovs available, should // return some data. ensure!( !buf_list_bytes.is_empty(), "chunks_vectored should return some data \ when remaining = {buf_list_remaining} > 0 \ and num_iovs = {num_iovs} > 0" ); ensure!( !oracle_bytes.is_empty(), "oracle chunks_vectored should return some data \ when remaining > 0 and num_iovs > 0" ); // Verify that buf_list's data matches the beginning of // oracle's data. ensure!( oracle_bytes.starts_with(&buf_list_bytes), "buf_list chunks_vectored data should match beginning \ of oracle data" ); // Verify that all iovs up to buf_list_filled are non-empty. for (i, iov) in buf_list_iovs[..buf_list_filled].iter().enumerate() { ensure!( !iov.is_empty(), "buf_list iov at index {i} should be non-empty", ); } } else if buf_list_remaining == 0 { // If no bytes remaining, should return no data ensure!( buf_list_bytes.is_empty() && oracle_bytes.is_empty(), "chunks_vectored should return no data when \ remaining == 0" ); } // If num_iovs == 0, we can't check anything since no iovs were // provided. All we're doing is ensuring that buf_list doesn't // panic. } Self::BufCopyToBytes(index) => { let len = index.index(1 + num_bytes * 5 / 4); eprintln!("buf_copy_to_bytes: {}", len); // copy_to_bytes can panic if len > remaining, so check first let buf_list_remaining = buf_list.remaining(); let oracle_remaining = oracle.remaining(); if len <= buf_list_remaining && len <= oracle_remaining { let buf_list_bytes = buf_list.copy_to_bytes(len); let oracle_bytes = oracle.copy_to_bytes(len); ensure!(buf_list_bytes == oracle_bytes, "copy_to_bytes didn't match"); } else { // Both should panic, so just skip this operation eprintln!(" skipping: len {} > remaining {}", len, buf_list_remaining); } } Self::BufGetU8 => { eprintln!("buf_get_u8"); if buf_list.remaining() >= 1 && oracle.remaining() >= 1 { let buf_list_val = buf_list.get_u8(); let oracle_val = oracle.get_u8(); ensure!( buf_list_val == oracle_val, "get_u8 didn't match: buf_list {} == oracle {}", buf_list_val, oracle_val ); } else { eprintln!(" skipping: not enough bytes remaining"); } } Self::BufGetU64 => { eprintln!("buf_get_u64"); if buf_list.remaining() >= 8 && oracle.remaining() >= 8 { let buf_list_val = buf_list.get_u64(); let oracle_val = oracle.get_u64(); ensure!( buf_list_val == oracle_val, "get_u64 didn't match: buf_list {} == oracle {}", buf_list_val, oracle_val ); } else { eprintln!(" skipping: not enough bytes remaining"); } } Self::BufGetU64Le => { eprintln!("buf_get_u64_le"); if buf_list.remaining() >= 8 && oracle.remaining() >= 8 { let buf_list_val = buf_list.get_u64_le(); let oracle_val = oracle.get_u64_le(); ensure!( buf_list_val == oracle_val, "get_u64_le didn't match: buf_list {} == oracle {}", buf_list_val, oracle_val ); } else { eprintln!(" skipping: not enough bytes remaining"); } } #[cfg(feature = "tokio1")] Self::PollRead { capacity, filled } => { use std::{mem::MaybeUninit, pin::Pin, task::Poll}; use tokio::io::{AsyncRead, ReadBuf}; let capacity = capacity.index(1 + num_bytes * 5 / 4); let mut buf_list_vec = vec![MaybeUninit::uninit(); capacity]; let mut oracle_vec = buf_list_vec.clone(); let mut buf_list_buf = ReadBuf::uninit(&mut buf_list_vec); // Fill up the first bytes of the vector. This uses capacity + 1 so that we can // sometimes fill up the whole buffer. let filled_index = filled.index(capacity + 1); let fill_vec = vec![0u8; filled_index]; buf_list_buf.put_slice(&fill_vec); let mut oracle_buf = ReadBuf::uninit(&mut oracle_vec); oracle_buf.put_slice(&fill_vec); eprintln!("capacity: {}, filled_index: {}", capacity, filled_index); let waker = dummy_waker::dummy_waker(); let mut context = std::task::Context::from_waker(&waker); let mut buf_list_pinned = Pin::new(buf_list); let buf_list_res = match buf_list_pinned .as_mut() .poll_read(&mut context, &mut buf_list_buf) { Poll::Ready(res) => res, Poll::Pending => unreachable!("buf_list never returns pending"), }; let mut oracle_pinned = Pin::new(oracle); let oracle_res = match oracle_pinned .as_mut() .poll_read(&mut context, &mut oracle_buf) { Poll::Ready(res) => res, Poll::Pending => unreachable!("oracle cursor never returns pending"), }; Self::assert_io_result_eq(buf_list_res, oracle_res) .context("result didn't match")?; ensure!( buf_list_buf.filled() == oracle_buf.filled(), "filled section didn't match" ); ensure!( buf_list_buf.remaining() == oracle_buf.remaining(), "remaining count didn't match" ); // Put buf_list and oracle back into their original places. buf_list = buf_list_pinned.get_mut(); oracle = oracle_pinned.get_mut(); } } // Check general properties: remaining and has_remaining are the same. let buf_list_remaining = buf_list.remaining(); let oracle_remaining = oracle.remaining(); ensure!( buf_list_remaining == oracle_remaining, "remaining didn't match: buf_list {} == oracle {}", buf_list_remaining, oracle_remaining ); let buf_list_has_remaining = buf_list.has_remaining(); let oracle_has_remaining = oracle.has_remaining(); ensure!( buf_list_has_remaining == oracle_has_remaining, "has_remaining didn't match: buf_list {} == oracle {}", buf_list_has_remaining, oracle_has_remaining ); // Also check that the position is the same. let buf_list_position = buf_list.position(); ensure!( buf_list_position == oracle.position(), "position didn't match: buf_list position {} == oracle position {}", buf_list_position, oracle.position(), ); Self::assert_io_result_eq(buf_list.stream_position(), oracle.stream_position()) .context("stream position didn't match")?; // Check that fill_buf returns an empty slice iff it is actually empty. let fill_buf = buf_list.fill_buf().expect("fill_buf never errors"); if buf_list_position < num_bytes as u64 { ensure!( !fill_buf.is_empty(), "fill_buf cannot be empty since buf_list.position {} < num_bytes {}", buf_list_position, num_bytes, ); } else { ensure!( fill_buf.is_empty(), "fill_buf must be empty since buf_list.position {} >= num_bytes {}", buf_list_position, num_bytes, ) } // Finally, check that the internal invariants are upheld. buf_list.assert_invariants()?; Ok(()) } fn assert_io_result_eq( buf_list_res: io::Result, oracle_res: io::Result, ) -> Result<()> { match (buf_list_res, oracle_res) { (Ok(buf_list_value), Ok(oracle_value)) => { ensure!( buf_list_value == oracle_value, "value didn't match: buf_list value {:?} == oracle value {:?}", buf_list_value, oracle_value ); } (Ok(buf_list_value), Err(oracle_err)) => { bail!( "BufList value Ok({:?}) is not the same as oracle error Err({})", buf_list_value, oracle_err, ); } (Err(buf_list_err), Ok(oracle_value)) => { bail!( "BufList error ({}) is not the same as oracle value ({:?})", buf_list_err, oracle_value ) } (Err(buf_list_err), Err(oracle_err)) => { // The kinds should match. ensure!( buf_list_err.kind() == oracle_err.kind(), "error kind didn't match: buf_list {:?} == oracle {:?}", buf_list_err.kind(), oracle_err.kind() ); } } Ok(()) } } fn cursor_ops_strategy() -> impl Strategy> { prop::collection::vec(any::(), 0..256) } #[test] fn test_cursor_buf_trait() { // Create a BufList with multiple chunks let mut buf_list = BufList::new(); buf_list.push_chunk(&b"hello "[..]); buf_list.push_chunk(&b"world"[..]); buf_list.push_chunk(&b"!"[..]); let mut cursor = crate::Cursor::new(buf_list.clone()); // Test remaining() assert_eq!(cursor.remaining(), 12); // Test chunk() assert_eq!(cursor.chunk(), b"hello "); // Test advance() cursor.advance(6); assert_eq!(cursor.remaining(), 6); assert_eq!(cursor.chunk(), b"world"); // Advance within the same chunk cursor.advance(3); assert_eq!(cursor.remaining(), 3); assert_eq!(cursor.chunk(), b"ld"); // Advance to the next chunk cursor.advance(2); assert_eq!(cursor.remaining(), 1); assert_eq!(cursor.chunk(), b"!"); // Advance to the end cursor.advance(1); assert_eq!(cursor.remaining(), 0); assert_eq!(cursor.chunk(), b""); // Test chunks_vectored let mut cursor = crate::Cursor::new(buf_list.clone()); let mut iovs = [io::IoSlice::new(&[]); 3]; let filled = cursor.chunks_vectored(&mut iovs); assert_eq!(filled, 3); assert_eq!(iovs[0].as_ref(), b"hello "); assert_eq!(iovs[1].as_ref(), b"world"); assert_eq!(iovs[2].as_ref(), b"!"); // Test chunks_vectored after advancing cursor.advance(6); let mut iovs = [io::IoSlice::new(&[]); 3]; let filled = cursor.chunks_vectored(&mut iovs); assert_eq!(filled, 2); assert_eq!(iovs[0].as_ref(), b"world"); assert_eq!(iovs[1].as_ref(), b"!"); // Test chunks_vectored with more iovs than remaining chunks let cursor2 = crate::Cursor::new(&buf_list); let mut iovs2 = [io::IoSlice::new(&[]); 10]; let filled2 = cursor2.chunks_vectored(&mut iovs2); assert_eq!(filled2, 3, "Should only fill 3 iovs for 3 chunks"); let total_bytes: usize = iovs2[..filled2].iter().map(|iov| iov.len()).sum(); assert_eq!(total_bytes, 12, "Total bytes should be 12"); } buf-list-1.1.2/src/cursor/tokio_imp.rs000064400000000000000000000046221046102023000160220ustar 00000000000000// Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 use super::CursorData; use crate::{BufList, Cursor}; use std::{ io::{self, SeekFrom}, pin::Pin, task::{Context, Poll}, }; use tokio::io::{AsyncBufRead, AsyncRead, AsyncSeek, ReadBuf}; impl + Unpin> AsyncSeek for Cursor { fn start_seek(mut self: Pin<&mut Self>, pos: SeekFrom) -> io::Result<()> { io::Seek::seek(&mut *self, pos).map(drop) } fn poll_complete(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(self.get_mut().position())) } } impl + Unpin> AsyncRead for Cursor { fn poll_read( mut self: Pin<&mut Self>, _: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { let this = &mut *self; this.data.tokio_poll_read_impl(this.inner.as_ref(), buf) } } impl + Unpin> AsyncBufRead for Cursor { fn poll_fill_buf(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(io::BufRead::fill_buf(self.get_mut())) } fn consume(mut self: Pin<&mut Self>, amt: usize) { io::BufRead::consume(&mut *self, amt) } } impl CursorData { fn tokio_poll_read_impl( &mut self, list: &BufList, buf: &mut ReadBuf<'_>, ) -> Poll> { // This is really similar to Self::read_impl, except it's written against the ReadBuf API. while buf.remaining() > 0 { let (chunk, chunk_pos) = match self.get_chunk_and_pos(list) { Some(value) => value, None => break, }; // The number of bytes to copy is the smaller of the two: // - the length of the chunk - the position in it. // - the number of bytes remaining. let n_to_copy = (chunk.len() - chunk_pos).min(buf.remaining()); let chunk_bytes = chunk.as_ref(); let bytes_to_copy = &chunk_bytes[chunk_pos..(chunk_pos + n_to_copy)]; buf.put_slice(bytes_to_copy); // Increment the position. self.pos += n_to_copy as u64; // If we've finished reading through the chunk, move to the next chunk. if n_to_copy == chunk.len() - chunk_pos { self.chunk += 1; } } Poll::Ready(Ok(())) } } buf-list-1.1.2/src/errors.rs000064400000000000000000000016071046102023000140270ustar 00000000000000// Copyright (c) 2018 the linkerd2-proxy authors // Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 //! Error types returned by buf-list. use std::{error, fmt}; /// An error returned if `read_exact` was called on a [`Cursor`](crate::Cursor) that doesn't have /// enough bytes remaining. /// /// This is private because `read_exact_impl` returns an `io::Error`. #[derive(Debug)] pub(crate) struct ReadExactError { pub(crate) remaining: u64, pub(crate) buf_len: usize, } impl error::Error for ReadExactError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { None } } impl fmt::Display for ReadExactError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Cursor had {} bytes remaining but buffer was {} bytes long", self.remaining, self.buf_len ) } } buf-list-1.1.2/src/imp.rs000064400000000000000000000247771046102023000133150ustar 00000000000000// Copyright (c) 2018 the linkerd2-proxy authors // Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 use bytes::{Buf, BufMut, Bytes, BytesMut}; use std::{ collections::VecDeque, io::IoSlice, iter::{FromIterator, FusedIterator}, sync::OnceLock, }; /// Data composed of a list of [`Bytes`] chunks. /// /// For more, see the [crate documentation](crate). #[derive(Clone, Debug, Default)] pub struct BufList { // Invariant: none of the bufs in this queue are zero-length. bufs: VecDeque, /// An index of chunks and their start positions. There's an additional index at the end, which /// is the length of the list (list.num_bytes()). start_pos: OnceLock>, } impl BufList { /// Creates a new, empty, `BufList`. #[inline] pub fn new() -> Self { Self::default() } #[inline] pub(crate) fn get_start_pos(&self) -> &[u64] { self.start_pos.get_or_init(|| { let mut start_pos = Vec::with_capacity(self.bufs.len() + 1); let mut next = 0u64; for chunk in self.bufs.iter() { start_pos.push(next); next += chunk.len() as u64; } // Add the length of the chunk at the end. start_pos.push(next); start_pos.into_boxed_slice() }) } /// Creates a new, empty, `BufList` with the given capacity. #[inline] pub fn with_capacity(capacity: usize) -> Self { Self { bufs: VecDeque::with_capacity(capacity), start_pos: OnceLock::new(), } } /// Returns the total number of chunks in this `BufList`. /// /// # Examples /// /// ``` /// use buf_list::BufList; /// /// let buf_list = vec![&b"hello"[..], &b"world"[..]].into_iter().collect::(); /// assert_eq!(buf_list.num_chunks(), 2); /// ``` #[inline] pub fn num_chunks(&self) -> usize { self.bufs.len() } /// Returns the total number of bytes across all chunks. /// /// # Examples /// /// ``` /// use buf_list::BufList; /// /// let buf_list = vec![&b"hello"[..], &b"world"[..]].into_iter().collect::(); /// assert_eq!(buf_list.num_bytes(), 10); /// ``` #[inline] pub fn num_bytes(&self) -> usize { self.remaining() } /// Provides a reference to the chunk at the given index. /// /// # Examples /// /// ``` /// use buf_list::BufList; /// use bytes::Bytes; /// /// let buf_list = vec![&b"hello"[..], &b"world"[..]].into_iter().collect::(); /// assert_eq!(buf_list.get_chunk(1), Some(&Bytes::from(&b"world"[..]))); /// ``` #[inline] pub fn get_chunk(&self, index: usize) -> Option<&Bytes> { self.bufs.get(index) } /// Iterates over the chunks in this list. #[inline] pub fn iter(&self) -> Iter<'_> { Iter { iter: self.bufs.iter(), } } /// Adds a new chunk to this list. /// /// If the provided [`Buf`] is zero-length, it will not be added to the list. /// /// # Examples /// /// ``` /// use buf_list::BufList; /// use bytes::{Buf, Bytes}; /// /// let mut buf_list = BufList::new(); /// /// // &'static [u8] implements Buf. /// buf_list.push_chunk(&b"hello"[..]); /// assert_eq!(buf_list.chunk(), &b"hello"[..]); /// /// // Bytes also implements Buf. /// buf_list.push_chunk(Bytes::from_static(&b"world"[..])); /// assert_eq!(buf_list.num_chunks(), 2); /// /// // A zero-length `Buf` will not be added to the list. /// buf_list.push_chunk(Bytes::new()); /// assert_eq!(buf_list.num_chunks(), 2); /// ``` pub fn push_chunk(&mut self, mut data: B) -> Bytes { // mutable borrow acquired, invalidate the OnceLock self.start_pos = OnceLock::new(); let len = data.remaining(); // `data` is (almost) certainly a `Bytes`, so `copy_to_bytes` should // internally be a cheap refcount bump almost all of the time. // But, if it isn't, this will copy it to a `Bytes` that we can // now clone. let bytes = data.copy_to_bytes(len); // Buffer a clone. Don't push zero-length bufs to uphold the invariant. if len > 0 { self.bufs.push_back(bytes.clone()); } // Return the bytes bytes } } impl Extend for BufList { fn extend>(&mut self, iter: T) { // mutable borrow acquired, invalidate the OnceLock self.start_pos = OnceLock::new(); for buf in iter.into_iter() { self.push_chunk(buf); } } } impl FromIterator for BufList { fn from_iter>(iter: T) -> Self { let mut buf_list = BufList::new(); for buf in iter.into_iter() { buf_list.push_chunk(buf); } buf_list } } impl IntoIterator for BufList { type Item = Bytes; type IntoIter = IntoIter; #[inline] fn into_iter(self) -> Self::IntoIter { IntoIter { iter: self.bufs.into_iter(), } } } impl<'a> IntoIterator for &'a BufList { type Item = &'a Bytes; type IntoIter = Iter<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() } } impl AsRef for BufList { fn as_ref(&self) -> &BufList { self } } impl Buf for BufList { fn remaining(&self) -> usize { self.bufs.iter().map(Buf::remaining).sum() } fn chunk(&self) -> &[u8] { self.bufs.front().map(Buf::chunk).unwrap_or(&[]) } fn chunks_vectored<'iovs>(&'iovs self, iovs: &mut [IoSlice<'iovs>]) -> usize { // Are there more than zero iovecs to write to? if iovs.is_empty() { return 0; } let to_fill = (iovs.len()).min(self.bufs.len()); for (i, iov) in iovs.iter_mut().enumerate().take(to_fill) { *iov = IoSlice::new(&self.bufs[i]); } to_fill } fn advance(&mut self, mut amt: usize) { // mutable borrow acquired, invalidate the OnceLock self.start_pos = OnceLock::new(); while amt > 0 { let rem = self.bufs[0].remaining(); // If the amount to advance by is less than the first buffer in // the buffer list, advance that buffer's cursor by `amt`, // and we're done. if rem > amt { self.bufs[0].advance(amt); return; } // Otherwise, advance the first buffer to its end, and // continue. self.bufs[0].advance(rem); amt -= rem; self.bufs.pop_front(); } } fn copy_to_bytes(&mut self, len: usize) -> Bytes { // mutable borrow acquired, invalidate the OnceLock self.start_pos = OnceLock::new(); // If the length of the requested `Bytes` is <= the length of the front // buffer, we can just use its `copy_to_bytes` implementation (which is // just a reference count bump). match self.bufs.front_mut() { Some(first) if len <= first.remaining() => { let buf = first.copy_to_bytes(len); // If we consumed the first buffer, also advance our "cursor" by // popping it. if first.remaining() == 0 { self.bufs.pop_front(); } buf } _ => { assert!( len <= self.remaining(), "`len` ({}) greater than remaining ({})", len, self.remaining() ); let mut buf = BytesMut::with_capacity(len); buf.put(self.take(len)); buf.freeze() } } } } impl> From for BufList { fn from(value: T) -> Self { let mut buf_list = BufList::with_capacity(1); buf_list.push_chunk(value.into()); buf_list } } /// An owned iterator over chunks in a [`BufList`]. /// /// Returned by the [`IntoIterator`] implementation for [`BufList`]. #[derive(Clone, Debug)] pub struct IntoIter { iter: std::collections::vec_deque::IntoIter, } impl Iterator for IntoIter { type Item = Bytes; #[inline] fn next(&mut self) -> Option { self.iter.next() } #[inline] fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl DoubleEndedIterator for IntoIter { #[inline] fn next_back(&mut self) -> Option { self.iter.next_back() } } impl ExactSizeIterator for IntoIter { #[inline] fn len(&self) -> usize { self.iter.len() } } impl FusedIterator for IntoIter {} /// A borrowed iterator over chunks in a [`BufList`]. /// /// Returned by [`BufList::iter`], and by the [`IntoIterator`] implementation for `&'a BufList`. #[derive(Clone, Debug)] pub struct Iter<'a> { iter: std::collections::vec_deque::Iter<'a, Bytes>, } impl<'a> Iterator for Iter<'a> { type Item = &'a Bytes; #[inline] fn next(&mut self) -> Option { self.iter.next() } // These methods are implemented manually to forward to the underlying // iterator. #[inline] fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } // fold has a special implementation, so forward it. #[inline] fn fold(self, init: B, f: F) -> B where Self: Sized, F: FnMut(B, Self::Item) -> B, { self.iter.fold(init, f) } // Can't implement try_fold as it uses `std::ops::Try` which isn't stable yet, as of Rust 1.67 #[inline] fn nth(&mut self, n: usize) -> Option { self.iter.nth(n) } #[inline] fn last(self) -> Option where Self: Sized, { self.iter.last() } } impl<'a> DoubleEndedIterator for Iter<'a> { #[inline] fn next_back(&mut self) -> Option { self.iter.next_back() } #[inline] fn rfold(self, init: B, f: F) -> B where Self: Sized, F: FnMut(B, Self::Item) -> B, { self.iter.rfold(init, f) } // Can't implement try_rfold as it uses `std::ops::Try` which isn't stable yet, as of Rust 1.67. } impl<'a> ExactSizeIterator for Iter<'a> { #[inline] fn len(&self) -> usize { self.iter.len() } } impl<'a> FusedIterator for Iter<'a> {} buf-list-1.1.2/src/lib.rs000064400000000000000000000107221046102023000132570ustar 00000000000000// Copyright (c) 2018 the linkerd2-proxy authors // Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 #![forbid(unsafe_code)] #![warn(missing_docs)] #![cfg_attr(doc_cfg, feature(doc_cfg))] //! A segmented list of [`bytes::Bytes`] chunks. //! //! # Overview //! //! This crate provides a [`BufList`] type that is a list of [`Bytes`](bytes::Bytes) chunks. The //! type implements [`bytes::Buf`], so it can be used in any APIs that use `Buf`. //! //! The main use case for [`BufList`] is to buffer data received as a stream of chunks without //! having to copy them into a single contiguous chunk of memory. The [`BufList`] can then be passed //! into any APIs that accept `Buf`. //! //! If you've ever wanted a `Vec` or a `VecDeque`, this type is for you. //! //! # Cursors //! //! This crate also provides [`Cursor`], which is a cursor type around a //! [`BufList`]. Similar to similar to [`std::io::Cursor`], a [`Cursor`] around //! a [`BufList`] implements //! //! * [`Seek`](std::io::Seek), [`Read`](std::io::Read), and [`BufRead`](std::io::BufRead) //! * [`bytes::Buf`] as well (in other words, both [`BufList`]s and [`Cursor`]s over them //! can be passed into any APIs that accept `Buf`). //! //! # Examples //! //! Gather chunks into a `BufList`, then write them all out to standard error in one go: //! //! ``` //! use buf_list::BufList; //! use tokio::io::AsyncWriteExt; //! //! # #[tokio::main(flavor = "current_thread")] //! # async fn main() -> Result<(), std::io::Error> { //! let mut buf_list = BufList::new(); //! buf_list.push_chunk(&b"hello "[..]); //! buf_list.push_chunk(&b"world"[..]); //! buf_list.push_chunk(&b"!"[..]); //! //! let mut stderr = tokio::io::stderr(); //! stderr.write_all_buf(&mut buf_list).await?; //! # Ok(()) } //! ``` //! //! Collect a fallible stream of `Bytes` into a `BufList`: //! //! ``` //! use buf_list::BufList; //! use bytes::Bytes; //! use futures::TryStreamExt; //! //! # #[tokio::main(flavor = "current_thread")] //! # async fn main() -> Result<(), ()> { //! // A common example is a stream of bytes read over HTTP. //! let stream = futures::stream::iter( //! vec![ //! Ok(Bytes::from_static(&b"laputa, "[..])), //! Ok(Bytes::from_static(&b"castle "[..])), //! Ok(Bytes::from_static(&b"in the sky"[..])) //! ], //! ); //! //! let buf_list = stream.try_collect::().await?; //! assert_eq!(buf_list.num_chunks(), 3); //! # Ok(()) } //! ``` //! //! ## Converting to `Stream`s //! //! A `BufList` can be converted into a `futures::Stream`, or a `TryStream`, of `Bytes` chunks. Use //! this recipe to do so: //! //! (This will be exposed as an API on `BufList` once `Stream` and/or `TryStream` become part of //! stable Rust.) //! //! ```rust //! use buf_list::BufList; //! use bytes::Bytes; //! use futures::{Stream, TryStream}; //! //! fn into_stream(buf_list: BufList) -> impl Stream { //! futures::stream::iter(buf_list) //! } //! //! fn into_try_stream(buf_list: BufList) -> impl TryStream { //! futures::stream::iter(buf_list.into_iter().map(Ok)) //! } //! ``` //! //! # Optional features //! //! * `tokio1`: With this feature enabled, [`Cursor`] implements the `tokio` crate's //! [`AsyncSeek`](tokio::io::AsyncSeek), [`AsyncRead`](tokio::io::AsyncRead) and //! [`AsyncBufRead`](tokio::io::AsyncBufRead). //! //! * `futures03`: With this feature enabled, [`Cursor`] implements the `futures` crate's //! [`AsyncSeek`](futures_io_03::AsyncSeek), [`AsyncRead`](futures_io_03::AsyncRead) and //! [`AsyncBufRead`](futures_io_03::AsyncBufRead). //! //! Note that supporting `futures03` means exporting 0.x types as a public interface. **This //! violates the //! [C-STABLE](https://rust-lang.github.io/api-guidelines/necessities.html#public-dependencies-of-a-stable-crate-are-stable-c-stable) //! guideline.** However, the maintainer of `buf-list` considers that acceptable since `futures03` //! is an optional feature and not critical to `buf-list`. As newer versions of the `futures` //! crate are released, `buf-list` will support their versions of the async traits as well. //! //! # Minimum supported Rust version //! //! The minimum supported Rust version (MSRV) is **1.70**. Optional features may //! cause a bump in the MSRV. //! //! `buf-list` has a conservative MSRV policy. MSRV bumps will be sparing, and //! if so, they will be accompanied by a minor version bump. mod cursor; pub(crate) mod errors; mod imp; pub use cursor::*; pub use imp::*; buf-list-1.1.2/tests/tests.rs000064400000000000000000000141731046102023000142320ustar 00000000000000// Copyright (c) The buf-list Contributors // SPDX-License-Identifier: Apache-2.0 use buf_list::BufList; use bytes::{Buf, Bytes}; use std::{io::IoSlice, ops::Deref}; #[test] fn test_basic() { let mut buf_list = vec![&b"hello"[..], &b"world"[..]] .into_iter() .collect::(); println!("{:?}", buf_list); assert_eq!(buf_list.num_bytes(), 10); assert_eq!(buf_list.num_chunks(), 2); let chunk = buf_list.push_chunk(&b"foo"[..]); assert_eq!(buf_list.num_bytes(), 13); assert_eq!(buf_list.num_chunks(), 3); assert_eq!(chunk, &b"foo"[..]); // Try inserting a zero-length chunk. This should not count as a chunk. buf_list.push_chunk(&[] as &[u8]); assert_eq!(buf_list.num_bytes(), 13); assert_eq!(buf_list.num_chunks(), 3); { let mut buf_list = buf_list.clone(); assert_eq!(buf_list.chunk(), &b"hello"[..]); // Advance by 2. This won't consume the first chunk. buf_list.advance(2); assert_eq!(buf_list.num_bytes(), 11); assert_eq!(buf_list.num_chunks(), 3); assert_eq!(buf_list.chunk(), &b"llo"[..]); // Advance by 3. This will consume the "hello" chunk exactly. buf_list.advance(3); assert_eq!(buf_list.num_bytes(), 8); assert_eq!(buf_list.num_chunks(), 2); assert_eq!(buf_list.chunk(), &b"world"[..]); // Advance by 6. This will consume the "world" chunk + the first byte of "foo". buf_list.advance(6); assert_eq!(buf_list.num_bytes(), 2); assert_eq!(buf_list.num_chunks(), 1); assert_eq!(buf_list.chunk(), &b"oo"[..]); // Advance by 2. This will consume the "foo" chunk exactly. buf_list.advance(2); assert_eq!(buf_list.num_bytes(), 0); assert_eq!(buf_list.num_chunks(), 0); assert_eq!(buf_list.chunk(), &[] as &[u8]); } { // Test chunks_vectored. let mut chunks_vectored = vec![IoSlice::new(&[]); 5]; let ret = buf_list.chunks_vectored(&mut chunks_vectored); assert_eq!(ret, 3); assert_eq!(chunks_vectored[0].deref(), &b"hello"[..]); assert_eq!(chunks_vectored[1].deref(), &b"world"[..]); assert_eq!(chunks_vectored[2].deref(), &b"foo"[..]); assert_eq!(chunks_vectored[3].deref(), &[] as &[u8]); // Test chunks_vectored with a smaller buffer. let mut chunks_vectored = vec![IoSlice::new(&[]); 2]; let ret = buf_list.chunks_vectored(&mut chunks_vectored); assert_eq!(ret, 2); assert_eq!(chunks_vectored[0].deref(), &b"hello"[..]); assert_eq!(chunks_vectored[1].deref(), &b"world"[..]); // Test with an empty buffer. let mut chunks_vectored = vec![]; let ret = buf_list.chunks_vectored(&mut chunks_vectored); assert_eq!(ret, 0); } { // Test copy_to_bytes. let mut buf_list = buf_list.clone(); // Copy the first two bytes -- this should just be a refcount. let bytes = buf_list.copy_to_bytes(2); assert_eq!(bytes, &b"he"[..]); assert_eq!(buf_list.num_bytes(), 11); assert_eq!(buf_list.num_chunks(), 3); // Copy the next 3 bytes. This should consume the buffer. let bytes = buf_list.copy_to_bytes(3); assert_eq!(bytes, &b"llo"[..]); assert_eq!(buf_list.num_bytes(), 8); assert_eq!(buf_list.num_chunks(), 2); // Copy 6 bytes. This should consume the next buffer. let bytes = buf_list.copy_to_bytes(6); assert_eq!(bytes, &b"worldf"[..]); assert_eq!(buf_list.num_bytes(), 2); assert_eq!(buf_list.num_chunks(), 1); // Copy the last 2 bytes + this next buffer -- ensure that all buffers are consumed. buf_list.push_chunk(&b"bar"[..]); let bytes = buf_list.copy_to_bytes(5); assert_eq!(bytes, &b"oobar"[..]); assert_eq!(buf_list.num_bytes(), 0); assert_eq!(buf_list.num_chunks(), 0); } } #[test] fn test_iter() { let buf_list = vec![&b"hello"[..], &b"world"[..], &b"foo"[..], &b"bar"[..]] .into_iter() .collect::(); // Test iteration over the buf_list. let mut iter = (&buf_list).into_iter(); println!("{:?}", iter); assert_eq!(iter.next(), Some(&Bytes::from_static(&b"hello"[..]))); assert_eq!(iter.size_hint(), (3, Some(3))); assert_eq!(iter.len(), 3); { let mut iter = iter.clone(); #[allow(clippy::iter_nth_zero)] { assert_eq!(iter.nth(0), Some(&Bytes::from_static(&b"world"[..]))); } assert_eq!(iter.last(), Some(&Bytes::from_static(&b"bar"[..]))); } { let iter = iter.clone(); let v = iter.fold(vec![], |mut v, item| { v.push(item); v }); assert_eq!( v, vec![ &Bytes::from_static(&b"world"[..]), &Bytes::from_static(&b"foo"[..]), &Bytes::from_static(&b"bar"[..]), ] ); } { let mut iter = iter.clone(); assert_eq!(iter.next_back(), Some(&Bytes::from_static(&b"bar"[..]))); assert_eq!( iter.rfold(vec![], |mut v, item| { v.push(item); v }), vec![ &Bytes::from_static(&b"foo"[..]), &Bytes::from_static(&b"world"[..]), ] ); } } #[test] fn test_into_iter() { let buf_list = vec![&b"hello"[..], &b"world"[..], &b"foo"[..], &b"bar"[..]] .into_iter() .collect::(); let into_iter = buf_list.into_iter(); println!("{:?}", into_iter); #[allow(clippy::redundant_clone)] let mut into_iter = into_iter.clone(); assert_eq!(into_iter.next(), Some(Bytes::from_static(&b"hello"[..]))); assert_eq!(into_iter.size_hint(), (3, Some(3))); assert_eq!(into_iter.len(), 3); assert_eq!(into_iter.next_back(), Some(Bytes::from_static(&b"bar"[..]))); } #[test] #[should_panic = "`len` (12) greater than remaining (10)"] fn test_copy_to_bytes_panic() { let mut buf_list = vec![&b"hello"[..], &b"world"[..]] .into_iter() .collect::(); buf_list.copy_to_bytes(12); }