pax_global_header00006660000000000000000000000064146306667350014532gustar00rootroot0000000000000052 comment=8bd5d409278c6a31cd3ec1101064093732c79df2 rust-yamux-yamux-v0.13.3/000077500000000000000000000000001463066673500153035ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/.editorconfig000066400000000000000000000001441463066673500177570ustar00rootroot00000000000000root = true [*] charset=utf-8 end_of_line=lf indent_size=4 indent_style=space max_line_length=100 rust-yamux-yamux-v0.13.3/.github/000077500000000000000000000000001463066673500166435ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/.github/dependabot.yml000066400000000000000000000003161463066673500214730ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "daily" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" rust-yamux-yamux-v0.13.3/.github/workflows/000077500000000000000000000000001463066673500207005ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/.github/workflows/rust.yml000066400000000000000000000007001463066673500224150ustar00rootroot00000000000000name: Rust on: push: branches: [ develop, master ] pull_request: branches: [ develop, master ] env: CARGO_TERM_COLOR: always RUSTFLAGS: '-Dwarnings' # Never tolerate warnings. jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check formatting run: cargo fmt -- --check - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose rust-yamux-yamux-v0.13.3/.github/workflows/stale.yml000066400000000000000000000016311463066673500225340ustar00rootroot00000000000000name: Close and mark stale issue on: schedule: - cron: '0 0 * * *' jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@v7 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'Oops, seems like we needed more information for this issue, please comment with more details or this issue will be closed in 7 days.' close-issue-message: 'This issue was closed because it is missing author input.' stale-issue-label: 'kind/stale' any-of-labels: 'need/author-input' exempt-issue-labels: 'need/triage,need/community-input,need/maintainer-input,need/maintainers-input,need/analysis,status/blocked,status/in-progress,status/ready,status/deferred,status/inactive' days-before-issue-stale: 6 days-before-issue-close: 7 enable-statistics: true rust-yamux-yamux-v0.13.3/.gitignore000066400000000000000000000000501463066673500172660ustar00rootroot00000000000000/target /Cargo.lock *.swp *.swo tags ID rust-yamux-yamux-v0.13.3/CHANGELOG.md000066400000000000000000000237071463066673500171250ustar00rootroot00000000000000# 0.13.3 - Wake up readers after setting the state to RecvClosed to not miss EOF. See [PR 190](https://github.com/libp2p/rust-yamux/pull/190). - Use `web-time` instead of `instant`. See [PR 191](https://github.com/libp2p/rust-yamux/pull/191). # 0.13.2 - Bound `Active`'s `pending_frames` to enforce backpressure. See [460baf2](https://github.com/libp2p/rust-yamux/commit/460baf2ccb7d5982b266cb3cb9c0bdf75b4fb779) # 0.13.1 - Fix WASM support using `instant::{Duration, Instant}` instead of `std::time::{Duration, Instant}`. See [PR 179](https://github.com/libp2p/rust-yamux/pull/179). # 0.13.0 - Introduce dynamic stream receive window auto-tuning. While low-resourced deployments maintain the benefit of small buffers, high resource deployments eventually end-up with a window of roughly the bandwidth-delay-product (ideal) and are thus able to use the entire available bandwidth. See [PR 176](https://github.com/libp2p/rust-yamux/pull/176) for performance results and details on the implementation. - Remove `WindowUpdateMode`. Behavior will always be `WindowUpdateMode::OnRead`, thus enabling flow-control and enforcing backpressure. See [PR 178](https://github.com/libp2p/rust-yamux/pull/178). # 0.12.1 - Deprecate `WindowUpdateMode::OnReceive`. It does not enforce flow-control, i.e. breaks backpressure. Use `WindowUpdateMode::OnRead` instead. See [PR #177](https://github.com/libp2p/rust-yamux/pull/177). # 0.12.0 - Remove `Control` and `ControlledConnection`. Users have to move to the `poll_` functions of `Connection`. See [PR #164](https://github.com/libp2p/rust-yamux/pull/164). - Fix a bug where `Stream`s would not be dropped until their corresponding `Connection` was dropped. See [PR #167](https://github.com/libp2p/rust-yamux/pull/167). # 0.11.1 - Avoid race condition between pending frames and closing stream. See [PR 156]. [PR 156]: https://github.com/libp2p/rust-yamux/pull/156 # 0.11.0 - Remove `Connection::control` in favor of `Control::new`. Remove `Connection::next_stream` in favor of `Connection::poll_next_inbound`. See [PR 142]. [PR 142]: https://github.com/libp2p/rust-yamux/pull/142 # 0.10.2 - Process command or socket result immediately and thereby no longer accessing the socket after it returned an error. See [PR 138] for details. [PR 138]: https://github.com/libp2p/rust-yamux/pull/138 # 0.10.1 - Update `parking_lot` dependency. See [PR 126]. - Flush socket while waiting for next frame. See [PR 130]. [PR 126]: https://github.com/libp2p/rust-yamux/pull/126 [PR 130]: https://github.com/libp2p/rust-yamux/pull/130 # 0.10.0 - Default to `WindowUpdateMode::OnRead`, thus enabling full Yamux flow-control, exercising back pressure on senders, preventing stream resets due to reaching the buffer limit. See the [`WindowUpdateMode` documentation] for details, especially the section on deadlocking when sending data larger than the receivers window. [`WindowUpdateMode` documentation]: https://docs.rs/yamux/0.9.0/yamux/enum.WindowUpdateMode.html # 0.9.0 - Force-split larger frames, for better interleaving of reads and writes between different substreams and to avoid single, large writes. By default frames are capped at, and thus split at, `16KiB`, which can be adjusted by a new configuration option, if necessary. - Send window updates earlier, when half of the window has been consumed, to minimise pauses due to transmission delays, particularly if there is just a single dominant substream. - Avoid possible premature stream resets of streams that have been properly closed and already dropped but receive window update or other frames while the remaining buffered frames are still sent out. Incoming frames for unknown streams are now ignored, instead of triggering a stream reset for the remote. # 0.8.1 - Avoid possible premature stream resets of streams that have been properly closed and already dropped but receive window update or other frames while the remaining buffered frames are still sent out. Incoming frames for unknown streams are now ignored, instead of triggering a stream reset for the remote. # 0.8.0 - Upgrade step 4 of 4. This version always assumes the new semantics and no longer sets the non-standard flag in intial window updates. - The configuration option `lazy_open` is removed. Initial window updates are sent automatically if the receive window is configured to be larger than the default. # 0.7.0 Upgrade step 3 of 4. This version sets the non-standard flag, but irrespective of whether it is present or not, always assumes the new additive semantics of the intial window update. # 0.6.0 Upgrade step 2 of 4. This version sets the non-standard flag, version 0.5.0 already recognises. # 0.5.0 This version begins the upgrade process spawning multiple versions that changes the meaning of the initial window update from *"This is the total size of the receive window."* to *"This is the size of the receive window in addition to the default size."* This is necessary for compatibility with other yamux implementations. See issue #92 for details. As a first step, version 0.5.0 interprets a non-standard flag to imply the new meaning. Future versions will set this flag and eventually the new meaning will always be assumed. Upgrading from the current implemention to the new semantics requires deployment of every intermediate version, each of which is only compatible with its immediate predecessor. Alternatively, if the default configuration together with `lazy_open` set to `true` is deployed on all communicating endpoints, one can skip directly to the end of the transition. # 0.4.9 - Bugfixes (#93). # 0.4.8 - Bugfixes (#91). - Improve documentation (#88). # 0.4.7 - Bugfix release (#85). # 0.4.6 - Send RST frame if the window of a dropped stream is 0 and it is in state `SendClosed` (#84). # 0.4.5 - Removed `bytes` (#77) and `thiserror` (#78) dependencies. - Removed implicit `BufWriter` creation (#77). Client code that depends on this (undocumented) behaviour needs to wrap the socket in a `BufWriter` before passing it to `Connection::new`. - Added `Connection::is_closed` flag (#80) to immediately return `Ok(None)` from `Connection::next_stream` after `Err(_)` or `Ok(None)` have been returned previously. # 0.4.4 - Control and stream command channels are now closed and drained immediately on error. This is done to prevent client code from submitting further close or other commands which will never be acted upon since the API contract of `Connection::next_stream` is that after `None` or an `Err(_)` is returned it must not be called again. # 0.4.3 - Updates nohash-hasher dependency to v0.2.0. # 0.4.2 - A new configuration option `lazy_open` (off by default) has been added and inbound streams are now acknowledged (#73). If `lazy_open` is set to `true` we will not immediately send an initial `WindowUpdate` frame but instead just set the `SYN` flag on the first outbound `Data` frame. See `Configuration::set_lazy_open` for details. # 0.4.1 - Log connection reset errors on debug level (#72). # 0.4.0 - Hide `StreamId::new` and update dependencies. # 0.3.0 Update to use and work with async/await: - `Config::set_max_pending_frames` has been removed. Internal back-pressure made the setting unnecessary. As another consequence the error `ConnectionError::TooManyPendingFrames` has been removed. - `Connection` no longer has methods to open a new stream or to close the connection. Instead a separate handle type `Control` has been added which allows these operations concurrently to the connection itself. - In Yamux 0.2.x every `StreamHandle` I/O operation would drive the `Connection`. Now, the only way the `Connection` makes progress is through its `next_stream` method which must be called continuously. For convenience a function `into_stream` has been added which turns the `Connection` into a `futures::stream::Stream` impl, invoking `next_stream` in its `poll_next` method. - `StreamHandle` has been renamed to `Stream` and its methods `credit` and `state` have been removed. - `Stream` also implements `futures::stream::Stream` and produces `Packet`s. - `ConnectionError::StreamNotFound` has been removed. Incoming frames for unknown streams are answered with a RESET frame, unless they finish the stream. - `DecodeError` has been renamed to `FrameDecodeError` and `DecodeError::Type` corresponds to `FramedDecodeError::Header` which handles not just unknown frame type errors, but more. Hence a new error `HeaderDecodeError` has been added for those error cases. # 0.2.2 - Updated dependencies (#56). # 0.2.1 - Bugfix release (pull request #54). # 0.2.0 - Added `max_pending_frames` setting to `Config`. A `Connection` buffers outgoing frames up to this limit (see pull request #51). - Added `ConnectionError::TooManyPendingFrames` if `max_pending_frames` has been reached. - Changed error types of `Connection::close` and `Connection::flush` from `std::io::Error` to `yamux::ConnectionError`. - Removed `Connection::shutdown` method which was deprecated since version 0.1.8. # 0.1.9 - Add `read_after_close` setting to `Config` which defaults to `true` to match the behaviour of previous versions. Setting `read_after_close` to `false` will cause stream reads to return with `Ok(0)` as soon as the connection is closed, preventing them from reading data from their buffer. # 0.1.8 - Mark `Connection::shutdown` as deprecated (#44). # 0.1.7 - Bugfix release (#36). - Support for half-closed streams (#38). - Avoids redundant RESET frames (#37). - Better test coverage (#40, #42). # 0.1.6 - Bugfix release (pull requests #34 and #35). # 0.1.5 - Bugfix release (pull request #33). # 0.1.4 - Bugfix release (pull requests #30 and #31). # 0.1.3 - Bugfix release (pull requests #27 and #28). # 0.1.2 - Bugfix release. See pull request #26 for details. # 0.1.1 - Forward `Stream::poll` to the newly added `Connection::poll` method which accepts `self` as a shared reference. See pull request #24 for details. # 0.1 - Initial release. rust-yamux-yamux-v0.13.3/Cargo.toml000066400000000000000000000001211463066673500172250ustar00rootroot00000000000000[workspace] members = ["yamux", "test-harness", "quickcheck-ext"] resolver = "2" rust-yamux-yamux-v0.13.3/LICENSE-APACHE000066400000000000000000000261361463066673500172370ustar00rootroot00000000000000 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. rust-yamux-yamux-v0.13.3/LICENSE-MIT000066400000000000000000000020551463066673500167410ustar00rootroot00000000000000Copyright 2018 Parity Technologies (UK) Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rust-yamux-yamux-v0.13.3/README.md000066400000000000000000000012121463066673500165560ustar00rootroot00000000000000# Yamux A stream multiplexer over reliable, ordered connections such as TCP/IP. Implements https://github.com/hashicorp/yamux/blob/master/spec.md ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. rust-yamux-yamux-v0.13.3/quickcheck-ext/000077500000000000000000000000001463066673500202135ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/quickcheck-ext/Cargo.toml000066400000000000000000000003171463066673500221440ustar00rootroot00000000000000[package] name = "quickcheck-ext" version = "0.1.0" edition = "2021" publish = false license = "Unlicense/MIT" [package.metadata.release] release = false [dependencies] quickcheck = "1" num-traits = "0.2" rust-yamux-yamux-v0.13.3/quickcheck-ext/src/000077500000000000000000000000001463066673500210025ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/quickcheck-ext/src/lib.rs000066400000000000000000000023661463066673500221250ustar00rootroot00000000000000#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub use quickcheck::*; use core::ops::Range; use num_traits::sign::Unsigned; pub trait GenRange { fn gen_range(&mut self, _range: Range) -> T; fn gen_index(&mut self, ubound: usize) -> usize { if ubound <= (core::u32::MAX as usize) { self.gen_range(0..ubound as u32) as usize } else { self.gen_range(0..ubound) } } } impl GenRange for Gen { fn gen_range(&mut self, range: Range) -> T { ::arbitrary(self) % (range.end - range.start) + range.start } } pub trait SliceRandom { fn shuffle(&mut self, arr: &mut [T]); fn choose_multiple<'a, T>( &mut self, arr: &'a [T], amount: usize, ) -> std::iter::Take> { let mut v: Vec<&T> = arr.iter().collect(); self.shuffle(&mut v); v.into_iter().take(amount) } } impl SliceRandom for Gen { fn shuffle(&mut self, arr: &mut [T]) { for i in (1..arr.len()).rev() { // invariant: elements with index > i have been locked in place. arr.swap(i, self.gen_index(i + 1)); } } } rust-yamux-yamux-v0.13.3/test-harness/000077500000000000000000000000001463066673500177235ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/test-harness/Cargo.toml000066400000000000000000000013051463066673500216520ustar00rootroot00000000000000[package] name = "test-harness" version = "0.1.0" edition = "2021" publish = false [dependencies] yamux = { path = "../yamux" } futures = "0.3.4" quickcheck = { package = "quickcheck-ext", path = "../quickcheck-ext" } tokio = { version = "1.0", features = ["net", "rt-multi-thread", "macros", "time"] } tokio-util = { version = "0.7", features = ["compat"] } anyhow = "1" log = "0.4.17" [dev-dependencies] criterion = "0.5" env_logger = "0.11" futures = "0.3.4" tokio = { version = "1.0", features = ["net", "rt-multi-thread", "macros", "time"] } tokio-util = { version = "0.7", features = ["compat"] } constrained-connection = "0.1" futures_ringbuf = "0.4.0" [[bench]] name = "concurrent" harness = false rust-yamux-yamux-v0.13.3/test-harness/benches/000077500000000000000000000000001463066673500213325ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/test-harness/benches/concurrent.rs000066400000000000000000000064171463066673500240720ustar00rootroot00000000000000// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. use constrained_connection::{new_unconstrained_connection, samples, Endpoint}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use std::iter; use std::sync::Arc; use test_harness::{dev_null_server, MessageSender, MessageSenderStrategy, Msg}; use tokio::{runtime::Runtime, task}; use yamux::{Config, Connection, Mode}; criterion_group!(benches, concurrent); criterion_main!(benches); #[derive(Debug, Clone)] struct Bytes(Arc>); impl AsRef<[u8]> for Bytes { fn as_ref(&self) -> &[u8] { &self.0[..] } } fn concurrent(c: &mut Criterion) { let data = Bytes(Arc::new(vec![0x42; 4096])); let networks = vec![ ("mobile", (|| samples::mobile_hsdpa().2) as fn() -> (_, _)), ( "adsl2+", (|| samples::residential_adsl2().2) as fn() -> (_, _), ), ("gbit-lan", (|| samples::gbit_lan().2) as fn() -> (_, _)), ( "unconstrained", new_unconstrained_connection as fn() -> (_, _), ), ]; let mut group = c.benchmark_group("concurrent"); group.sample_size(10); for (network_name, new_connection) in networks.into_iter() { for nstreams in [1, 10, 100].iter() { for nmessages in [1, 10, 100].iter() { let data = data.clone(); let rt = Runtime::new().unwrap(); group.throughput(Throughput::Bytes( (nstreams * nmessages * data.0.len()) as u64, )); group.bench_function( BenchmarkId::from_parameter(format!( "{}/#streams{}/#messages{}", network_name, nstreams, nmessages )), |b| { b.iter(|| { let (server, client) = new_connection(); rt.block_on(oneway(*nstreams, *nmessages, data.clone(), server, client)) }) }, ); } } } group.finish(); } async fn oneway( nstreams: usize, nmessages: usize, data: Bytes, server: Endpoint, client: Endpoint, ) { let server = Connection::new(server, Config::default(), Mode::Server); let client = Connection::new(client, Config::default(), Mode::Client); task::spawn(dev_null_server(server)); let messages = iter::repeat(data) .map(|b| Msg(b.0.to_vec())) .take(nstreams) .collect(); // `MessageSender` will use 1 stream per message. let num_streams_used = MessageSender::new(client, messages, true) .with_message_multiplier(nmessages as u64) .with_strategy(MessageSenderStrategy::Send) .await .unwrap(); assert_eq!(num_streams_used, nstreams); } rust-yamux-yamux-v0.13.3/test-harness/src/000077500000000000000000000000001463066673500205125ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/test-harness/src/lib.rs000066400000000000000000000327551463066673500216420ustar00rootroot00000000000000use futures::future::BoxFuture; use futures::stream::FuturesUnordered; use futures::{ future, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, StreamExt, TryStreamExt, }; use futures::{stream, FutureExt, Stream}; use quickcheck::{Arbitrary, Gen}; use std::future::Future; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::pin::Pin; use std::task::{Context, Poll}; use std::{fmt, io, mem}; use tokio::net::{TcpListener, TcpSocket, TcpStream}; use tokio::task; use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; use yamux::Config; use yamux::ConnectionError; use yamux::{Connection, Mode}; pub async fn connected_peers( server_config: Config, client_config: Config, buffer_sizes: Option, ) -> io::Result<(Connection>, Connection>)> { let (listener, addr) = bind(buffer_sizes).await?; let server = async { let (stream, _) = listener.accept().await?; Ok(Connection::new( stream.compat(), server_config, Mode::Server, )) }; let client = async { let stream = new_socket(buffer_sizes)?.connect(addr).await?; Ok(Connection::new( stream.compat(), client_config, Mode::Client, )) }; futures::future::try_join(server, client).await } pub async fn bind(buffer_sizes: Option) -> io::Result<(TcpListener, SocketAddr)> { let socket = new_socket(buffer_sizes)?; socket.bind(SocketAddr::V4(SocketAddrV4::new( Ipv4Addr::new(127, 0, 0, 1), 0, )))?; let listener = socket.listen(1024)?; let address = listener.local_addr()?; Ok((listener, address)) } fn new_socket(buffer_sizes: Option) -> io::Result { let socket = TcpSocket::new_v4()?; if let Some(size) = buffer_sizes { socket.set_send_buffer_size(size.send)?; socket.set_recv_buffer_size(size.recv)?; } Ok(socket) } /// For each incoming stream of `c` echo back to the sender. pub async fn echo_server(mut c: Connection) -> Result<(), ConnectionError> where T: AsyncRead + AsyncWrite + Unpin, { stream::poll_fn(|cx| c.poll_next_inbound(cx)) .try_for_each_concurrent(None, |mut stream| async move { { let (mut r, mut w) = AsyncReadExt::split(&mut stream); futures::io::copy(&mut r, &mut w).await.unwrap(); } stream.close().await?; Ok(()) }) .await } /// For each incoming stream of `c`, read to end but don't write back. pub async fn dev_null_server(mut c: Connection) -> Result<(), ConnectionError> where T: AsyncRead + AsyncWrite + Unpin, { stream::poll_fn(|cx| c.poll_next_inbound(cx)) .try_for_each_concurrent(None, |mut stream| async move { let mut buf = [0u8; 1024]; while let Ok(n) = stream.read(&mut buf).await { if n == 0 { break; } } stream.close().await?; Ok(()) }) .await } pub struct MessageSender { connection: Connection, pending_messages: Vec, worker_streams: FuturesUnordered>, streams_processed: usize, /// Whether to spawn a new task for each stream. spawn_tasks: bool, /// How many times to send each message on the stream message_multiplier: u64, strategy: MessageSenderStrategy, } #[derive(Copy, Clone)] pub enum MessageSenderStrategy { SendRecv, Send, } impl MessageSender { pub fn new(connection: Connection, messages: Vec, spawn_tasks: bool) -> Self { Self { connection, pending_messages: messages, worker_streams: FuturesUnordered::default(), streams_processed: 0, spawn_tasks, message_multiplier: 1, strategy: MessageSenderStrategy::SendRecv, } } pub fn with_message_multiplier(mut self, multiplier: u64) -> Self { self.message_multiplier = multiplier; self } pub fn with_strategy(mut self, strategy: MessageSenderStrategy) -> Self { self.strategy = strategy; self } } impl Future for MessageSender where T: AsyncRead + AsyncWrite + Unpin, { type Output = yamux::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { if this.pending_messages.is_empty() && this.worker_streams.is_empty() { futures::ready!(this.connection.poll_close(cx)?); return Poll::Ready(Ok(this.streams_processed)); } if let Some(message) = this.pending_messages.pop() { match this.connection.poll_new_outbound(cx)? { Poll::Ready(mut stream) => { let multiplier = this.message_multiplier; let strategy = this.strategy; let future = async move { for _ in 0..multiplier { match strategy { MessageSenderStrategy::SendRecv => { send_recv_message(&mut stream, &message).await.unwrap() } MessageSenderStrategy::Send => { stream.write_all(&message.0).await.unwrap() } }; } stream.close().await.unwrap(); }; let worker_stream_future = if this.spawn_tasks { async { task::spawn(future).await.unwrap() }.boxed() } else { future.boxed() }; this.worker_streams.push(worker_stream_future); continue; } Poll::Pending => { this.pending_messages.push(message); } } } match this.worker_streams.poll_next_unpin(cx) { Poll::Ready(Some(())) => { this.streams_processed += 1; continue; } Poll::Ready(None) | Poll::Pending => {} } match this.connection.poll_next_inbound(cx)? { Poll::Ready(Some(stream)) => { drop(stream); panic!("Did not expect remote to open a stream"); } Poll::Ready(None) => { panic!("Did not expect remote to close the connection"); } Poll::Pending => {} } return Poll::Pending; } } } /// For each incoming stream, do nothing. pub async fn noop_server(c: impl Stream>) { c.for_each(|maybe_stream| { drop(maybe_stream); future::ready(()) }) .await; } /// Send and receive buffer size for a TCP socket. #[derive(Clone, Debug, Copy)] pub struct TcpBufferSizes { send: u32, recv: u32, } impl Arbitrary for TcpBufferSizes { fn arbitrary(g: &mut Gen) -> Self { let send = if bool::arbitrary(g) { 16 * 1024 } else { 32 * 1024 }; // Have receive buffer size be some multiple of send buffer size. let recv = if bool::arbitrary(g) { send * 2 } else { send * 4 }; TcpBufferSizes { send, recv } } } pub async fn send_recv_message(stream: &mut yamux::Stream, Msg(msg): &Msg) -> io::Result<()> { let id = stream.id(); let (mut reader, mut writer) = AsyncReadExt::split(stream); let len = msg.len(); let write_fut = async { writer.write_all(msg).await.unwrap(); log::debug!("C: {}: sent {} bytes", id, len); }; let mut data = vec![0; msg.len()]; let read_fut = async { reader.read_exact(&mut data).await.unwrap(); log::debug!("C: {}: received {} bytes", id, data.len()); }; futures::future::join(write_fut, read_fut).await; assert_eq!(&data, msg); Ok(()) } /// Send all messages, using only a single stream. pub async fn send_on_single_stream( mut stream: yamux::Stream, iter: impl IntoIterator, ) -> Result<(), ConnectionError> { log::debug!("C: new stream: {}", stream); for msg in iter { send_recv_message(&mut stream, &msg).await?; } stream.close().await?; Ok(()) } pub struct EchoServer { connection: Connection, worker_streams: FuturesUnordered>>, streams_processed: usize, connection_closed: bool, } impl EchoServer { pub fn new(connection: Connection) -> Self { Self { connection, worker_streams: FuturesUnordered::default(), streams_processed: 0, connection_closed: false, } } } impl Future for EchoServer where T: AsyncRead + AsyncWrite + Unpin, { type Output = yamux::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { match this.worker_streams.poll_next_unpin(cx) { Poll::Ready(Some(Ok(()))) => { this.streams_processed += 1; continue; } Poll::Ready(Some(Err(e))) => { eprintln!("A stream failed: {}", e); continue; } Poll::Ready(None) => { if this.connection_closed { return Poll::Ready(Ok(this.streams_processed)); } } Poll::Pending => {} } match this.connection.poll_next_inbound(cx) { Poll::Ready(Some(Ok(mut stream))) => { this.worker_streams.push( async move { { let (mut r, mut w) = AsyncReadExt::split(&mut stream); futures::io::copy(&mut r, &mut w).await?; } stream.close().await?; Ok(()) } .boxed(), ); continue; } Poll::Ready(None) | Poll::Ready(Some(Err(_))) => { this.connection_closed = true; continue; } Poll::Pending => {} } return Poll::Pending; } } } #[derive(Debug)] pub struct OpenStreamsClient { connection: Option>, streams: Vec, to_open: usize, } impl OpenStreamsClient { pub fn new(connection: Connection, to_open: usize) -> Self { Self { connection: Some(connection), streams: vec![], to_open, } } } impl Future for OpenStreamsClient where T: AsyncRead + AsyncWrite + Unpin + fmt::Debug, { type Output = yamux::Result<(Connection, Vec)>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); let connection = this.connection.as_mut().unwrap(); loop { // Drive connection to make progress. match connection.poll_next_inbound(cx)? { Poll::Ready(_stream) => { panic!("Unexpected inbound stream"); } Poll::Pending => {} } if this.streams.len() < this.to_open { match connection.poll_new_outbound(cx)? { Poll::Ready(stream) => { this.streams.push(stream); continue; } Poll::Pending => {} } } if this.streams.len() == this.to_open { return Poll::Ready(Ok(( this.connection.take().unwrap(), mem::take(&mut this.streams), ))); } return Poll::Pending; } } } #[derive(Clone, Debug)] pub struct Msg(pub Vec); impl Arbitrary for Msg { fn arbitrary(g: &mut Gen) -> Msg { let mut msg = Msg(Arbitrary::arbitrary(g)); if msg.0.is_empty() { msg.0.push(Arbitrary::arbitrary(g)); } msg } fn shrink(&self) -> Box> { Box::new(self.0.shrink().filter(|v| !v.is_empty()).map(Msg)) } } #[derive(Clone, Debug)] pub struct TestConfig(pub Config); impl Arbitrary for TestConfig { fn arbitrary(g: &mut Gen) -> Self { use quickcheck::GenRange; let mut c = Config::default(); let max_num_streams = 512; c.set_read_after_close(Arbitrary::arbitrary(g)); c.set_max_num_streams(max_num_streams); if bool::arbitrary(g) { c.set_max_connection_receive_window(Some( g.gen_range(max_num_streams * (yamux::DEFAULT_CREDIT as usize)..usize::MAX), )); } else { c.set_max_connection_receive_window(None); } TestConfig(c) } } rust-yamux-yamux-v0.13.3/test-harness/tests/000077500000000000000000000000001463066673500210655ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/test-harness/tests/ack_backlog.rs000066400000000000000000000204331463066673500236550ustar00rootroot00000000000000use futures::channel::oneshot; use futures::future::BoxFuture; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::{future, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, StreamExt}; use std::future::Future; use std::mem; use std::pin::Pin; use std::task::{Context, Poll}; use test_harness::bind; use tokio::net::TcpStream; use tokio_util::compat::TokioAsyncReadCompatExt; use yamux::{Config, Connection, ConnectionError, Mode, Stream}; #[tokio::test] async fn honours_ack_backlog_of_256() { let _ = env_logger::try_init(); let (tx, rx) = oneshot::channel(); let (listener, address) = bind(None).await.expect("bind"); let server = async { let socket = listener.accept().await.expect("accept").0.compat(); let connection = Connection::new(socket, Config::default(), Mode::Server); Server::new(connection, rx).await }; let client = async { let socket = TcpStream::connect(address).await.expect("connect").compat(); let connection = Connection::new(socket, Config::default(), Mode::Client); Client::new(connection, tx).await }; let (server_processed, client_processed) = future::try_join(server, client).await.unwrap(); assert_eq!(server_processed, 257); assert_eq!(client_processed, 257); } enum Server { Idle { connection: Connection, trigger: oneshot::Receiver<()>, }, Accepting { connection: Connection, worker_streams: FuturesUnordered>>, streams_processed: usize, }, Poisoned, } impl Server { fn new(connection: Connection, trigger: oneshot::Receiver<()>) -> Self { Server::Idle { connection, trigger, } } } impl Future for Server where T: AsyncRead + AsyncWrite + Unpin, { type Output = yamux::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { match mem::replace(this, Server::Poisoned) { Server::Idle { mut trigger, connection, } => match trigger.poll_unpin(cx) { Poll::Ready(_) => { *this = Server::Accepting { connection, worker_streams: Default::default(), streams_processed: 0, }; continue; } Poll::Pending => { *this = Server::Idle { trigger, connection, }; return Poll::Pending; } }, Server::Accepting { mut connection, mut streams_processed, mut worker_streams, } => { match connection.poll_next_inbound(cx)? { Poll::Ready(Some(stream)) => { worker_streams.push(pong_ping(stream).boxed()); *this = Server::Accepting { connection, streams_processed, worker_streams, }; continue; } Poll::Ready(None) => { return Poll::Ready(Ok(streams_processed)); } Poll::Pending => {} } match worker_streams.poll_next_unpin(cx)? { Poll::Ready(Some(())) => { streams_processed += 1; *this = Server::Accepting { connection, streams_processed, worker_streams, }; continue; } Poll::Ready(None) | Poll::Pending => {} } *this = Server::Accepting { connection, streams_processed, worker_streams, }; return Poll::Pending; } Server::Poisoned => unreachable!(), } } } } struct Client { connection: Connection, worker_streams: FuturesUnordered>>, trigger: Option>, streams_processed: usize, } impl Client { fn new(connection: Connection, trigger: oneshot::Sender<()>) -> Self { Self { connection, trigger: Some(trigger), worker_streams: FuturesUnordered::default(), streams_processed: 0, } } } impl Future for Client where T: AsyncRead + AsyncWrite + Unpin, { type Output = yamux::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { // First, try to open 256 streams if this.worker_streams.len() < 256 && this.streams_processed == 0 { match this.connection.poll_new_outbound(cx)? { Poll::Ready(stream) => { this.worker_streams.push(ping_pong(stream).boxed()); continue; } Poll::Pending => { panic!("Should be able to open 256 streams without yielding") } } } if this.worker_streams.len() == 256 && this.streams_processed == 0 { let poll_result = this.connection.poll_new_outbound(cx); match (poll_result, this.trigger.take()) { (Poll::Pending, Some(trigger)) => { // This is what we want, our task gets parked because we have hit the limit. // Tell the server to start processing streams and wait until we get woken. trigger.send(()).unwrap(); return Poll::Pending; } (Poll::Ready(stream), None) => { // We got woken because the server has started to acknowledge streams. this.worker_streams.push(ping_pong(stream.unwrap()).boxed()); continue; } (Poll::Ready(e), Some(_)) => { panic!("should not be able to open stream if server hasn't acknowledged existing streams: {:?}", e) } (Poll::Pending, None) => {} } } match this.worker_streams.poll_next_unpin(cx)? { Poll::Ready(Some(())) => { this.streams_processed += 1; continue; } Poll::Ready(None) if this.streams_processed > 0 => { return Poll::Ready(Ok(this.streams_processed)); } Poll::Ready(None) | Poll::Pending => {} } // Allow the connection to make progress match this.connection.poll_next_inbound(cx)? { Poll::Ready(Some(_)) => { panic!("server never opens stream") } Poll::Ready(None) => { return Poll::Ready(Ok(this.streams_processed)); } Poll::Pending => {} } return Poll::Pending; } } } async fn ping_pong(mut stream: Stream) -> Result<(), ConnectionError> { let mut buffer = [0u8; 4]; stream.write_all(b"ping").await?; stream.read_exact(&mut buffer).await?; assert_eq!(&buffer, b"pong"); stream.close().await?; Ok(()) } async fn pong_ping(mut stream: Stream) -> Result<(), ConnectionError> { let mut buffer = [0u8; 4]; stream.write_all(b"pong").await?; stream.read_exact(&mut buffer).await?; assert_eq!(&buffer, b"ping"); stream.close().await?; Ok(()) } rust-yamux-yamux-v0.13.3/test-harness/tests/ack_timing.rs000066400000000000000000000166771463066673500235610ustar00rootroot00000000000000use futures::future::BoxFuture; use futures::future::FutureExt; use futures::{future, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use std::future::Future; use std::mem; use std::pin::Pin; use std::task::{Context, Poll}; use test_harness::bind; use tokio::net::TcpStream; use tokio_util::compat::TokioAsyncReadCompatExt; use yamux::{Config, Connection, ConnectionError, Mode, Stream}; #[tokio::test] async fn stream_is_acknowledged_on_first_use() { let _ = env_logger::try_init(); let (listener, address) = bind(None).await.expect("bind"); let server = async { let socket = listener.accept().await.expect("accept").0.compat(); let connection = Connection::new(socket, Config::default(), Mode::Server); Server::new(connection).await }; let client = async { let socket = TcpStream::connect(address).await.expect("connect").compat(); let connection = Connection::new(socket, Config::default(), Mode::Client); Client::new(connection).await }; let ((), ()) = future::try_join(server, client).await.unwrap(); } enum Server { Accepting { connection: Connection, }, Working { connection: Connection, stream: BoxFuture<'static, yamux::Result<()>>, }, Idle { connection: Connection, }, Poisoned, } impl Server { fn new(connection: Connection) -> Self { Server::Accepting { connection } } } impl Future for Server where T: AsyncRead + AsyncWrite + Unpin, { type Output = yamux::Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { match mem::replace(this, Self::Poisoned) { Self::Accepting { mut connection } => match connection.poll_next_inbound(cx)? { Poll::Ready(Some(stream)) => { *this = Self::Working { connection, stream: pong_ping(stream).boxed(), }; continue; } Poll::Ready(None) => { panic!("connection closed before receiving a new stream") } Poll::Pending => { *this = Self::Accepting { connection }; return Poll::Pending; } }, Self::Working { mut connection, mut stream, } => { match stream.poll_unpin(cx)? { Poll::Ready(()) => { *this = Self::Idle { connection }; continue; } Poll::Pending => {} } match connection.poll_next_inbound(cx)? { Poll::Ready(Some(_)) => { panic!("not expecting new stream"); } Poll::Ready(None) => { panic!("connection closed before stream completed") } Poll::Pending => { *this = Self::Working { connection, stream }; return Poll::Pending; } } } Self::Idle { mut connection } => match connection.poll_next_inbound(cx)? { Poll::Ready(Some(_)) => { panic!("not expecting new stream"); } Poll::Ready(None) => return Poll::Ready(Ok(())), Poll::Pending => { *this = Self::Idle { connection }; return Poll::Pending; } }, Self::Poisoned => unreachable!(), } } } } enum Client { Opening { connection: Connection, }, Working { connection: Connection, stream: BoxFuture<'static, yamux::Result<()>>, }, Poisoned, } impl Client { fn new(connection: Connection) -> Self { Self::Opening { connection } } } impl Future for Client where T: AsyncRead + AsyncWrite + Unpin, { type Output = yamux::Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { match mem::replace(this, Self::Poisoned) { Self::Opening { mut connection } => match connection.poll_new_outbound(cx)? { Poll::Ready(stream) => { *this = Self::Working { connection, stream: ping_pong(stream).boxed(), }; continue; } Poll::Pending => { *this = Self::Opening { connection }; return Poll::Pending; } }, Self::Working { mut connection, mut stream, } => { match stream.poll_unpin(cx)? { Poll::Ready(()) => { return Poll::Ready(Ok(())); } Poll::Pending => {} } match connection.poll_next_inbound(cx)? { Poll::Ready(Some(_)) => { panic!("not expecting new stream"); } Poll::Ready(None) => { panic!("connection closed before stream completed") } Poll::Pending => { *this = Self::Working { connection, stream }; return Poll::Pending; } } } Self::Poisoned => unreachable!(), } } } } /// Handler for the **outbound** stream on the client. /// /// Initially, the stream is not acknowledged. The server will only acknowledge the stream with the first frame. async fn ping_pong(mut stream: Stream) -> Result<(), ConnectionError> { assert!( stream.is_pending_ack(), "newly returned stream should not be acknowledged" ); let mut buffer = [0u8; 4]; stream.write_all(b"ping").await?; stream.read_exact(&mut buffer).await?; assert!( !stream.is_pending_ack(), "stream should be acknowledged once we received the first data" ); assert_eq!(&buffer, b"pong"); stream.close().await?; Ok(()) } /// Handler for the **inbound** stream on the server. /// /// Initially, the stream is not acknowledged. We only include the ACK flag in the first frame. async fn pong_ping(mut stream: Stream) -> Result<(), ConnectionError> { assert!( stream.is_pending_ack(), "before sending anything we should not have acknowledged the stream to the remote" ); let mut buffer = [0u8; 4]; stream.write_all(b"pong").await?; assert!( !stream.is_pending_ack(), "we should have sent an ACK flag with the first payload" ); stream.read_exact(&mut buffer).await?; assert_eq!(&buffer, b"ping"); stream.close().await?; Ok(()) } rust-yamux-yamux-v0.13.3/test-harness/tests/poll_api.rs000066400000000000000000000266471463066673500232510ustar00rootroot00000000000000use futures::executor::LocalPool; use futures::future::join; use futures::prelude::*; use futures::task::{Spawn, SpawnExt}; use futures::{future, stream, AsyncReadExt, AsyncWriteExt, FutureExt, StreamExt}; use quickcheck::QuickCheck; use std::iter; use std::panic::panic_any; use std::pin::pin; use test_harness::*; use tokio::net::TcpStream; use tokio::runtime::Runtime; use tokio::task; use tokio_util::compat::TokioAsyncReadCompatExt; use yamux::{Config, Connection, ConnectionError, Mode}; #[test] fn prop_config_send_recv_multi() { let _ = env_logger::try_init(); fn prop(msgs: Vec, cfg1: TestConfig, cfg2: TestConfig) { Runtime::new().unwrap().block_on(async move { let num_messagses = msgs.len(); let (listener, address) = bind(None).await.expect("bind"); let server = async { let socket = listener.accept().await.expect("accept").0.compat(); let connection = Connection::new(socket, cfg1.0, Mode::Server); EchoServer::new(connection).await }; let client = async { let socket = TcpStream::connect(address).await.expect("connect").compat(); let connection = Connection::new(socket, cfg2.0, Mode::Client); MessageSender::new(connection, msgs, false).await }; let (server_processed, client_processed) = futures::future::try_join(server, client).await.unwrap(); assert_eq!(server_processed, num_messagses); assert_eq!(client_processed, num_messagses); }) } QuickCheck::new().quickcheck(prop as fn(_, _, _) -> _) } #[test] fn concurrent_streams() { let _ = env_logger::try_init(); fn prop(tcp_buffer_sizes: Option) { const PAYLOAD_SIZE: usize = 128 * 1024; let data = Msg(vec![0x42; PAYLOAD_SIZE]); let n_streams = 512; let mut cfg = Config::default(); cfg.set_split_send_size(PAYLOAD_SIZE); // Use a large frame size to speed up the test. Runtime::new().expect("new runtime").block_on(async move { let (server, client) = connected_peers(cfg.clone(), cfg, tcp_buffer_sizes) .await .unwrap(); task::spawn(echo_server(server)); let client = MessageSender::new( client, iter::repeat(data).take(n_streams).collect::>(), true, ); let num_processed = client.await.unwrap(); assert_eq!(num_processed, n_streams); }); } QuickCheck::new().tests(3).quickcheck(prop as fn(_) -> _) } #[test] fn prop_max_streams() { fn prop(n: usize) -> Result { let max_streams = n % 100; let mut cfg = Config::default(); cfg.set_max_num_streams(max_streams); Runtime::new().unwrap().block_on(async move { let (server, client) = connected_peers(cfg.clone(), cfg, None).await?; task::spawn(EchoServer::new(server)); let client = OpenStreamsClient::new(client, max_streams); let (client, streams) = client.await?; assert_eq!(streams.len(), max_streams); let open_result = OpenStreamsClient::new(client, 1).await; Ok(matches!(open_result, Err(ConnectionError::TooManyStreams))) }) } QuickCheck::new().tests(7).quickcheck(prop as fn(_) -> _) } #[test] fn prop_send_recv_half_closed() { fn prop(msg: Msg) -> Result<(), ConnectionError> { let msg_len = msg.0.len(); Runtime::new().unwrap().block_on(async move { let (mut server, mut client) = connected_peers(Config::default(), Config::default(), None).await?; // Server should be able to write on a stream shutdown by the client. let server = async { let mut server = stream::poll_fn(move |cx| server.poll_next_inbound(cx)); let mut first_stream = server.next().await.ok_or(ConnectionError::Closed)??; task::spawn(noop_server(server)); let mut buf = vec![0; msg_len]; first_stream.read_exact(&mut buf).await?; first_stream.write_all(&buf).await?; first_stream.close().await?; Result::<(), ConnectionError>::Ok(()) }; // Client should be able to read after shutting down the stream. let client = async { let mut stream = future::poll_fn(|cx| client.poll_new_outbound(cx)) .await .unwrap(); task::spawn(noop_server(stream::poll_fn(move |cx| { client.poll_next_inbound(cx) }))); stream.write_all(&msg.0).await?; stream.close().await?; assert!(stream.is_write_closed()); let mut buf = vec![0; msg_len]; stream.read_exact(&mut buf).await?; assert_eq!(buf, msg.0); assert_eq!(Some(0), stream.read(&mut buf).await.ok()); assert!(stream.is_closed()); Result::<(), ConnectionError>::Ok(()) }; futures::future::try_join(server, client).await?; Ok(()) }) } QuickCheck::new().tests(7).quickcheck(prop as fn(_) -> _) } #[test] fn prop_config_send_recv_single() { fn prop( mut msgs: Vec, TestConfig(cfg1): TestConfig, TestConfig(cfg2): TestConfig, ) -> Result<(), ConnectionError> { msgs.insert(0, Msg(vec![1u8; yamux::DEFAULT_CREDIT as usize])); Runtime::new().unwrap().block_on(async move { let (server, mut client) = connected_peers(cfg1, cfg2, None).await?; let server = echo_server(server); let client = async { let stream = future::poll_fn(|cx| client.poll_new_outbound(cx)) .await .unwrap(); let client_task = noop_server(stream::poll_fn(|cx| client.poll_next_inbound(cx))); future::select(pin!(client_task), pin!(send_on_single_stream(stream, msgs))).await; future::poll_fn(|cx| client.poll_close(cx)).await.unwrap(); Ok(()) }; futures::future::try_join(server, client).await?; Ok(()) }) } QuickCheck::new() .tests(10) .quickcheck(prop as fn(_, _, _) -> _) } /// This test simulates two endpoints of a Yamux connection which may be unable to /// write simultaneously but can make progress by reading. If both endpoints /// don't read in-between trying to finish their writes, a deadlock occurs. #[test] fn write_deadlock() { let _ = env_logger::try_init(); let mut pool = LocalPool::new(); // We make the message to transmit large enough s.t. the "server" // is forced to start writing (i.e. echoing) the bytes before // having read the entire payload. let msg = vec![1u8; 1024 * 1024]; // We choose a "connection capacity" that is artificially below // the size of a receive window. If it were equal or greater, // multiple concurrently writing streams would be needed to non-deterministically // provoke the write deadlock. This is supposed to reflect the // fact that the sum of receive windows of all open streams can easily // be larger than the send capacity of the connection at any point in time. // Using such a low capacity here therefore yields a more reproducible test. let capacity = 1024; // Create a bounded channel representing the underlying "connection". // Each endpoint gets a name and a bounded capacity for its outbound // channel (which is the other's inbound channel). let (server_endpoint, client_endpoint) = futures_ringbuf::Endpoint::pair(capacity, capacity); // Create and spawn a "server" that echoes every message back to the client. let server = Connection::new(server_endpoint, Config::default(), Mode::Server); pool.spawner() .spawn_obj( async move { echo_server(server).await.unwrap() } .boxed() .into(), ) .unwrap(); // Create and spawn a "client" that sends messages expected to be echoed // by the server. let mut client = Connection::new(client_endpoint, Config::default(), Mode::Client); let stream = pool .run_until(future::poll_fn(|cx| client.poll_new_outbound(cx))) .unwrap(); // Continuously advance the Yamux connection of the client in a background task. pool.spawner() .spawn_obj( noop_server(stream::poll_fn(move |cx| client.poll_next_inbound(cx))) .boxed() .into(), ) .unwrap(); // Send the message, expecting it to be echo'd. pool.run_until( pool.spawner() .spawn_with_handle( async move { let (mut reader, mut writer) = AsyncReadExt::split(stream); let mut b = vec![0; msg.len()]; // Write & read concurrently, so that the client is able // to start reading the echo'd bytes before it even finished // sending them all. let _ = join( writer.write_all(msg.as_ref()).map_err(|e| panic_any(e)), reader.read_exact(&mut b[..]).map_err(|e| panic_any(e)), ) .await; let mut stream = reader.reunite(writer).unwrap(); stream.close().await.unwrap(); log::debug!("C: Stream {} done.", stream.id()); assert_eq!(b, msg); } .boxed(), ) .unwrap(), ); } #[test] fn close_through_drop_of_stream_propagates_to_remote() { let _ = env_logger::try_init(); let mut pool = LocalPool::new(); let (server_endpoint, client_endpoint) = futures_ringbuf::Endpoint::pair(1024, 1024); let mut server = Connection::new(server_endpoint, Config::default(), Mode::Server); let mut client = Connection::new(client_endpoint, Config::default(), Mode::Client); // Spawn client, opening a stream, writing to the stream, dropping the stream, driving the // client connection state machine. pool.spawner() .spawn_obj( async { let mut stream = future::poll_fn(|cx| client.poll_new_outbound(cx)) .await .unwrap(); stream.write_all(&[42]).await.unwrap(); drop(stream); noop_server(stream::poll_fn(move |cx| client.poll_next_inbound(cx))).await; } .boxed() .into(), ) .unwrap(); // Accept inbound stream. let mut stream_server_side = pool .run_until(future::poll_fn(|cx| server.poll_next_inbound(cx))) .unwrap() .unwrap(); // Spawn server connection state machine. pool.spawner() .spawn_obj( noop_server(stream::poll_fn(move |cx| server.poll_next_inbound(cx))) .boxed() .into(), ) .unwrap(); // Expect to eventually receive close on stream. pool.run_until(async { let mut buf = Vec::new(); stream_server_side.read_to_end(&mut buf).await?; assert_eq!(buf, vec![42]); Ok::<(), std::io::Error>(()) }) .unwrap(); } rust-yamux-yamux-v0.13.3/yamux/000077500000000000000000000000001463066673500164465ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/yamux/Cargo.toml000066400000000000000000000013551463066673500204020ustar00rootroot00000000000000[package] name = "yamux" version = "0.13.3" authors = ["Parity Technologies "] license = "Apache-2.0 OR MIT" description = "Multiplexer over reliable, ordered connections" keywords = ["network", "protocol"] categories = ["network-programming"] repository = "https://github.com/paritytech/yamux" edition = "2021" [dependencies] futures = { version = "0.3.12", default-features = false, features = ["std", "executor"] } log = "0.4.8" nohash-hasher = "0.2" parking_lot = "0.12" rand = "0.8.3" static_assertions = "1" pin-project = "1.1.0" web-time = "1.1.0" [dev-dependencies] futures = { version = "0.3.12", default-features = false, features = ["executor"] } quickcheck = { package = "quickcheck-ext", path = "../quickcheck-ext" } rust-yamux-yamux-v0.13.3/yamux/src/000077500000000000000000000000001463066673500172355ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/yamux/src/chunks.rs000066400000000000000000000064261463066673500211060ustar00rootroot00000000000000// Copyright (c) 2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. use std::{collections::VecDeque, io}; /// A sequence of [`Chunk`] values. /// /// [`Chunks::len`] considers all [`Chunk`] elements and computes the total /// result, i.e. the length of all bytes, by summing up the lengths of all /// [`Chunk`] elements. #[derive(Debug)] pub(crate) struct Chunks { seq: VecDeque, len: usize, } impl Chunks { /// A new empty chunk list. pub(crate) fn new() -> Self { Chunks { seq: VecDeque::new(), len: 0, } } /// The total length of bytes yet-to-be-read in all `Chunk`s. pub(crate) fn len(&self) -> usize { self.len - self.seq.front().map(|c| c.offset()).unwrap_or(0) } /// Add another chunk of bytes to the end. pub(crate) fn push(&mut self, x: Vec) { self.len += x.len(); if !x.is_empty() { self.seq.push_back(Chunk { cursor: io::Cursor::new(x), }) } } /// Remove and return the first chunk. pub(crate) fn pop(&mut self) -> Option { let chunk = self.seq.pop_front(); self.len -= chunk.as_ref().map(|c| c.len() + c.offset()).unwrap_or(0); chunk } /// Get a mutable reference to the first chunk. pub(crate) fn front_mut(&mut self) -> Option<&mut Chunk> { self.seq.front_mut() } } /// A `Chunk` wraps a `std::io::Cursor>`. /// /// It provides a byte-slice view and a way to advance the cursor so the /// vector can be consumed in steps. #[derive(Debug)] pub(crate) struct Chunk { cursor: io::Cursor>, } impl Chunk { /// Is this chunk empty? pub(crate) fn is_empty(&self) -> bool { self.len() == 0 } /// The remaining number of bytes in this `Chunk`. pub(crate) fn len(&self) -> usize { self.cursor.get_ref().len() - self.offset() } /// The sum of bytes that the cursor has been `advance`d over. pub(crate) fn offset(&self) -> usize { self.cursor.position() as usize } /// Move the cursor position by `amount` bytes. /// /// The `AsRef<[u8]>` impl of `Chunk` provides a byte-slice view /// from the current position to the end. pub(crate) fn advance(&mut self, amount: usize) { assert!({ // the new position must not exceed the vector's length let pos = self.offset().checked_add(amount); let max = self.cursor.get_ref().len(); pos.is_some() && pos <= Some(max) }); self.cursor .set_position(self.cursor.position() + amount as u64); } /// Consume `self` and return the inner vector. pub(crate) fn into_vec(self) -> Vec { self.cursor.into_inner() } } impl AsRef<[u8]> for Chunk { fn as_ref(&self) -> &[u8] { &self.cursor.get_ref()[self.offset()..] } } rust-yamux-yamux-v0.13.3/yamux/src/connection.rs000066400000000000000000001026371463066673500217530ustar00rootroot00000000000000// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. //! This module contains the `Connection` type and associated helpers. //! A `Connection` wraps an underlying (async) I/O resource and multiplexes //! `Stream`s over it. mod cleanup; mod closing; mod rtt; mod stream; use crate::tagged_stream::TaggedStream; use crate::{ error::ConnectionError, frame::header::{self, Data, GoAway, Header, Ping, StreamId, Tag, WindowUpdate, CONNECTION_ID}, frame::{self, Frame}, Config, DEFAULT_CREDIT, }; use crate::{Result, MAX_ACK_BACKLOG}; use cleanup::Cleanup; use closing::Closing; use futures::stream::SelectAll; use futures::{channel::mpsc, future::Either, prelude::*, sink::SinkExt, stream::Fuse}; use nohash_hasher::IntMap; use parking_lot::Mutex; use std::collections::VecDeque; use std::task::{Context, Waker}; use std::{fmt, sync::Arc, task::Poll}; pub use stream::{Packet, State, Stream}; /// How the connection is used. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum Mode { /// Client to server connection. Client, /// Server to client connection. Server, } /// The connection identifier. /// /// Randomly generated, this is mainly intended to improve log output. #[derive(Clone, Copy)] pub(crate) struct Id(u32); impl Id { /// Create a random connection ID. pub(crate) fn random() -> Self { Id(rand::random()) } } impl fmt::Debug for Id { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:08x}", self.0) } } impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:08x}", self.0) } } /// A Yamux connection object. /// /// Wraps the underlying I/O resource and makes progress via its /// [`Connection::poll_next_inbound`] method which must be called repeatedly /// until `Ok(None)` signals EOF or an error is encountered. #[derive(Debug)] pub struct Connection { inner: ConnectionState, } impl Connection { pub fn new(socket: T, cfg: Config, mode: Mode) -> Self { Self { inner: ConnectionState::Active(Active::new(socket, cfg, mode)), } } /// Poll for a new outbound stream. /// /// This function will fail if the current state does not allow opening new outbound streams. pub fn poll_new_outbound(&mut self, cx: &mut Context<'_>) -> Poll> { loop { match std::mem::replace(&mut self.inner, ConnectionState::Poisoned) { ConnectionState::Active(mut active) => match active.poll_new_outbound(cx) { Poll::Ready(Ok(stream)) => { self.inner = ConnectionState::Active(active); return Poll::Ready(Ok(stream)); } Poll::Pending => { self.inner = ConnectionState::Active(active); return Poll::Pending; } Poll::Ready(Err(e)) => { self.inner = ConnectionState::Cleanup(active.cleanup(e)); continue; } }, ConnectionState::Closing(mut inner) => match inner.poll_unpin(cx) { Poll::Ready(Ok(())) => { self.inner = ConnectionState::Closed; return Poll::Ready(Err(ConnectionError::Closed)); } Poll::Ready(Err(e)) => { self.inner = ConnectionState::Closed; return Poll::Ready(Err(e)); } Poll::Pending => { self.inner = ConnectionState::Closing(inner); return Poll::Pending; } }, ConnectionState::Cleanup(mut inner) => match inner.poll_unpin(cx) { Poll::Ready(e) => { self.inner = ConnectionState::Closed; return Poll::Ready(Err(e)); } Poll::Pending => { self.inner = ConnectionState::Cleanup(inner); return Poll::Pending; } }, ConnectionState::Closed => { self.inner = ConnectionState::Closed; return Poll::Ready(Err(ConnectionError::Closed)); } ConnectionState::Poisoned => unreachable!(), } } } /// Poll for the next inbound stream. /// /// If this function returns `None`, the underlying connection is closed. pub fn poll_next_inbound(&mut self, cx: &mut Context<'_>) -> Poll>> { loop { match std::mem::replace(&mut self.inner, ConnectionState::Poisoned) { ConnectionState::Active(mut active) => match active.poll(cx) { Poll::Ready(Ok(stream)) => { self.inner = ConnectionState::Active(active); return Poll::Ready(Some(Ok(stream))); } Poll::Ready(Err(e)) => { self.inner = ConnectionState::Cleanup(active.cleanup(e)); continue; } Poll::Pending => { self.inner = ConnectionState::Active(active); return Poll::Pending; } }, ConnectionState::Closing(mut closing) => match closing.poll_unpin(cx) { Poll::Ready(Ok(())) => { self.inner = ConnectionState::Closed; return Poll::Ready(None); } Poll::Ready(Err(e)) => { self.inner = ConnectionState::Closed; return Poll::Ready(Some(Err(e))); } Poll::Pending => { self.inner = ConnectionState::Closing(closing); return Poll::Pending; } }, ConnectionState::Cleanup(mut cleanup) => match cleanup.poll_unpin(cx) { Poll::Ready(ConnectionError::Closed) => { self.inner = ConnectionState::Closed; return Poll::Ready(None); } Poll::Ready(other) => { self.inner = ConnectionState::Closed; return Poll::Ready(Some(Err(other))); } Poll::Pending => { self.inner = ConnectionState::Cleanup(cleanup); return Poll::Pending; } }, ConnectionState::Closed => { self.inner = ConnectionState::Closed; return Poll::Ready(None); } ConnectionState::Poisoned => unreachable!(), } } } /// Close the connection. pub fn poll_close(&mut self, cx: &mut Context<'_>) -> Poll> { loop { match std::mem::replace(&mut self.inner, ConnectionState::Poisoned) { ConnectionState::Active(active) => { self.inner = ConnectionState::Closing(active.close()); } ConnectionState::Closing(mut inner) => match inner.poll_unpin(cx)? { Poll::Ready(()) => { self.inner = ConnectionState::Closed; } Poll::Pending => { self.inner = ConnectionState::Closing(inner); return Poll::Pending; } }, ConnectionState::Cleanup(mut cleanup) => match cleanup.poll_unpin(cx) { Poll::Ready(reason) => { log::warn!("Failure while closing connection: {}", reason); self.inner = ConnectionState::Closed; return Poll::Ready(Ok(())); } Poll::Pending => { self.inner = ConnectionState::Cleanup(cleanup); return Poll::Pending; } }, ConnectionState::Closed => { self.inner = ConnectionState::Closed; return Poll::Ready(Ok(())); } ConnectionState::Poisoned => { unreachable!() } } } } } impl Drop for Connection { fn drop(&mut self) { match &mut self.inner { ConnectionState::Active(active) => active.drop_all_streams(), ConnectionState::Closing(_) => {} ConnectionState::Cleanup(_) => {} ConnectionState::Closed => {} ConnectionState::Poisoned => {} } } } enum ConnectionState { /// The connection is alive and healthy. Active(Active), /// Our user requested to shutdown the connection, we are working on it. Closing(Closing), /// An error occurred and we are cleaning up our resources. Cleanup(Cleanup), /// The connection is closed. Closed, /// Something went wrong during our state transitions. Should never happen unless there is a bug. Poisoned, } impl fmt::Debug for ConnectionState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ConnectionState::Active(_) => write!(f, "Active"), ConnectionState::Closing(_) => write!(f, "Closing"), ConnectionState::Cleanup(_) => write!(f, "Cleanup"), ConnectionState::Closed => write!(f, "Closed"), ConnectionState::Poisoned => write!(f, "Poisoned"), } } } /// The active state of [`Connection`]. struct Active { id: Id, mode: Mode, config: Arc, socket: Fuse>, next_id: u32, streams: IntMap>>, stream_receivers: SelectAll>>, no_streams_waker: Option, pending_read_frame: Option>, pending_write_frame: Option>, new_outbound_stream_waker: Option, rtt: rtt::Rtt, /// A stream's `max_stream_receive_window` can grow beyond [`DEFAULT_CREDIT`], see /// [`Stream::next_window_update`]. This field is the sum of the bytes by which all streams' /// `max_stream_receive_window` have each exceeded [`DEFAULT_CREDIT`]. Used to enforce /// [`Config::max_connection_receive_window`]. accumulated_max_stream_windows: Arc>, } /// `Stream` to `Connection` commands. #[derive(Debug)] pub(crate) enum StreamCommand { /// A new frame should be sent to the remote. SendFrame(Frame>), /// Close a stream. CloseStream { ack: bool }, } /// Possible actions as a result of incoming frame handling. #[derive(Debug)] pub(crate) enum Action { /// Nothing to be done. None, /// A new stream has been opened by the remote. New(Stream), /// A ping should be answered. Ping(Frame), /// The connection should be terminated. Terminate(Frame), } impl fmt::Debug for Active { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Connection") .field("id", &self.id) .field("mode", &self.mode) .field("streams", &self.streams.len()) .field("next_id", &self.next_id) .finish() } } impl fmt::Display for Active { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "(Connection {} {:?} (streams {}))", self.id, self.mode, self.streams.len() ) } } impl Active { /// Create a new `Connection` from the given I/O resource. fn new(socket: T, cfg: Config, mode: Mode) -> Self { let id = Id::random(); log::debug!("new connection: {} ({:?})", id, mode); let socket = frame::Io::new(id, socket).fuse(); Active { id, mode, config: Arc::new(cfg), socket, streams: IntMap::default(), stream_receivers: SelectAll::default(), no_streams_waker: None, next_id: match mode { Mode::Client => 1, Mode::Server => 2, }, pending_read_frame: None, pending_write_frame: None, new_outbound_stream_waker: None, rtt: rtt::Rtt::new(), accumulated_max_stream_windows: Default::default(), } } /// Gracefully close the connection to the remote. fn close(self) -> Closing { let pending_frames = self .pending_read_frame .into_iter() .chain(self.pending_write_frame) .collect::>>(); Closing::new(self.stream_receivers, pending_frames, self.socket) } /// Cleanup all our resources. /// /// This should be called in the context of an unrecoverable error on the connection. fn cleanup(mut self, error: ConnectionError) -> Cleanup { self.drop_all_streams(); Cleanup::new(self.stream_receivers, error) } fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { loop { if self.socket.poll_ready_unpin(cx).is_ready() { // Note `next_ping` does not register a waker and thus if not called regularly (idle // connection) no ping is sent. This is deliberate as an idle connection does not // need RTT measurements to increase its stream receive window. if let Some(frame) = self.rtt.next_ping() { self.socket.start_send_unpin(frame.into())?; continue; } // Privilege pending `Pong` and `GoAway` `Frame`s // over `Frame`s from the receivers. if let Some(frame) = self .pending_read_frame .take() .or_else(|| self.pending_write_frame.take()) { self.socket.start_send_unpin(frame)?; continue; } } match self.socket.poll_flush_unpin(cx)? { Poll::Ready(()) => {} Poll::Pending => {} } if self.pending_write_frame.is_none() { match self.stream_receivers.poll_next_unpin(cx) { Poll::Ready(Some((_, Some(StreamCommand::SendFrame(frame))))) => { log::trace!( "{}/{}: sending: {}", self.id, frame.header().stream_id(), frame.header() ); self.pending_write_frame.replace(frame.into()); continue; } Poll::Ready(Some((id, Some(StreamCommand::CloseStream { ack })))) => { log::trace!("{}/{}: sending close", self.id, id); self.pending_write_frame .replace(Frame::close_stream(id, ack).into()); continue; } Poll::Ready(Some((id, None))) => { if let Some(frame) = self.on_drop_stream(id) { log::trace!("{}/{}: sending: {}", self.id, id, frame.header()); self.pending_write_frame.replace(frame); }; continue; } Poll::Ready(None) => { self.no_streams_waker = Some(cx.waker().clone()); } Poll::Pending => {} } } if self.pending_read_frame.is_none() { match self.socket.poll_next_unpin(cx) { Poll::Ready(Some(frame)) => { match self.on_frame(frame?)? { Action::None => {} Action::New(stream) => { log::trace!("{}: new inbound {} of {}", self.id, stream, self); return Poll::Ready(Ok(stream)); } Action::Ping(f) => { log::trace!("{}/{}: pong", self.id, f.header().stream_id()); self.pending_read_frame.replace(f.into()); } Action::Terminate(f) => { log::trace!("{}: sending term", self.id); self.pending_read_frame.replace(f.into()); } } continue; } Poll::Ready(None) => { return Poll::Ready(Err(ConnectionError::Closed)); } Poll::Pending => {} } } // If we make it this far, at least one of the above must have registered a waker. return Poll::Pending; } } fn poll_new_outbound(&mut self, cx: &mut Context<'_>) -> Poll> { if self.streams.len() >= self.config.max_num_streams { log::error!("{}: maximum number of streams reached", self.id); return Poll::Ready(Err(ConnectionError::TooManyStreams)); } if self.ack_backlog() >= MAX_ACK_BACKLOG { log::debug!("{MAX_ACK_BACKLOG} streams waiting for ACK, registering task for wake-up until remote acknowledges at least one stream"); self.new_outbound_stream_waker = Some(cx.waker().clone()); return Poll::Pending; } log::trace!("{}: creating new outbound stream", self.id); let id = self.next_stream_id()?; let stream = self.make_new_outbound_stream(id); log::debug!("{}: new outbound {} of {}", self.id, stream, self); self.streams.insert(id, stream.clone_shared()); Poll::Ready(Ok(stream)) } fn on_drop_stream(&mut self, stream_id: StreamId) -> Option> { let s = self.streams.remove(&stream_id).expect("stream not found"); log::trace!("{}: removing dropped stream {}", self.id, stream_id); let frame = { let mut shared = s.lock(); let frame = match shared.update_state(self.id, stream_id, State::Closed) { // The stream was dropped without calling `poll_close`. // We reset the stream to inform the remote of the closure. State::Open { .. } => { let mut header = Header::data(stream_id, 0); header.rst(); Some(Frame::new(header)) } // The stream was dropped without calling `poll_close`. // We have already received a FIN from remote and send one // back which closes the stream for good. State::RecvClosed => { let mut header = Header::data(stream_id, 0); header.fin(); Some(Frame::new(header)) } // The stream was properly closed. We already sent our FIN frame. // The remote may be out of credit though and blocked on // writing more data. We may need to reset the stream. State::SendClosed => { // The remote has either still credit or will be given more // due to an enqueued window update or we already have // inbound frames in the socket buffer which will be // processed later. In any case we will reply with an RST in // `Connection::on_data` because the stream will no longer // be known. None } // The stream was properly closed. We already have sent our FIN frame. The // remote end has already done so in the past. State::Closed => None, }; if let Some(w) = shared.reader.take() { w.wake() } if let Some(w) = shared.writer.take() { w.wake() } frame }; frame.map(Into::into) } /// Process the result of reading from the socket. /// /// Unless `frame` is `Ok(Some(_))` we will assume the connection got closed /// and return a corresponding error, which terminates the connection. /// Otherwise we process the frame and potentially return a new `Stream` /// if one was opened by the remote. fn on_frame(&mut self, frame: Frame<()>) -> Result { log::trace!("{}: received: {}", self.id, frame.header()); if frame.header().flags().contains(header::ACK) && matches!(frame.header().tag(), Tag::Data | Tag::WindowUpdate) { let id = frame.header().stream_id(); if let Some(stream) = self.streams.get(&id) { stream .lock() .update_state(self.id, id, State::Open { acknowledged: true }); } if let Some(waker) = self.new_outbound_stream_waker.take() { waker.wake(); } } let action = match frame.header().tag() { Tag::Data => self.on_data(frame.into_data()), Tag::WindowUpdate => self.on_window_update(&frame.into_window_update()), Tag::Ping => self.on_ping(&frame.into_ping()), Tag::GoAway => return Err(ConnectionError::Closed), }; Ok(action) } fn on_data(&mut self, frame: Frame) -> Action { let stream_id = frame.header().stream_id(); if frame.header().flags().contains(header::RST) { // stream reset if let Some(s) = self.streams.get_mut(&stream_id) { let mut shared = s.lock(); shared.update_state(self.id, stream_id, State::Closed); if let Some(w) = shared.reader.take() { w.wake() } if let Some(w) = shared.writer.take() { w.wake() } } return Action::None; } let is_finish = frame.header().flags().contains(header::FIN); // half-close if frame.header().flags().contains(header::SYN) { // new stream if !self.is_valid_remote_id(stream_id, Tag::Data) { log::error!("{}: invalid stream id {}", self.id, stream_id); return Action::Terminate(Frame::protocol_error()); } if frame.body().len() > DEFAULT_CREDIT as usize { log::error!( "{}/{}: 1st body of stream exceeds default credit", self.id, stream_id ); return Action::Terminate(Frame::protocol_error()); } if self.streams.contains_key(&stream_id) { log::error!("{}/{}: stream already exists", self.id, stream_id); return Action::Terminate(Frame::protocol_error()); } if self.streams.len() == self.config.max_num_streams { log::error!("{}: maximum number of streams reached", self.id); return Action::Terminate(Frame::internal_error()); } let stream = self.make_new_inbound_stream(stream_id, DEFAULT_CREDIT); { let mut shared = stream.shared(); if is_finish { shared.update_state(self.id, stream_id, State::RecvClosed); } shared.consume_receive_window(frame.body_len()); shared.buffer.push(frame.into_body()); } self.streams.insert(stream_id, stream.clone_shared()); return Action::New(stream); } if let Some(s) = self.streams.get_mut(&stream_id) { let mut shared = s.lock(); if frame.body_len() > shared.receive_window() { log::error!( "{}/{}: frame body larger than window of stream", self.id, stream_id ); return Action::Terminate(Frame::protocol_error()); } if is_finish { shared.update_state(self.id, stream_id, State::RecvClosed); } shared.consume_receive_window(frame.body_len()); shared.buffer.push(frame.into_body()); if let Some(w) = shared.reader.take() { w.wake() } } else { log::trace!( "{}/{}: data frame for unknown stream, possibly dropped earlier: {:?}", self.id, stream_id, frame ); // We do not consider this a protocol violation and thus do not send a stream reset // because we may still be processing pending `StreamCommand`s of this stream that were // sent before it has been dropped and "garbage collected". Such a stream reset would // interfere with the frames that still need to be sent, causing premature stream // termination for the remote. // // See https://github.com/paritytech/yamux/issues/110 for details. } Action::None } fn on_window_update(&mut self, frame: &Frame) -> Action { let stream_id = frame.header().stream_id(); if frame.header().flags().contains(header::RST) { // stream reset if let Some(s) = self.streams.get_mut(&stream_id) { let mut shared = s.lock(); shared.update_state(self.id, stream_id, State::Closed); if let Some(w) = shared.reader.take() { w.wake() } if let Some(w) = shared.writer.take() { w.wake() } } return Action::None; } let is_finish = frame.header().flags().contains(header::FIN); // half-close if frame.header().flags().contains(header::SYN) { // new stream if !self.is_valid_remote_id(stream_id, Tag::WindowUpdate) { log::error!("{}: invalid stream id {}", self.id, stream_id); return Action::Terminate(Frame::protocol_error()); } if self.streams.contains_key(&stream_id) { log::error!("{}/{}: stream already exists", self.id, stream_id); return Action::Terminate(Frame::protocol_error()); } if self.streams.len() == self.config.max_num_streams { log::error!("{}: maximum number of streams reached", self.id); return Action::Terminate(Frame::protocol_error()); } let credit = frame.header().credit() + DEFAULT_CREDIT; let stream = self.make_new_inbound_stream(stream_id, credit); if is_finish { stream .shared() .update_state(self.id, stream_id, State::RecvClosed); } self.streams.insert(stream_id, stream.clone_shared()); return Action::New(stream); } if let Some(s) = self.streams.get_mut(&stream_id) { let mut shared = s.lock(); shared.increase_send_window_by(frame.header().credit()); if is_finish { shared.update_state(self.id, stream_id, State::RecvClosed); if let Some(w) = shared.reader.take() { w.wake() } } if let Some(w) = shared.writer.take() { w.wake() } } else { log::trace!( "{}/{}: window update for unknown stream, possibly dropped earlier: {:?}", self.id, stream_id, frame ); // We do not consider this a protocol violation and thus do not send a stream reset // because we may still be processing pending `StreamCommand`s of this stream that were // sent before it has been dropped and "garbage collected". Such a stream reset would // interfere with the frames that still need to be sent, causing premature stream // termination for the remote. // // See https://github.com/paritytech/yamux/issues/110 for details. } Action::None } fn on_ping(&mut self, frame: &Frame) -> Action { let stream_id = frame.header().stream_id(); if frame.header().flags().contains(header::ACK) { return self.rtt.handle_pong(frame.nonce()); } if stream_id == CONNECTION_ID || self.streams.contains_key(&stream_id) { let mut hdr = Header::ping(frame.header().nonce()); hdr.ack(); return Action::Ping(Frame::new(hdr)); } log::debug!( "{}/{}: ping for unknown stream, possibly dropped earlier: {:?}", self.id, stream_id, frame ); // We do not consider this a protocol violation and thus do not send a stream reset because // we may still be processing pending `StreamCommand`s of this stream that were sent before // it has been dropped and "garbage collected". Such a stream reset would interfere with the // frames that still need to be sent, causing premature stream termination for the remote. // // See https://github.com/paritytech/yamux/issues/110 for details. Action::None } fn make_new_inbound_stream(&mut self, id: StreamId, credit: u32) -> Stream { let config = self.config.clone(); let (sender, receiver) = mpsc::channel(10); // 10 is an arbitrary number. self.stream_receivers.push(TaggedStream::new(id, receiver)); if let Some(waker) = self.no_streams_waker.take() { waker.wake(); } Stream::new_inbound( id, self.id, config, credit, sender, self.rtt.clone(), self.accumulated_max_stream_windows.clone(), ) } fn make_new_outbound_stream(&mut self, id: StreamId) -> Stream { let config = self.config.clone(); let (sender, receiver) = mpsc::channel(10); // 10 is an arbitrary number. self.stream_receivers.push(TaggedStream::new(id, receiver)); if let Some(waker) = self.no_streams_waker.take() { waker.wake(); } Stream::new_outbound( id, self.id, config, sender, self.rtt.clone(), self.accumulated_max_stream_windows.clone(), ) } fn next_stream_id(&mut self) -> Result { let proposed = StreamId::new(self.next_id); self.next_id = self .next_id .checked_add(2) .ok_or(ConnectionError::NoMoreStreamIds)?; match self.mode { Mode::Client => assert!(proposed.is_client()), Mode::Server => assert!(proposed.is_server()), } Ok(proposed) } /// The ACK backlog is defined as the number of outbound streams that have not yet been acknowledged. fn ack_backlog(&mut self) -> usize { self.streams .iter() // Whether this is an outbound stream. // // Clients use odd IDs and servers use even IDs. // A stream is outbound if: // // - Its ID is odd and we are the client. // - Its ID is even and we are the server. .filter(|(id, _)| match self.mode { Mode::Client => id.is_client(), Mode::Server => id.is_server(), }) .filter(|(_, s)| s.lock().is_pending_ack()) .count() } // Check if the given stream ID is valid w.r.t. the provided tag and our connection mode. fn is_valid_remote_id(&self, id: StreamId, tag: Tag) -> bool { if tag == Tag::Ping || tag == Tag::GoAway { return id.is_session(); } match self.mode { Mode::Client => id.is_server(), Mode::Server => id.is_client(), } } } impl Active { /// Close and drop all `Stream`s and wake any pending `Waker`s. fn drop_all_streams(&mut self) { for (id, s) in self.streams.drain() { let mut shared = s.lock(); shared.update_state(self.id, id, State::Closed); if let Some(w) = shared.reader.take() { w.wake() } if let Some(w) = shared.writer.take() { w.wake() } } } } rust-yamux-yamux-v0.13.3/yamux/src/connection/000077500000000000000000000000001463066673500213745ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/yamux/src/connection/cleanup.rs000066400000000000000000000037351463066673500234010ustar00rootroot00000000000000use crate::connection::StreamCommand; use crate::tagged_stream::TaggedStream; use crate::{ConnectionError, StreamId}; use futures::channel::mpsc; use futures::stream::SelectAll; use futures::StreamExt; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; /// A [`Future`] that cleans up resources in case of an error. #[must_use] pub struct Cleanup { state: State, stream_receivers: SelectAll>>, error: Option, } impl Cleanup { pub(crate) fn new( stream_receivers: SelectAll>>, error: ConnectionError, ) -> Self { Self { state: State::ClosingStreamReceiver, stream_receivers, error: Some(error), } } } impl Future for Cleanup { type Output = ConnectionError; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { match this.state { State::ClosingStreamReceiver => { for stream in this.stream_receivers.iter_mut() { stream.inner_mut().close(); } this.state = State::DrainingStreamReceiver; } State::DrainingStreamReceiver => match this.stream_receivers.poll_next_unpin(cx) { Poll::Ready(Some(cmd)) => { drop(cmd); } Poll::Ready(None) | Poll::Pending => { return Poll::Ready( this.error .take() .expect("to not be called after completion"), ) } }, } } } } #[allow(clippy::enum_variant_names)] enum State { ClosingStreamReceiver, DrainingStreamReceiver, } rust-yamux-yamux-v0.13.3/yamux/src/connection/closing.rs000066400000000000000000000064611463066673500234070ustar00rootroot00000000000000use crate::connection::StreamCommand; use crate::frame::Frame; use crate::tagged_stream::TaggedStream; use crate::Result; use crate::{frame, StreamId}; use futures::channel::mpsc; use futures::stream::{Fuse, SelectAll}; use futures::{ready, AsyncRead, AsyncWrite, SinkExt, StreamExt}; use std::collections::VecDeque; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; /// A [`Future`] that gracefully closes the yamux connection. #[must_use] pub struct Closing { state: State, stream_receivers: SelectAll>>, pending_frames: VecDeque>, socket: Fuse>, } impl Closing where T: AsyncRead + AsyncWrite + Unpin, { pub(crate) fn new( stream_receivers: SelectAll>>, pending_frames: VecDeque>, socket: Fuse>, ) -> Self { Self { state: State::FlushingPendingFrames, stream_receivers, pending_frames, socket, } } } impl Future for Closing where T: AsyncRead + AsyncWrite + Unpin, { type Output = Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { match this.state { State::FlushingPendingFrames => { ready!(this.socket.poll_ready_unpin(cx))?; match this.pending_frames.pop_front() { Some(frame) => this.socket.start_send_unpin(frame)?, None => this.state = State::ClosingStreamReceiver, } } State::ClosingStreamReceiver => { for stream in this.stream_receivers.iter_mut() { stream.inner_mut().close(); } this.state = State::DrainingStreamReceiver; } State::DrainingStreamReceiver => { match this.stream_receivers.poll_next_unpin(cx) { Poll::Ready(Some((_, Some(StreamCommand::SendFrame(frame))))) => { this.pending_frames.push_back(frame.into()); } Poll::Ready(Some((id, Some(StreamCommand::CloseStream { ack })))) => { this.pending_frames .push_back(Frame::close_stream(id, ack).into()); } Poll::Ready(Some((_, None))) => {} Poll::Pending | Poll::Ready(None) => { // No more frames from streams, append `Term` frame and flush them all. this.pending_frames.push_back(Frame::term().into()); this.state = State::ClosingSocket; continue; } } } State::ClosingSocket => { ready!(this.socket.poll_close_unpin(cx))?; return Poll::Ready(Ok(())); } } } } } enum State { FlushingPendingFrames, ClosingStreamReceiver, DrainingStreamReceiver, ClosingSocket, } rust-yamux-yamux-v0.13.3/yamux/src/connection/rtt.rs000066400000000000000000000073721463066673500225640ustar00rootroot00000000000000// Copyright (c) 2023 Protocol Labs. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. //! Connection round-trip time measurement use std::sync::Arc; use parking_lot::Mutex; use web_time::{Duration, Instant}; use crate::connection::Action; use crate::frame::{header::Ping, Frame}; const PING_INTERVAL: Duration = Duration::from_secs(10); #[derive(Clone, Debug)] pub(crate) struct Rtt(Arc>); impl Rtt { pub(crate) fn new() -> Self { Self(Arc::new(Mutex::new(RttInner { rtt: None, state: RttState::Waiting { next: Instant::now(), }, }))) } pub(crate) fn next_ping(&mut self) -> Option> { let state = &mut self.0.lock().state; match state { RttState::AwaitingPong { .. } => return None, RttState::Waiting { next } => { if *next > Instant::now() { return None; } } } let nonce = rand::random(); *state = RttState::AwaitingPong { sent_at: Instant::now(), nonce, }; log::debug!("sending ping {nonce}"); Some(Frame::ping(nonce)) } pub(crate) fn handle_pong(&mut self, received_nonce: u32) -> Action { let inner = &mut self.0.lock(); let (sent_at, expected_nonce) = match inner.state { RttState::Waiting { .. } => { log::error!("received unexpected pong {received_nonce}"); return Action::Terminate(Frame::protocol_error()); } RttState::AwaitingPong { sent_at, nonce } => (sent_at, nonce), }; if received_nonce != expected_nonce { log::error!("received pong with {received_nonce} but expected {expected_nonce}"); return Action::Terminate(Frame::protocol_error()); } let rtt = sent_at.elapsed(); inner.rtt = Some(rtt); log::debug!("received pong {received_nonce}, estimated round-trip-time {rtt:?}"); inner.state = RttState::Waiting { next: Instant::now() + PING_INTERVAL, }; return Action::None; } pub(crate) fn get(&self) -> Option { self.0.lock().rtt } } #[cfg(test)] impl quickcheck::Arbitrary for Rtt { fn arbitrary(g: &mut quickcheck::Gen) -> Self { Self(Arc::new(Mutex::new(RttInner::arbitrary(g)))) } } #[derive(Debug)] #[cfg_attr(test, derive(Clone))] struct RttInner { state: RttState, rtt: Option, } #[cfg(test)] impl quickcheck::Arbitrary for RttInner { fn arbitrary(g: &mut quickcheck::Gen) -> Self { Self { state: RttState::arbitrary(g), rtt: if bool::arbitrary(g) { Some(Duration::arbitrary(g)) } else { None }, } } } #[derive(Debug)] #[cfg_attr(test, derive(Clone))] enum RttState { AwaitingPong { sent_at: Instant, nonce: u32 }, Waiting { next: Instant }, } #[cfg(test)] impl quickcheck::Arbitrary for RttState { fn arbitrary(g: &mut quickcheck::Gen) -> Self { if bool::arbitrary(g) { RttState::AwaitingPong { sent_at: Instant::now(), nonce: u32::arbitrary(g), } } else { RttState::Waiting { next: Instant::now(), } } } } rust-yamux-yamux-v0.13.3/yamux/src/connection/stream.rs000066400000000000000000000407051463066673500232430ustar00rootroot00000000000000// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. use crate::connection::rtt::Rtt; use crate::frame::header::ACK; use crate::{ chunks::Chunks, connection::{self, rtt, StreamCommand}, frame::{ header::{Data, Header, StreamId, WindowUpdate}, Frame, }, Config, DEFAULT_CREDIT, }; use flow_control::FlowController; use futures::{ channel::mpsc, future::Either, io::{AsyncRead, AsyncWrite}, ready, SinkExt, }; use parking_lot::{Mutex, MutexGuard}; use std::{ fmt, io, pin::Pin, sync::Arc, task::{Context, Poll, Waker}, }; mod flow_control; /// The state of a Yamux stream. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum State { /// Open bidirectionally. Open { /// Whether the stream is acknowledged. /// /// For outbound streams, this tracks whether the remote has acknowledged our stream. /// For inbound streams, this tracks whether we have acknowledged the stream to the remote. /// /// This starts out with `false` and is set to `true` when we receive or send an `ACK` flag for this stream. /// We may also directly transition: /// - from `Open` to `RecvClosed` if the remote immediately sends `FIN`. /// - from `Open` to `Closed` if the remote immediately sends `RST`. acknowledged: bool, }, /// Open for incoming messages. SendClosed, /// Open for outgoing messages. RecvClosed, /// Closed (terminal state). Closed, } impl State { /// Can we receive messages over this stream? pub fn can_read(self) -> bool { !matches!(self, State::RecvClosed | State::Closed) } /// Can we send messages over this stream? pub fn can_write(self) -> bool { !matches!(self, State::SendClosed | State::Closed) } } /// Indicate if a flag still needs to be set on an outbound header. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum Flag { /// No flag needs to be set. None, /// The stream was opened lazily, so set the initial SYN flag. Syn, /// The stream still needs acknowledgement, so set the ACK flag. Ack, } /// A multiplexed Yamux stream. /// /// Streams are created either outbound via [`crate::Connection::poll_new_outbound`] /// or inbound via [`crate::Connection::poll_next_inbound`]. /// /// `Stream` implements [`AsyncRead`] and [`AsyncWrite`] and also /// [`futures::stream::Stream`]. pub struct Stream { id: StreamId, conn: connection::Id, config: Arc, sender: mpsc::Sender, flag: Flag, shared: Arc>, } impl fmt::Debug for Stream { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Stream") .field("id", &self.id.val()) .field("connection", &self.conn) .finish() } } impl fmt::Display for Stream { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "(Stream {}/{})", self.conn, self.id.val()) } } impl Stream { pub(crate) fn new_inbound( id: StreamId, conn: connection::Id, config: Arc, send_window: u32, sender: mpsc::Sender, rtt: rtt::Rtt, accumulated_max_stream_windows: Arc>, ) -> Self { Self { id, conn, config: config.clone(), sender, flag: Flag::Ack, shared: Arc::new(Mutex::new(Shared::new( DEFAULT_CREDIT, send_window, accumulated_max_stream_windows, rtt, config, ))), } } pub(crate) fn new_outbound( id: StreamId, conn: connection::Id, config: Arc, sender: mpsc::Sender, rtt: rtt::Rtt, accumulated_max_stream_windows: Arc>, ) -> Self { Self { id, conn, config: config.clone(), sender, flag: Flag::Syn, shared: Arc::new(Mutex::new(Shared::new( DEFAULT_CREDIT, DEFAULT_CREDIT, accumulated_max_stream_windows, rtt, config, ))), } } /// Get this stream's identifier. pub fn id(&self) -> StreamId { self.id } pub fn is_write_closed(&self) -> bool { matches!(self.shared().state(), State::SendClosed) } pub fn is_closed(&self) -> bool { matches!(self.shared().state(), State::Closed) } /// Whether we are still waiting for the remote to acknowledge this stream. pub fn is_pending_ack(&self) -> bool { self.shared().is_pending_ack() } pub(crate) fn shared(&self) -> MutexGuard<'_, Shared> { self.shared.lock() } pub(crate) fn clone_shared(&self) -> Arc> { self.shared.clone() } fn write_zero_err(&self) -> io::Error { let msg = format!("{}/{}: connection is closed", self.conn, self.id); io::Error::new(io::ErrorKind::WriteZero, msg) } /// Set ACK or SYN flag if necessary. fn add_flag(&mut self, header: &mut Header>) { match self.flag { Flag::None => (), Flag::Syn => { header.syn(); self.flag = Flag::None } Flag::Ack => { header.ack(); self.flag = Flag::None } } } /// Send new credit to the sending side via a window update message if /// permitted. fn send_window_update(&mut self, cx: &mut Context) -> Poll> { if !self.shared.lock().state.can_read() { return Poll::Ready(Ok(())); } ready!(self .sender .poll_ready(cx) .map_err(|_| self.write_zero_err())?); let Some(credit) = self.shared.lock().next_window_update() else { return Poll::Ready(Ok(())); }; let mut frame = Frame::window_update(self.id, credit).right(); self.add_flag(frame.header_mut()); let cmd = StreamCommand::SendFrame(frame); self.sender .start_send(cmd) .map_err(|_| self.write_zero_err())?; Poll::Ready(Ok(())) } } /// Byte data produced by the [`futures::stream::Stream`] impl of [`Stream`]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Packet(Vec); impl AsRef<[u8]> for Packet { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } impl futures::stream::Stream for Stream { type Item = io::Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { if !self.config.read_after_close && self.sender.is_closed() { return Poll::Ready(None); } match self.send_window_update(cx) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e))), // Continue reading buffered data even though sending a window update blocked. Poll::Pending => {} } let mut shared = self.shared(); if let Some(bytes) = shared.buffer.pop() { let off = bytes.offset(); let mut vec = bytes.into_vec(); if off != 0 { // This should generally not happen when the stream is used only as // a `futures::stream::Stream` since the whole point of this impl is // to consume chunks atomically. It may perhaps happen when mixing // this impl and the `AsyncRead` one. log::debug!( "{}/{}: chunk has been partially consumed", self.conn, self.id ); vec = vec.split_off(off) } return Poll::Ready(Some(Ok(Packet(vec)))); } // Buffer is empty, let's check if we can expect to read more data. if !shared.state().can_read() { log::debug!("{}/{}: eof", self.conn, self.id); return Poll::Ready(None); // stream has been reset } // Since we have no more data at this point, we want to be woken up // by the connection when more becomes available for us. shared.reader = Some(cx.waker().clone()); Poll::Pending } } // Like the `futures::stream::Stream` impl above, but copies bytes into the // provided mutable slice. impl AsyncRead for Stream { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context, buf: &mut [u8], ) -> Poll> { if !self.config.read_after_close && self.sender.is_closed() { return Poll::Ready(Ok(0)); } match self.send_window_update(cx) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), // Continue reading buffered data even though sending a window update blocked. Poll::Pending => {} } // Copy data from stream buffer. let mut shared = self.shared(); let mut n = 0; while let Some(chunk) = shared.buffer.front_mut() { if chunk.is_empty() { shared.buffer.pop(); continue; } let k = std::cmp::min(chunk.len(), buf.len() - n); buf[n..n + k].copy_from_slice(&chunk.as_ref()[..k]); n += k; chunk.advance(k); if n == buf.len() { break; } } if n > 0 { log::trace!("{}/{}: read {} bytes", self.conn, self.id, n); return Poll::Ready(Ok(n)); } // Buffer is empty, let's check if we can expect to read more data. if !shared.state().can_read() { log::debug!("{}/{}: eof", self.conn, self.id); return Poll::Ready(Ok(0)); // stream has been reset } // Since we have no more data at this point, we want to be woken up // by the connection when more becomes available for us. shared.reader = Some(cx.waker().clone()); Poll::Pending } } impl AsyncWrite for Stream { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { ready!(self .sender .poll_ready(cx) .map_err(|_| self.write_zero_err())?); let body = { let mut shared = self.shared(); if !shared.state().can_write() { log::debug!("{}/{}: can no longer write", self.conn, self.id); return Poll::Ready(Err(self.write_zero_err())); } if shared.send_window() == 0 { log::trace!("{}/{}: no more credit left", self.conn, self.id); shared.writer = Some(cx.waker().clone()); return Poll::Pending; } let k = std::cmp::min( shared.send_window(), buf.len().try_into().unwrap_or(u32::MAX), ); let k = std::cmp::min( k, self.config.split_send_size.try_into().unwrap_or(u32::MAX), ); shared.consume_send_window(k); Vec::from(&buf[..k as usize]) }; let n = body.len(); let mut frame = Frame::data(self.id, body).expect("body <= u32::MAX").left(); self.add_flag(frame.header_mut()); log::trace!("{}/{}: write {} bytes", self.conn, self.id, n); // technically, the frame hasn't been sent yet on the wire but from the perspective of this data structure, we've queued the frame for sending // We are tracking this information: // a) to be consistent with outbound streams // b) to correctly test our behaviour around timing of when ACKs are sent. See `ack_timing.rs` test. if frame.header().flags().contains(ACK) { self.shared() .update_state(self.conn, self.id, State::Open { acknowledged: true }); } let cmd = StreamCommand::SendFrame(frame); self.sender .start_send(cmd) .map_err(|_| self.write_zero_err())?; Poll::Ready(Ok(n)) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.sender .poll_flush_unpin(cx) .map_err(|_| self.write_zero_err()) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { if self.is_closed() { return Poll::Ready(Ok(())); } ready!(self .sender .poll_ready(cx) .map_err(|_| self.write_zero_err())?); let ack = if self.flag == Flag::Ack { self.flag = Flag::None; true } else { false }; log::trace!("{}/{}: close", self.conn, self.id); let cmd = StreamCommand::CloseStream { ack }; self.sender .start_send(cmd) .map_err(|_| self.write_zero_err())?; self.shared() .update_state(self.conn, self.id, State::SendClosed); Poll::Ready(Ok(())) } } #[derive(Debug)] pub(crate) struct Shared { state: State, flow_controller: FlowController, pub(crate) buffer: Chunks, pub(crate) reader: Option, pub(crate) writer: Option, } impl Shared { fn new( receive_window: u32, send_window: u32, accumulated_max_stream_windows: Arc>, rtt: Rtt, config: Arc, ) -> Self { Shared { state: State::Open { acknowledged: false, }, flow_controller: FlowController::new( receive_window, send_window, accumulated_max_stream_windows, rtt, config, ), buffer: Chunks::new(), reader: None, writer: None, } } pub(crate) fn state(&self) -> State { self.state } /// Update the stream state and return the state before it was updated. pub(crate) fn update_state( &mut self, cid: connection::Id, sid: StreamId, next: State, ) -> State { use self::State::*; let current = self.state; match (current, next) { (Closed, _) => {} (Open { .. }, _) => self.state = next, (RecvClosed, Closed) => self.state = Closed, (RecvClosed, Open { .. }) => {} (RecvClosed, RecvClosed) => {} (RecvClosed, SendClosed) => self.state = Closed, (SendClosed, Closed) => self.state = Closed, (SendClosed, Open { .. }) => {} (SendClosed, RecvClosed) => self.state = Closed, (SendClosed, SendClosed) => {} } log::trace!( "{}/{}: update state: (from {:?} to {:?} -> {:?})", cid, sid, current, next, self.state ); current // Return the previous stream state for informational purposes. } pub(crate) fn next_window_update(&mut self) -> Option { self.flow_controller.next_window_update(self.buffer.len()) } /// Whether we are still waiting for the remote to acknowledge this stream. pub fn is_pending_ack(&self) -> bool { matches!( self.state(), State::Open { acknowledged: false } ) } pub(crate) fn send_window(&self) -> u32 { self.flow_controller.send_window() } pub(crate) fn consume_send_window(&mut self, i: u32) { self.flow_controller.consume_send_window(i) } pub(crate) fn increase_send_window_by(&mut self, i: u32) { self.flow_controller.increase_send_window_by(i) } pub(crate) fn receive_window(&self) -> u32 { self.flow_controller.receive_window() } pub(crate) fn consume_receive_window(&mut self, i: u32) { self.flow_controller.consume_receive_window(i) } } rust-yamux-yamux-v0.13.3/yamux/src/connection/stream/000077500000000000000000000000001463066673500226675ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/yamux/src/connection/stream/flow_control.rs000066400000000000000000000257521463066673500257570ustar00rootroot00000000000000use std::{cmp, sync::Arc}; use parking_lot::Mutex; use web_time::Instant; use crate::{connection::rtt::Rtt, Config, DEFAULT_CREDIT}; #[derive(Debug)] pub(crate) struct FlowController { config: Arc, last_window_update: Instant, /// See [`Connection::rtt`]. rtt: Rtt, /// See [`Connection::accumulated_max_stream_windows`]. accumulated_max_stream_windows: Arc>, receive_window: u32, max_receive_window: u32, send_window: u32, } impl FlowController { pub(crate) fn new( receive_window: u32, send_window: u32, accumulated_max_stream_windows: Arc>, rtt: Rtt, config: Arc, ) -> Self { Self { receive_window, send_window, config, rtt, accumulated_max_stream_windows, max_receive_window: DEFAULT_CREDIT, last_window_update: Instant::now(), } } /// Calculate the number of additional window bytes the receiving side (local) should grant the /// sending side (remote) via a window update message. /// /// Returns `None` if too small to justify a window update message. pub(crate) fn next_window_update(&mut self, buffer_len: usize) -> Option { self.assert_invariants(buffer_len); let bytes_received = self.max_receive_window - self.receive_window; let mut next_window_update = bytes_received.saturating_sub(buffer_len.try_into().unwrap_or(u32::MAX)); // Don't send an update in case half or more of the window is still available to the sender. if next_window_update < self.max_receive_window / 2 { return None; } log::trace!( "received {} mb in {} seconds ({} mbit/s)", next_window_update as f64 / crate::MIB as f64, self.last_window_update.elapsed().as_secs_f64(), next_window_update as f64 / crate::MIB as f64 * 8.0 / self.last_window_update.elapsed().as_secs_f64() ); // Auto-tuning `max_receive_window` // // The ideal `max_receive_window` is equal to the bandwidth-delay-product (BDP), thus // allowing the remote sender to exhaust the entire available bandwidth on a single stream. // Choosing `max_receive_window` too small prevents the remote sender from exhausting the // available bandwidth. Choosing `max_receive_window` to large is wasteful and delays // backpressure from the receiver to the sender on the stream. // // In case the remote sender has exhausted half or more of its credit in less than 2 // round-trips, try to double `max_receive_window`. // // For simplicity `max_receive_window` is never decreased. // // This implementation is heavily influenced by QUIC. See document below for rational on the // above strategy. // // https://docs.google.com/document/d/1F2YfdDXKpy20WVKJueEf4abn_LVZHhMUMS5gX6Pgjl4/edit?usp=sharing if self .rtt .get() .map(|rtt| self.last_window_update.elapsed() < rtt * 2) .unwrap_or(false) { let mut accumulated_max_stream_windows = self.accumulated_max_stream_windows.lock(); // Ideally one can just double it: let new_max = self.max_receive_window.saturating_mul(2); // But one has to consider the configured connection limit: let new_max = { let connection_limit: usize = self.max_receive_window as usize + // the overall configured conneciton limit (self.config.max_connection_receive_window.unwrap_or(usize::MAX) // minus the minimum amount of window guaranteed to each stream - self.config.max_num_streams * DEFAULT_CREDIT as usize // minus the amount of bytes beyond the minimum amount (`DEFAULT_CREDIT`) // already allocated by this and other streams on the connection. - *accumulated_max_stream_windows); cmp::min(new_max, connection_limit.try_into().unwrap_or(u32::MAX)) }; // Account for the additional credit on the accumulated connection counter. *accumulated_max_stream_windows += (new_max - self.max_receive_window) as usize; drop(accumulated_max_stream_windows); log::debug!( "old window_max: {} mb, new window_max: {} mb", self.max_receive_window as f64 / crate::MIB as f64, new_max as f64 / crate::MIB as f64 ); self.max_receive_window = new_max; // Recalculate `next_window_update` with the new `max_receive_window`. let bytes_received = self.max_receive_window - self.receive_window; next_window_update = bytes_received.saturating_sub(buffer_len.try_into().unwrap_or(u32::MAX)); } self.last_window_update = Instant::now(); self.receive_window += next_window_update; self.assert_invariants(buffer_len); return Some(next_window_update); } fn assert_invariants(&self, buffer_len: usize) { if !cfg!(debug_assertions) { return; } let config = &self.config; let rtt = self.rtt.get(); let accumulated_max_stream_windows = *self.accumulated_max_stream_windows.lock(); assert!( buffer_len <= self.max_receive_window as usize, "The current buffer size never exceeds the maximum stream receive window." ); assert!( self.receive_window <= self.max_receive_window, "The current window never exceeds the maximum." ); assert!( (self.max_receive_window - DEFAULT_CREDIT) as usize <= config.max_connection_receive_window.unwrap_or(usize::MAX) - config.max_num_streams * DEFAULT_CREDIT as usize, "The maximum never exceeds its maximum portion of the configured connection limit." ); assert!( (self.max_receive_window - DEFAULT_CREDIT) as usize <= accumulated_max_stream_windows, "The amount by which the stream maximum exceeds DEFAULT_CREDIT is tracked in accumulated_max_stream_windows." ); if rtt.is_none() { assert_eq!( self.max_receive_window, DEFAULT_CREDIT, "The maximum is only increased iff an rtt measurement is available." ); } } pub(crate) fn send_window(&self) -> u32 { self.send_window } pub(crate) fn consume_send_window(&mut self, i: u32) { self.send_window = self .send_window .checked_sub(i) .expect("not exceed send window"); } pub(crate) fn increase_send_window_by(&mut self, i: u32) { self.send_window = self .send_window .checked_add(i) .expect("send window not to exceed u32"); } pub(crate) fn receive_window(&self) -> u32 { self.receive_window } pub(crate) fn consume_receive_window(&mut self, i: u32) { self.receive_window = self .receive_window .checked_sub(i) .expect("not exceed receive window"); } } impl Drop for FlowController { fn drop(&mut self) { let mut accumulated_max_stream_windows = self.accumulated_max_stream_windows.lock(); debug_assert!( *accumulated_max_stream_windows >= (self.max_receive_window - DEFAULT_CREDIT) as usize, "{accumulated_max_stream_windows} {}", self.max_receive_window ); *accumulated_max_stream_windows -= (self.max_receive_window - DEFAULT_CREDIT) as usize; } } #[cfg(test)] mod tests { use super::*; use quickcheck::{GenRange, QuickCheck}; use web_time::Duration; #[derive(Debug)] struct Input { controller: FlowController, buffer_len: usize, } #[cfg(test)] impl Clone for Input { fn clone(&self) -> Self { Self { controller: FlowController { config: self.controller.config.clone(), accumulated_max_stream_windows: Arc::new(Mutex::new( self.controller .accumulated_max_stream_windows .lock() .clone(), )), rtt: self.controller.rtt.clone(), last_window_update: self.controller.last_window_update.clone(), receive_window: self.controller.receive_window, max_receive_window: self.controller.max_receive_window, send_window: self.controller.send_window, }, buffer_len: self.buffer_len, } } } impl quickcheck::Arbitrary for Input { fn arbitrary(g: &mut quickcheck::Gen) -> Self { let config = Arc::new(Config::arbitrary(g)); let rtt = Rtt::arbitrary(g); let max_connection_minus_default = config.max_connection_receive_window.unwrap_or(usize::MAX) - (config.max_num_streams * (DEFAULT_CREDIT as usize)); let max_receive_window = if rtt.get().is_none() { DEFAULT_CREDIT } else { g.gen_range( DEFAULT_CREDIT ..(DEFAULT_CREDIT as usize) .saturating_add(max_connection_minus_default) .try_into() .unwrap_or(u32::MAX) .saturating_add(1), ) }; let receive_window = g.gen_range(0..max_receive_window); let buffer_len = g.gen_range(0..max_receive_window as usize); let accumulated_max_stream_windows = Arc::new(Mutex::new(g.gen_range( (max_receive_window - DEFAULT_CREDIT) as usize ..max_connection_minus_default.saturating_add(1), ))); let last_window_update = Instant::now() - Duration::from_secs(g.gen_range(0..(60 * 60 * 24))); let send_window = g.gen_range(0..u32::MAX); Self { controller: FlowController { accumulated_max_stream_windows, rtt, last_window_update, config, receive_window, max_receive_window, send_window, }, buffer_len, } } } #[test] fn next_window_update() { fn property( Input { mut controller, buffer_len, }: Input, ) { controller.next_window_update(buffer_len); } QuickCheck::new().quickcheck(property as fn(_)) } } rust-yamux-yamux-v0.13.3/yamux/src/error.rs000066400000000000000000000051401463066673500207340ustar00rootroot00000000000000// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. use crate::frame::FrameDecodeError; /// The various error cases a connection may encounter. #[non_exhaustive] #[derive(Debug)] pub enum ConnectionError { /// An underlying I/O error occured. Io(std::io::Error), /// Decoding a Yamux message frame failed. Decode(FrameDecodeError), /// The whole range of stream IDs has been used up. NoMoreStreamIds, /// An operation fails because the connection is closed. Closed, /// Too many streams are open, so no further ones can be opened at this time. TooManyStreams, } impl std::fmt::Display for ConnectionError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { ConnectionError::Io(e) => write!(f, "i/o error: {}", e), ConnectionError::Decode(e) => write!(f, "decode error: {}", e), ConnectionError::NoMoreStreamIds => { f.write_str("number of stream ids has been exhausted") } ConnectionError::Closed => f.write_str("connection is closed"), ConnectionError::TooManyStreams => f.write_str("maximum number of streams reached"), } } } impl std::error::Error for ConnectionError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { ConnectionError::Io(e) => Some(e), ConnectionError::Decode(e) => Some(e), ConnectionError::NoMoreStreamIds | ConnectionError::Closed | ConnectionError::TooManyStreams => None, } } } impl From for ConnectionError { fn from(e: std::io::Error) -> Self { ConnectionError::Io(e) } } impl From for ConnectionError { fn from(e: FrameDecodeError) -> Self { ConnectionError::Decode(e) } } impl From for ConnectionError { fn from(_: futures::channel::mpsc::SendError) -> Self { ConnectionError::Closed } } impl From for ConnectionError { fn from(_: futures::channel::oneshot::Canceled) -> Self { ConnectionError::Closed } } rust-yamux-yamux-v0.13.3/yamux/src/frame.rs000066400000000000000000000077361463066673500207120ustar00rootroot00000000000000// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. pub mod header; mod io; use futures::future::Either; use header::{Data, GoAway, Header, Ping, StreamId, WindowUpdate}; use std::{convert::TryInto, num::TryFromIntError}; pub use io::FrameDecodeError; pub(crate) use io::Io; /// A Yamux message frame consisting of header and body. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Frame { header: Header, body: Vec, } impl Frame { pub fn new(header: Header) -> Self { Frame { header, body: Vec::new(), } } pub fn header(&self) -> &Header { &self.header } pub fn header_mut(&mut self) -> &mut Header { &mut self.header } /// Introduce this frame to the right of a binary frame type. pub(crate) fn right(self) -> Frame> { Frame { header: self.header.right(), body: self.body, } } /// Introduce this frame to the left of a binary frame type. pub(crate) fn left(self) -> Frame> { Frame { header: self.header.left(), body: self.body, } } } impl From> for Frame<()> { fn from(f: Frame) -> Frame<()> { Frame { header: f.header.into(), body: f.body, } } } impl Frame<()> { pub(crate) fn into_data(self) -> Frame { Frame { header: self.header.into_data(), body: self.body, } } pub(crate) fn into_window_update(self) -> Frame { Frame { header: self.header.into_window_update(), body: self.body, } } pub(crate) fn into_ping(self) -> Frame { Frame { header: self.header.into_ping(), body: self.body, } } } impl Frame { pub fn data(id: StreamId, b: Vec) -> Result { Ok(Frame { header: Header::data(id, b.len().try_into()?), body: b, }) } pub fn close_stream(id: StreamId, ack: bool) -> Self { let mut header = Header::data(id, 0); header.fin(); if ack { header.ack() } Frame::new(header) } pub fn body(&self) -> &[u8] { &self.body } pub fn body_len(&self) -> u32 { // Safe cast since we construct `Frame::`s only with // `Vec` of length [0, u32::MAX] in `Frame::data` above. self.body().len() as u32 } pub fn into_body(self) -> Vec { self.body } } impl Frame { pub fn window_update(id: StreamId, credit: u32) -> Self { Frame { header: Header::window_update(id, credit), body: Vec::new(), } } } impl Frame { pub fn ping(nonce: u32) -> Self { let mut header = Header::ping(nonce); header.syn(); Frame { header, body: Vec::new(), } } pub fn nonce(&self) -> u32 { self.header.nonce() } } impl Frame { pub fn term() -> Self { Frame { header: Header::term(), body: Vec::new(), } } pub fn protocol_error() -> Self { Frame { header: Header::protocol_error(), body: Vec::new(), } } pub fn internal_error() -> Self { Frame { header: Header::internal_error(), body: Vec::new(), } } } rust-yamux-yamux-v0.13.3/yamux/src/frame/000077500000000000000000000000001463066673500203275ustar00rootroot00000000000000rust-yamux-yamux-v0.13.3/yamux/src/frame/header.rs000066400000000000000000000254421463066673500221340ustar00rootroot00000000000000// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. use futures::future::Either; use std::fmt; /// The message frame header. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Header { version: Version, tag: Tag, flags: Flags, stream_id: StreamId, length: Len, _marker: std::marker::PhantomData, } impl fmt::Display for Header { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "(Header {:?} {} (len {}) (flags {:?}))", self.tag, self.stream_id, self.length.val(), self.flags.val() ) } } impl Header { pub fn tag(&self) -> Tag { self.tag } pub fn flags(&self) -> Flags { self.flags } pub fn stream_id(&self) -> StreamId { self.stream_id } pub fn len(&self) -> Len { self.length } #[cfg(test)] pub fn set_len(&mut self, len: u32) { self.length = Len(len) } /// Arbitrary type cast, use with caution. fn cast(self) -> Header { Header { version: self.version, tag: self.tag, flags: self.flags, stream_id: self.stream_id, length: self.length, _marker: std::marker::PhantomData, } } /// Introduce this header to the right of a binary header type. pub(crate) fn right(self) -> Header> { self.cast() } /// Introduce this header to the left of a binary header type. pub(crate) fn left(self) -> Header> { self.cast() } } impl From> for Header<()> { fn from(h: Header) -> Header<()> { h.cast() } } impl Header<()> { pub(crate) fn into_data(self) -> Header { debug_assert_eq!(self.tag, Tag::Data); self.cast() } pub(crate) fn into_window_update(self) -> Header { debug_assert_eq!(self.tag, Tag::WindowUpdate); self.cast() } pub(crate) fn into_ping(self) -> Header { debug_assert_eq!(self.tag, Tag::Ping); self.cast() } } impl Header { /// Set the [`SYN`] flag. pub fn syn(&mut self) { self.flags.0 |= SYN.0 } } impl Header { /// Set the [`ACK`] flag. pub fn ack(&mut self) { self.flags.0 |= ACK.0 } } impl Header { /// Set the [`FIN`] flag. pub fn fin(&mut self) { self.flags.0 |= FIN.0 } } impl Header { /// Set the [`RST`] flag. pub fn rst(&mut self) { self.flags.0 |= RST.0 } } impl Header { /// Create a new data frame header. pub fn data(id: StreamId, len: u32) -> Self { Header { version: Version(0), tag: Tag::Data, flags: Flags(0), stream_id: id, length: Len(len), _marker: std::marker::PhantomData, } } } impl Header { /// Create a new window update frame header. pub fn window_update(id: StreamId, credit: u32) -> Self { Header { version: Version(0), tag: Tag::WindowUpdate, flags: Flags(0), stream_id: id, length: Len(credit), _marker: std::marker::PhantomData, } } /// The credit this window update grants to the remote. pub fn credit(&self) -> u32 { self.length.0 } } impl Header { /// Create a new ping frame header. pub fn ping(nonce: u32) -> Self { Header { version: Version(0), tag: Tag::Ping, flags: Flags(0), stream_id: StreamId(0), length: Len(nonce), _marker: std::marker::PhantomData, } } /// The nonce of this ping. pub fn nonce(&self) -> u32 { self.length.0 } } impl Header { /// Terminate the session without indicating an error to the remote. pub fn term() -> Self { Self::go_away(0) } /// Terminate the session indicating a protocol error to the remote. pub fn protocol_error() -> Self { Self::go_away(1) } /// Terminate the session indicating an internal error to the remote. pub fn internal_error() -> Self { Self::go_away(2) } fn go_away(code: u32) -> Self { Header { version: Version(0), tag: Tag::GoAway, flags: Flags(0), stream_id: StreamId(0), length: Len(code), _marker: std::marker::PhantomData, } } } /// Data message type. #[derive(Clone, Debug)] pub enum Data {} /// Window update message type. #[derive(Clone, Debug)] pub enum WindowUpdate {} /// Ping message type. #[derive(Clone, Debug)] pub enum Ping {} /// Go Away message type. #[derive(Clone, Debug)] pub enum GoAway {} /// Types which have a `syn` method. pub trait HasSyn: private::Sealed {} impl HasSyn for Data {} impl HasSyn for WindowUpdate {} impl HasSyn for Ping {} impl HasSyn for Either {} /// Types which have an `ack` method. pub trait HasAck: private::Sealed {} impl HasAck for Data {} impl HasAck for WindowUpdate {} impl HasAck for Ping {} impl HasAck for Either {} /// Types which have a `fin` method. pub trait HasFin: private::Sealed {} impl HasFin for Data {} impl HasFin for WindowUpdate {} /// Types which have a `rst` method. pub trait HasRst: private::Sealed {} impl HasRst for Data {} impl HasRst for WindowUpdate {} pub(super) mod private { pub trait Sealed {} impl Sealed for super::Data {} impl Sealed for super::WindowUpdate {} impl Sealed for super::Ping {} impl Sealed for super::GoAway {} impl Sealed for super::Either {} } /// A tag is the runtime representation of a message type. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Tag { Data, WindowUpdate, Ping, GoAway, } /// The protocol version a message corresponds to. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Version(u8); /// The message length. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Len(u32); impl Len { pub fn val(self) -> u32 { self.0 } } pub const CONNECTION_ID: StreamId = StreamId(0); /// The ID of a stream. /// /// The value 0 denotes no particular stream but the whole session. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct StreamId(u32); impl StreamId { pub(crate) fn new(val: u32) -> Self { StreamId(val) } pub fn is_server(self) -> bool { self.0 % 2 == 0 } pub fn is_client(self) -> bool { !self.is_server() } pub fn is_session(self) -> bool { self == CONNECTION_ID } pub fn val(self) -> u32 { self.0 } } impl fmt::Display for StreamId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } impl nohash_hasher::IsEnabled for StreamId {} /// Possible flags set on a message. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Flags(u16); impl Flags { pub fn contains(self, other: Flags) -> bool { self.0 & other.0 == other.0 } pub fn val(self) -> u16 { self.0 } } /// Indicates the start of a new stream. pub const SYN: Flags = Flags(1); /// Acknowledges the start of a new stream. pub const ACK: Flags = Flags(2); /// Indicates the half-closing of a stream. pub const FIN: Flags = Flags(4); /// Indicates an immediate stream reset. pub const RST: Flags = Flags(8); /// The serialised header size in bytes. pub const HEADER_SIZE: usize = 12; /// Encode a [`Header`] value. pub fn encode(hdr: &Header) -> [u8; HEADER_SIZE] { let mut buf = [0; HEADER_SIZE]; buf[0] = hdr.version.0; buf[1] = hdr.tag as u8; buf[2..4].copy_from_slice(&hdr.flags.0.to_be_bytes()); buf[4..8].copy_from_slice(&hdr.stream_id.0.to_be_bytes()); buf[8..HEADER_SIZE].copy_from_slice(&hdr.length.0.to_be_bytes()); buf } /// Decode a [`Header`] value. pub fn decode(buf: &[u8; HEADER_SIZE]) -> Result, HeaderDecodeError> { if buf[0] != 0 { return Err(HeaderDecodeError::Version(buf[0])); } let hdr = Header { version: Version(buf[0]), tag: match buf[1] { 0 => Tag::Data, 1 => Tag::WindowUpdate, 2 => Tag::Ping, 3 => Tag::GoAway, t => return Err(HeaderDecodeError::Type(t)), }, flags: Flags(u16::from_be_bytes([buf[2], buf[3]])), stream_id: StreamId(u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]])), length: Len(u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]])), _marker: std::marker::PhantomData, }; Ok(hdr) } /// Possible errors while decoding a message frame header. #[non_exhaustive] #[derive(Debug)] pub enum HeaderDecodeError { /// Unknown version. Version(u8), /// An unknown frame type. Type(u8), } impl std::fmt::Display for HeaderDecodeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { HeaderDecodeError::Version(v) => write!(f, "unknown version: {}", v), HeaderDecodeError::Type(t) => write!(f, "unknown frame type: {}", t), } } } impl std::error::Error for HeaderDecodeError {} #[cfg(test)] mod tests { use super::*; use quickcheck::{Arbitrary, Gen, QuickCheck}; impl Arbitrary for Header<()> { fn arbitrary(g: &mut Gen) -> Self { let tag = *g .choose(&[Tag::Data, Tag::WindowUpdate, Tag::Ping, Tag::GoAway]) .unwrap(); Header { version: Version(0), tag, flags: Flags(Arbitrary::arbitrary(g)), stream_id: StreamId(Arbitrary::arbitrary(g)), length: Len(Arbitrary::arbitrary(g)), _marker: std::marker::PhantomData, } } } #[test] fn encode_decode_identity() { fn property(hdr: Header<()>) -> bool { match decode(&encode(&hdr)) { Ok(x) => x == hdr, Err(e) => { eprintln!("decode error: {}", e); false } } } QuickCheck::new() .tests(10_000) .quickcheck(property as fn(Header<()>) -> bool) } } rust-yamux-yamux-v0.13.3/yamux/src/frame/io.rs000066400000000000000000000303741463066673500213130ustar00rootroot00000000000000// Copyright (c) 2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. use super::{ header::{self, HeaderDecodeError}, Frame, }; use crate::connection::Id; use futures::{prelude::*, ready}; use std::{ fmt, io, pin::Pin, task::{Context, Poll}, }; /// Maximum Yamux frame body length /// /// Limits the amount of bytes a remote can cause the local node to allocate at once when reading. /// /// Chosen based on intuition in past iterations. const MAX_FRAME_BODY_LEN: usize = 1 * crate::MIB; /// A [`Stream`] and writer of [`Frame`] values. #[derive(Debug)] pub(crate) struct Io { id: Id, io: T, read_state: ReadState, write_state: WriteState, } impl Io { pub(crate) fn new(id: Id, io: T) -> Self { Io { id, io, read_state: ReadState::Init, write_state: WriteState::Init, } } } /// The stages of writing a new `Frame`. enum WriteState { Init, Header { header: [u8; header::HEADER_SIZE], buffer: Vec, offset: usize, }, Body { buffer: Vec, offset: usize, }, } impl fmt::Debug for WriteState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { WriteState::Init => f.write_str("(WriteState::Init)"), WriteState::Header { offset, .. } => { write!(f, "(WriteState::Header (offset {}))", offset) } WriteState::Body { offset, buffer } => { write!( f, "(WriteState::Body (offset {}) (buffer-len {}))", offset, buffer.len() ) } } } } impl Sink> for Io { type Error = io::Error; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = Pin::into_inner(self); loop { log::trace!("{}: write: {:?}", this.id, this.write_state); match &mut this.write_state { WriteState::Init => return Poll::Ready(Ok(())), WriteState::Header { header, buffer, ref mut offset, } => match Pin::new(&mut this.io).poll_write(cx, &header[*offset..]) { Poll::Pending => return Poll::Pending, Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), Poll::Ready(Ok(n)) => { if n == 0 { return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); } *offset += n; if *offset == header.len() { if !buffer.is_empty() { let buffer = std::mem::take(buffer); this.write_state = WriteState::Body { buffer, offset: 0 }; } else { this.write_state = WriteState::Init; } } } }, WriteState::Body { buffer, ref mut offset, } => match Pin::new(&mut this.io).poll_write(cx, &buffer[*offset..]) { Poll::Pending => return Poll::Pending, Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), Poll::Ready(Ok(n)) => { if n == 0 { return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); } *offset += n; if *offset == buffer.len() { this.write_state = WriteState::Init; } } }, } } } fn start_send(self: Pin<&mut Self>, f: Frame<()>) -> Result<(), Self::Error> { let header = header::encode(&f.header); let buffer = f.body; self.get_mut().write_state = WriteState::Header { header, buffer, offset: 0, }; Ok(()) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = Pin::into_inner(self); ready!(this.poll_ready_unpin(cx))?; Pin::new(&mut this.io).poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = Pin::into_inner(self); ready!(this.poll_ready_unpin(cx))?; Pin::new(&mut this.io).poll_close(cx) } } /// The stages of reading a new `Frame`. enum ReadState { /// Initial reading state. Init, /// Reading the frame header. Header { offset: usize, buffer: [u8; header::HEADER_SIZE], }, /// Reading the frame body. Body { header: header::Header<()>, offset: usize, buffer: Vec, }, } impl Stream for Io { type Item = Result, FrameDecodeError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = &mut *self; loop { log::trace!("{}: read: {:?}", this.id, this.read_state); match this.read_state { ReadState::Init => { this.read_state = ReadState::Header { offset: 0, buffer: [0; header::HEADER_SIZE], }; } ReadState::Header { ref mut offset, ref mut buffer, } => { if *offset == header::HEADER_SIZE { let header = match header::decode(buffer) { Ok(hd) => hd, Err(e) => return Poll::Ready(Some(Err(e.into()))), }; log::trace!("{}: read: {}", this.id, header); if header.tag() != header::Tag::Data { this.read_state = ReadState::Init; return Poll::Ready(Some(Ok(Frame::new(header)))); } let body_len = header.len().val() as usize; if body_len > MAX_FRAME_BODY_LEN { return Poll::Ready(Some(Err(FrameDecodeError::FrameTooLarge( body_len, )))); } this.read_state = ReadState::Body { header, offset: 0, buffer: vec![0; body_len], }; continue; } let buf = &mut buffer[*offset..header::HEADER_SIZE]; match ready!(Pin::new(&mut this.io).poll_read(cx, buf))? { 0 => { if *offset == 0 { return Poll::Ready(None); } let e = FrameDecodeError::Io(io::ErrorKind::UnexpectedEof.into()); return Poll::Ready(Some(Err(e))); } n => *offset += n, } } ReadState::Body { ref header, ref mut offset, ref mut buffer, } => { let body_len = header.len().val() as usize; if *offset == body_len { let h = header.clone(); let v = std::mem::take(buffer); this.read_state = ReadState::Init; return Poll::Ready(Some(Ok(Frame { header: h, body: v }))); } let buf = &mut buffer[*offset..body_len]; match ready!(Pin::new(&mut this.io).poll_read(cx, buf))? { 0 => { let e = FrameDecodeError::Io(io::ErrorKind::UnexpectedEof.into()); return Poll::Ready(Some(Err(e))); } n => *offset += n, } } } } } } impl fmt::Debug for ReadState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ReadState::Init => f.write_str("(ReadState::Init)"), ReadState::Header { offset, .. } => { write!(f, "(ReadState::Header (offset {}))", offset) } ReadState::Body { header, offset, buffer, } => { write!( f, "(ReadState::Body (header {}) (offset {}) (buffer-len {}))", header, offset, buffer.len() ) } } } } /// Possible errors while decoding a message frame. #[non_exhaustive] #[derive(Debug)] pub enum FrameDecodeError { /// An I/O error. Io(io::Error), /// Decoding the frame header failed. Header(HeaderDecodeError), /// A data frame body length is larger than the configured maximum. FrameTooLarge(usize), } impl std::fmt::Display for FrameDecodeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { FrameDecodeError::Io(e) => write!(f, "i/o error: {}", e), FrameDecodeError::Header(e) => write!(f, "decode error: {}", e), FrameDecodeError::FrameTooLarge(n) => write!(f, "frame body is too large ({})", n), } } } impl std::error::Error for FrameDecodeError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { FrameDecodeError::Io(e) => Some(e), FrameDecodeError::Header(e) => Some(e), FrameDecodeError::FrameTooLarge(_) => None, } } } impl From for FrameDecodeError { fn from(e: std::io::Error) -> Self { FrameDecodeError::Io(e) } } impl From for FrameDecodeError { fn from(e: HeaderDecodeError) -> Self { FrameDecodeError::Header(e) } } #[cfg(test)] mod tests { use super::*; use quickcheck::{Arbitrary, Gen, QuickCheck}; use rand::RngCore; impl Arbitrary for Frame<()> { fn arbitrary(g: &mut Gen) -> Self { let mut header: header::Header<()> = Arbitrary::arbitrary(g); let body = if header.tag() == header::Tag::Data { header.set_len(header.len().val() % 4096); let mut b = vec![0; header.len().val() as usize]; rand::thread_rng().fill_bytes(&mut b); b } else { Vec::new() }; Frame { header, body } } } #[test] fn encode_decode_identity() { fn property(f: Frame<()>) -> bool { futures::executor::block_on(async move { let id = crate::connection::Id::random(); let mut io = Io::new(id, futures::io::Cursor::new(Vec::new())); if io.send(f.clone()).await.is_err() { return false; } if io.flush().await.is_err() { return false; } io.io.set_position(0); if let Ok(Some(x)) = io.try_next().await { x == f } else { false } }) } QuickCheck::new() .tests(10_000) .quickcheck(property as fn(Frame<()>) -> bool) } } rust-yamux-yamux-v0.13.3/yamux/src/lib.rs000066400000000000000000000162661463066673500203640ustar00rootroot00000000000000// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 or MIT license, at your option. // // A copy of the Apache License, Version 2.0 is included in the software as // LICENSE-APACHE and a copy of the MIT license is included in the software // as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0 // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. //! This crate implements the [Yamux specification][1]. //! //! It multiplexes independent I/O streams over reliable, ordered connections, //! such as TCP/IP. //! //! The two primary objects, clients of this crate interact with, are: //! //! - [`Connection`], which wraps the underlying I/O resource, e.g. a socket, and //! provides methods for opening outbound or accepting inbound streams. //! - [`Stream`], which implements [`futures::io::AsyncRead`] and //! [`futures::io::AsyncWrite`]. //! //! [1]: https://github.com/hashicorp/yamux/blob/master/spec.md #![forbid(unsafe_code)] mod chunks; mod error; mod frame; pub(crate) mod connection; mod tagged_stream; pub use crate::connection::{Connection, Mode, Packet, Stream}; pub use crate::error::ConnectionError; pub use crate::frame::{ header::{HeaderDecodeError, StreamId}, FrameDecodeError, }; const KIB: usize = 1024; const MIB: usize = KIB * 1024; const GIB: usize = MIB * 1024; pub const DEFAULT_CREDIT: u32 = 256 * KIB as u32; // as per yamux specification pub type Result = std::result::Result; /// The maximum number of streams we will open without an acknowledgement from the other peer. /// /// This enables a very basic form of backpressure on the creation of streams. const MAX_ACK_BACKLOG: usize = 256; /// Default maximum number of bytes a Yamux data frame might carry as its /// payload when being send. Larger Payloads will be split. /// /// The data frame payload size is not restricted by the yamux specification. /// Still, this implementation restricts the size to: /// /// 1. Reduce delays sending time-sensitive frames, e.g. window updates. /// 2. Minimize head-of-line blocking across streams. /// 3. Enable better interleaving of send and receive operations, as each is /// carried out atomically instead of concurrently with its respective /// counterpart. /// /// For details on why this concrete value was chosen, see /// https://github.com/paritytech/yamux/issues/100. const DEFAULT_SPLIT_SEND_SIZE: usize = 16 * KIB; /// Yamux configuration. /// /// The default configuration values are as follows: /// /// - max. for the total receive window size across all streams of a connection = 1 GiB /// - max. number of streams = 512 /// - read after close = true /// - split send size = 16 KiB #[derive(Debug, Clone)] pub struct Config { max_connection_receive_window: Option, max_num_streams: usize, read_after_close: bool, split_send_size: usize, } impl Default for Config { fn default() -> Self { Config { max_connection_receive_window: Some(1 * GIB), max_num_streams: 512, read_after_close: true, split_send_size: DEFAULT_SPLIT_SEND_SIZE, } } } impl Config { /// Set the upper limit for the total receive window size across all streams of a connection. /// /// Must be `>= 256 KiB * max_num_streams` to allow each stream at least the Yamux default /// window size. /// /// The window of a stream starts at 256 KiB and is increased (auto-tuned) based on the /// connection's round-trip time and the stream's bandwidth (striving for the /// bandwidth-delay-product). /// /// Set to `None` to disable limit, i.e. allow each stream to grow receive window based on /// connection's round-trip time and stream's bandwidth without limit. /// /// ## DOS attack mitigation /// /// A remote node (attacker) might trick the local node (target) into allocating large stream /// receive windows, trying to make the local node run out of memory. /// /// This attack is difficult, as the local node only increases the stream receive window up to /// 2x the bandwidth-delay-product, where bandwidth is the amount of bytes read, not just /// received. In other words, the attacker has to send (and have the local node read) /// significant amount of bytes on a stream over a long period of time to increase the stream /// receive window. E.g. on a 60ms 10Gbit/s connection the bandwidth-delay-product is ~75 MiB /// and thus the local node will at most allocate ~150 MiB (2x bandwidth-delay-product) per /// stream. /// /// Despite the difficulty of the attack one should choose a reasonable /// `max_connection_receive_window` to protect against this attack, especially since an attacker /// might use more than one stream per connection. pub fn set_max_connection_receive_window(&mut self, n: Option) -> &mut Self { self.max_connection_receive_window = n; assert!( self.max_connection_receive_window.unwrap_or(usize::MAX) >= self.max_num_streams * DEFAULT_CREDIT as usize, "`max_connection_receive_window` must be `>= 256 KiB * max_num_streams` to allow each stream at least the Yamux default window size" ); self } /// Set the max. number of streams per connection. pub fn set_max_num_streams(&mut self, n: usize) -> &mut Self { self.max_num_streams = n; assert!( self.max_connection_receive_window.unwrap_or(usize::MAX) >= self.max_num_streams * DEFAULT_CREDIT as usize, "`max_connection_receive_window` must be `>= 256 KiB * max_num_streams` to allow each stream at least the Yamux default window size" ); self } /// Allow or disallow streams to read from buffered data after /// the connection has been closed. pub fn set_read_after_close(&mut self, b: bool) -> &mut Self { self.read_after_close = b; self } /// Set the max. payload size used when sending data frames. Payloads larger /// than the configured max. will be split. pub fn set_split_send_size(&mut self, n: usize) -> &mut Self { self.split_send_size = n; self } } // Check that we can safely cast a `usize` to a `u64`. static_assertions::const_assert! { std::mem::size_of::() <= std::mem::size_of::() } // Check that we can safely cast a `u32` to a `usize`. static_assertions::const_assert! { std::mem::size_of::() <= std::mem::size_of::() } #[cfg(test)] impl quickcheck::Arbitrary for Config { fn arbitrary(g: &mut quickcheck::Gen) -> Self { use quickcheck::GenRange; let max_num_streams = g.gen_range(0..u16::MAX as usize); Config { max_connection_receive_window: if bool::arbitrary(g) { Some(g.gen_range((DEFAULT_CREDIT as usize * max_num_streams)..usize::MAX)) } else { None }, max_num_streams, read_after_close: bool::arbitrary(g), split_send_size: g.gen_range(DEFAULT_SPLIT_SEND_SIZE..usize::MAX), } } } rust-yamux-yamux-v0.13.3/yamux/src/tagged_stream.rs000066400000000000000000000021241463066673500224100ustar00rootroot00000000000000use futures::Stream; use std::pin::Pin; use std::task::{Context, Poll}; /// A stream that yields its tag with every item. #[pin_project::pin_project] pub struct TaggedStream { key: K, #[pin] inner: S, reported_none: bool, } impl TaggedStream { pub fn new(key: K, inner: S) -> Self { Self { key, inner, reported_none: false, } } pub fn inner_mut(&mut self) -> &mut S { &mut self.inner } } impl Stream for TaggedStream where K: Copy, S: Stream, { type Item = (K, Option); fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); if *this.reported_none { return Poll::Ready(None); } match futures::ready!(this.inner.poll_next(cx)) { Some(item) => Poll::Ready(Some((*this.key, Some(item)))), None => { *this.reported_none = true; Poll::Ready(Some((*this.key, None))) } } } }