diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d9230fbe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,145 @@ +name: CI + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +# Disable previous runs +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +# ${{ vars.CI_UNIFIED_IMAGE }} is defined in the repository variables +env: + CI_IMAGE: "paritytech/ci-unified:bullseye-1.75.0-2024-01-22-v20240222" + +jobs: + set-image: + # This workaround sets the container image for each job using 'set-image' job output. + # env variables don't work for PR from forks, so we need to use outputs. + runs-on: ubuntu-latest + outputs: + CI_IMAGE: ${{ steps.set_image.outputs.CI_IMAGE }} + steps: + - id: set_image + run: echo "CI_IMAGE=${{ env.CI_IMAGE }}" >> $GITHUB_OUTPUT + fmt: + name: Cargo fmt + runs-on: ubuntu-latest + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.CI_IMAGE }} + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + cache-all-crates: true + + - name: Cargo fmt + run: cargo +nightly fmt --all -- --check + + machete: + name: Check unused dependencies + runs-on: ubuntu-latest + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.CI_IMAGE }} + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + cache-all-crates: true + + - name: Install cargo-machete + run: cargo install cargo-machete + + - name: Check unused dependencies + run: cargo machete + + check: + name: Cargo check + runs-on: ubuntu-latest + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.CI_IMAGE }} + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + cache-all-crates: true + + - name: Cargo check + run: cargo check + + doc: + name: Check documentation + runs-on: ubuntu-latest + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.CI_IMAGE }} + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + cache-all-crates: true + - name: Check documentation + run: RUSTDOCFLAGS="-D warnings -D rustdoc::broken_intra_doc_links" cargo doc --workspace --no-deps --document-private-items + + clippy: + name: Cargo clippy + runs-on: ubuntu-latest + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.CI_IMAGE }} + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + cache-all-crates: true + + # TODO: Allow clippy to fail and do not cancel other tasks. + # Clippy is fixed by: https://github.com/paritytech/litep2p/pull/57. + - name: Run clippy + continue-on-error: true + run: cargo clippy + + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.CI_IMAGE }} + options: --sysctl net.ipv6.conf.all.disable_ipv6=0 + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + cache-all-crates: true + + - name: Run tests + run: cargo test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..a8c5deff --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,52 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.3.0] - 2023-04-05 + +### Added + +- Expose `reuse_port` option for TCP and WebSocket transports ([#69](https://github.com/paritytech/litep2p/pull/69)) +- protocol/mdns: Use `SO_REUSEPORT` for the mDNS socket ([#68](https://github.com/paritytech/litep2p/pull/68)) +- Add support for protocol/agent version ([#64](https://github.com/paritytech/litep2p/pull/64)) + +## [0.2.0] - 2023-09-05 + +This is the second release of litep2p, v0.2.0. The quality of the first release was so bad that this release is a complete rewrite of the library. + +Support is added for the following features: + +* Transport protocols: + * TCP + * QUIC + * WebRTC + * WebSocket + +* Protocols: + * [`/ipfs/identify/1.0.0`](https://github.com/libp2p/specs/tree/master/identify) + * [`/ipfs/ping/1.0.0`](https://github.com/libp2p/specs/blob/master/ping/ping.md) + * [`/ipfs/kad/1.0.0`](https://github.com/libp2p/specs/tree/master/kad-dht) + * [`/ipfs/bitswap/1.2.0`](https://github.com/ipfs/specs/blob/main/BITSWAP.md) + * Request-response protocol + * Notification protocol + * Multicast DNS + * API for creating custom protocols + +This time the architecture has been designed to be extensible and integrating new transport and/or user-level protocols should be easier. Additionally, the test coverage is higher both in terms of unit and integration tests. The project also contains conformance tests which test the behavior of `litep2p` against, [`rust-libp2p`](https://github.com/libp2p/rust-libp2p/), [`go-libp2p`](https://github.com/libp2p/go-libp2p/) and Substrate's [`sc-network`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/client/network). Currently the Substrate conformance tests are not enabled by default as they require unpublished/unaccepted changes to Substrate. + +## [0.1.0] - 2023-04-04 + +This is the first release of `litep2p`, v0.1.0. + +Support is added for the following: + +* TCP + Noise + Yamux (compatibility with `libp2p`) +* [`/ipfs/identify/1.0.0`](https://github.com/libp2p/specs/tree/master/identify) +* [`/ipfs/ping/1.0.0`](https://github.com/libp2p/specs/blob/master/ping/ping.md) +* Request-response protocol +* Notification protocol + +The code quality is atrocious but it works and the second release focuses on providing high test coverage for the library. After that is done and most of the functionality is covered (unit, integration and conformance tests, benchmarks), the focus can be turned to refactoring the code into something clean and efficient. diff --git a/Cargo.lock b/Cargo.lock index d9f83a1b..634e5f6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2881,7 +2881,7 @@ checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "litep2p" -version = "0.2.0" +version = "0.3.0" dependencies = [ "async-trait", "asynchronous-codec", @@ -2890,7 +2890,6 @@ dependencies = [ "cid", "ed25519-dalek", "futures", - "futures-rustls", "futures-timer", "futures_ringbuf", "hex-literal", @@ -2909,6 +2908,7 @@ dependencies = [ "quickcheck", "quinn", "rand 0.8.5", + "rand_xorshift", "rcgen 0.10.0", "ring 0.16.20", "rustls 0.20.8", @@ -4193,6 +4193,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rayon" version = "1.7.0" @@ -5806,9 +5815,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "str0m" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48572247f422dcbe68630c973f8296fbd5157119cd36a3223e48bf83d47727" +checksum = "d3f10d3f68e60168d81110410428a435dbde28cc5525f5f7c6fdec92dbdc2800" dependencies = [ "combine", "crc", diff --git a/Cargo.toml b/Cargo.toml index 68fc0eb5..86f1e5ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "litep2p" description = "Peer-to-peer networking library" license = "MIT" -version = "0.2.0" +version = "0.3.0" edition = "2021" [build-dependencies] @@ -15,7 +15,6 @@ bytes = "1.4.0" cid = "0.10.1" ed25519-dalek = "1.0.1" futures = "0.3.27" -futures-rustls = "0.22.2" futures-timer = "3.0.2" hex-literal = "0.4.1" indexmap = { version = "2.0.0", features = ["std"] } @@ -37,7 +36,7 @@ simple-dns = "0.5.3" smallvec = "1.10.0" snow = { version = "0.9.3", features = ["ring-resolver"], default-features = false } socket2 = { version = "0.5.5", features = ["all"] } -str0m = "0.2.0" +str0m = "0.4.1" thiserror = "1.0.39" tokio-stream = "0.1.12" tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] } @@ -79,6 +78,7 @@ libp2p = { version = "0.51.3", features = [ "quic", ]} quickcheck = "1.0.3" +rand_xorshift = "0.3.0" sc-network = "0.28.0" sc-utils = "8.0.0" serde_json = "1.0.108" diff --git a/rustfmt.toml b/rustfmt.toml index f0d1a9dc..30af9121 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,9 +1,20 @@ +# Basic +edition = "2021" +max_width = 100 + +# Imports +imports_granularity = "Crate" reorder_imports = true + +# Consistency newline_style = "Unix" + +# Misc chain_width = 80 spaces_around_ranges = false match_arm_blocks = false trailing_comma = "Vertical" -edition = "2021" + +# Format comments comment_width = 100 wrap_comments = true diff --git a/src/multistream_select/dialer_select.rs b/src/multistream_select/dialer_select.rs index 40f16a1e..489845d5 100644 --- a/src/multistream_select/dialer_select.rs +++ b/src/multistream_select/dialer_select.rs @@ -24,7 +24,9 @@ use crate::{ codec::unsigned_varint::UnsignedVarint, error::{self, Error}, multistream_select::{ - protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}, + protocol::{ + encode_multistream_message, HeaderLine, Message, MessageIO, Protocol, ProtocolError, + }, Negotiated, NegotiationError, Version, }, types::protocol::ProtocolName, @@ -288,7 +290,7 @@ where } /// `multistream-select` handshake result for dialer. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum HandshakeResult { /// Handshake is not complete, data missing. NotReady, @@ -323,7 +325,6 @@ pub struct DialerState { state: HandshakeState, } -// TODO: tests impl DialerState { /// Propose protocol to remote peer. /// @@ -333,21 +334,14 @@ impl DialerState { protocol: ProtocolName, fallback_names: Vec, ) -> crate::Result<(Self, Vec)> { - // encode `/multistream-select/1.0.0` header - let mut bytes = BytesMut::with_capacity(64); - let message = Message::Header(HeaderLine::V1); - let _ = message.encode(&mut bytes).map_err(|_| Error::InvalidData)?; - let mut header = UnsignedVarint::encode(bytes)?; - - // encode proposed protocol - let mut proto_bytes = BytesMut::with_capacity(512); - let message = Message::Protocol(Protocol::try_from(protocol.as_bytes()).unwrap()); - let _ = message.encode(&mut proto_bytes).map_err(|_| Error::InvalidData)?; - let proto_bytes = UnsignedVarint::encode(proto_bytes)?; - - // TODO: add fallback names - - header.append(&mut proto_bytes.into()); + let message = encode_multistream_message( + std::iter::once(protocol.clone()) + .chain(fallback_names.clone()) + .filter_map(|protocol| Protocol::try_from(protocol.as_ref()).ok()) + .map(|protocol| Message::Protocol(protocol)), + )? + .freeze() + .to_vec(); Ok(( Self { @@ -355,7 +349,7 @@ impl DialerState { fallback_names, state: HandshakeState::WaitingResponse, }, - header, + message, )) } @@ -392,10 +386,9 @@ impl DialerState { return Ok(HandshakeResult::Succeeded(self.protocol.clone())); } - // TODO: zzz for fallback in &self.fallback_names { if fallback.as_bytes() == protocol.as_ref() { - return Ok(HandshakeResult::Succeeded(self.protocol.clone())); + return Ok(HandshakeResult::Succeeded(fallback.clone())); } } @@ -758,4 +751,144 @@ mod tests { server.await.unwrap(); client.await.unwrap(); } + + #[test] + fn propose() { + let (mut dialer_state, message) = + DialerState::propose(ProtocolName::from("/13371338/proto/1"), vec![]).unwrap(); + let message = bytes::BytesMut::from(&message[..]).freeze(); + + let Message::Protocols(protocols) = Message::decode(message).unwrap() else { + panic!("invalid message type"); + }; + + assert_eq!(protocols.len(), 2); + assert_eq!( + protocols[0], + Protocol::try_from(&b"/multistream/1.0.0"[..]) + .expect("valid multitstream-select header") + ); + assert_eq!( + protocols[1], + Protocol::try_from(&b"/13371338/proto/1"[..]) + .expect("valid multitstream-select header") + ); + } + + #[test] + fn propose_with_fallback() { + let (mut dialer_state, message) = DialerState::propose( + ProtocolName::from("/13371338/proto/1"), + vec![ProtocolName::from("/sup/proto/1")], + ) + .unwrap(); + let message = bytes::BytesMut::from(&message[..]).freeze(); + + let Message::Protocols(protocols) = Message::decode(message).unwrap() else { + panic!("invalid message type"); + }; + + assert_eq!(protocols.len(), 3); + assert_eq!( + protocols[0], + Protocol::try_from(&b"/multistream/1.0.0"[..]) + .expect("valid multitstream-select header") + ); + assert_eq!( + protocols[1], + Protocol::try_from(&b"/13371338/proto/1"[..]) + .expect("valid multitstream-select header") + ); + assert_eq!( + protocols[2], + Protocol::try_from(&b"/sup/proto/1"[..]).expect("valid multitstream-select header") + ); + } + + #[test] + fn register_response_invalid_message() { + // send only header line + let mut bytes = BytesMut::with_capacity(32); + let message = Message::Header(HeaderLine::V1); + let _ = message.encode(&mut bytes).map_err(|_| Error::InvalidData).unwrap(); + + let (mut dialer_state, _message) = + DialerState::propose(ProtocolName::from("/13371338/proto/1"), vec![]).unwrap(); + + match dialer_state.register_response(bytes.freeze().to_vec()) { + Err(Error::NegotiationError(error::NegotiationError::MultistreamSelectError( + NegotiationError::Failed, + ))) => {} + event => panic!("invalid event: {event:?}"), + } + } + + #[test] + fn header_line_missing() { + // header line missing + let mut bytes = BytesMut::with_capacity(256); + let message = Message::Protocols(vec![ + Protocol::try_from(&b"/13371338/proto/1"[..]).unwrap(), + Protocol::try_from(&b"/sup/proto/1"[..]).unwrap(), + ]); + let _ = message.encode(&mut bytes).map_err(|_| Error::InvalidData).unwrap(); + + let (mut dialer_state, _message) = + DialerState::propose(ProtocolName::from("/13371338/proto/1"), vec![]).unwrap(); + + match dialer_state.register_response(bytes.freeze().to_vec()) { + Err(Error::NegotiationError(error::NegotiationError::MultistreamSelectError( + NegotiationError::Failed, + ))) => {} + event => panic!("invalid event: {event:?}"), + } + } + + #[test] + fn negotiate_main_protocol() { + let message = encode_multistream_message( + vec![Message::Protocol( + Protocol::try_from(&b"/13371338/proto/1"[..]).unwrap(), + )] + .into_iter(), + ) + .unwrap() + .freeze(); + + let (mut dialer_state, _message) = DialerState::propose( + ProtocolName::from("/13371338/proto/1"), + vec![ProtocolName::from("/sup/proto/1")], + ) + .unwrap(); + + match dialer_state.register_response(message.to_vec()) { + Ok(HandshakeResult::Succeeded(negotiated)) => + assert_eq!(negotiated, ProtocolName::from("/13371338/proto/1")), + _ => panic!("invalid event"), + } + } + + #[test] + fn negotiate_fallback_protocol() { + let message = encode_multistream_message( + vec![Message::Protocol( + Protocol::try_from(&b"/sup/proto/1"[..]).unwrap(), + )] + .into_iter(), + ) + .unwrap() + .freeze(); + + let (mut dialer_state, _message) = DialerState::propose( + ProtocolName::from("/13371338/proto/1"), + vec![ProtocolName::from("/sup/proto/1")], + ) + .unwrap(); + + match dialer_state.register_response(message.to_vec()) { + Ok(HandshakeResult::Succeeded(negotiated)) => + assert_eq!(negotiated, ProtocolName::from("/sup/proto/1")), + _ => panic!("invalid event"), + } + } } diff --git a/src/multistream_select/listener_select.rs b/src/multistream_select/listener_select.rs index 4217a332..ae7d4d4b 100644 --- a/src/multistream_select/listener_select.rs +++ b/src/multistream_select/listener_select.rs @@ -25,7 +25,9 @@ use crate::{ codec::unsigned_varint::UnsignedVarint, error::{self, Error}, multistream_select::{ - protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}, + protocol::{ + encode_multistream_message, HeaderLine, Message, MessageIO, Protocol, ProtocolError, + }, Negotiated, NegotiationError, }, types::protocol::ProtocolName, @@ -322,6 +324,25 @@ where } } +/// Result of [`listener_negotiate()`]. +#[derive(Debug)] +pub enum ListenerSelectResult { + /// Requested protocol is available and substream can be accepted. + Accepted { + /// Protocol that is confirmed. + protocol: ProtocolName, + + /// `multistream-select` message. + message: BytesMut, + }, + + /// Requested protocol is not available. + Rejected { + /// `multistream-select` message. + message: BytesMut, + }, +} + /// Negotiate protocols for listener. /// /// Parse protocols offered by the remote peer and check if any of the offered protocols match @@ -330,7 +351,7 @@ where pub fn listener_negotiate<'a>( supported_protocols: &'a mut impl Iterator, payload: Bytes, -) -> crate::Result<(ProtocolName, BytesMut)> { +) -> crate::Result { let Message::Protocols(protocols) = Message::decode(payload).map_err(|_| Error::InvalidData)? else { return Err(Error::NegotiationError( @@ -344,37 +365,39 @@ pub fn listener_negotiate<'a>( let header = Protocol::try_from(&b"/multistream/1.0.0"[..]).expect("valid multitstream-select header"); - if !std::matches!(protocol_iter.next(), Some(header)) { + if protocol_iter.next() != Some(header) { return Err(Error::NegotiationError( error::NegotiationError::MultistreamSelectError(NegotiationError::Failed), )); } for protocol in protocol_iter { + tracing::trace!( + target: LOG_TARGET, + protocol = ?std::str::from_utf8(protocol.as_ref()), + "listener: checking protocol", + ); + for supported in &mut *supported_protocols { if protocol.as_ref() == supported.as_bytes() { - // encode `/multistream-select/1.0.0` header - let mut bytes = BytesMut::with_capacity(64); - let message = Message::Header(HeaderLine::V1); - let _ = message.encode(&mut bytes).map_err(|_| Error::InvalidData)?; - let mut header = UnsignedVarint::encode(bytes)?; - - // encode negotiated protocol - let mut proto_bytes = BytesMut::with_capacity(512); - let message = Message::Protocol(protocol); - let _ = message.encode(&mut proto_bytes).map_err(|_| Error::InvalidData)?; - let proto_bytes = UnsignedVarint::encode(proto_bytes)?; - - header.append(&mut proto_bytes.into()); - - return Ok((supported.clone(), BytesMut::from(&header[..]))); + return Ok(ListenerSelectResult::Accepted { + protocol: supported.clone(), + message: encode_multistream_message(std::iter::once(Message::Protocol( + protocol, + )))?, + }); } } } - Err(Error::NegotiationError( - error::NegotiationError::MultistreamSelectError(NegotiationError::Failed), - )) + tracing::trace!( + target: LOG_TARGET, + "listener: handshake rejected, no supported protocol found", + ); + + Ok(ListenerSelectResult::Rejected { + message: encode_multistream_message(std::iter::once(Message::NotAvailable))?, + }) } #[cfg(test)] @@ -382,14 +405,137 @@ mod tests { use super::*; #[test] - fn listener_negotiate_works() {} + fn listener_negotiate_works() { + let mut local_protocols = vec![ + ProtocolName::from("/13371338/proto/1"), + ProtocolName::from("/sup/proto/1"), + ProtocolName::from("/13371338/proto/2"), + ProtocolName::from("/13371338/proto/3"), + ProtocolName::from("/13371338/proto/4"), + ]; + let message = encode_multistream_message( + vec![ + Message::Protocol(Protocol::try_from(&b"/13371338/proto/1"[..]).unwrap()), + Message::Protocol(Protocol::try_from(&b"/sup/proto/1"[..]).unwrap()), + ] + .into_iter(), + ) + .unwrap() + .freeze(); + + match listener_negotiate(&mut local_protocols.iter(), message) { + Err(error) => panic!("error received: {error:?}"), + Ok(ListenerSelectResult::Rejected { .. }) => panic!("message rejected"), + Ok(ListenerSelectResult::Accepted { protocol, message }) => { + assert_eq!(protocol, ProtocolName::from("/13371338/proto/1")); + } + } + } #[test] - fn invalid_message_offered() {} + fn invalid_message() { + let mut local_protocols = vec![ + ProtocolName::from("/13371338/proto/1"), + ProtocolName::from("/sup/proto/1"), + ProtocolName::from("/13371338/proto/2"), + ProtocolName::from("/13371338/proto/3"), + ProtocolName::from("/13371338/proto/4"), + ]; + let message = encode_multistream_message(std::iter::once(Message::Protocols(vec![ + Protocol::try_from(&b"/13371338/proto/1"[..]).unwrap(), + Protocol::try_from(&b"/sup/proto/1"[..]).unwrap(), + ]))) + .unwrap() + .freeze(); + + match listener_negotiate(&mut local_protocols.iter(), message) { + Err(error) => assert!(std::matches!(error, Error::InvalidData)), + _ => panic!("invalid event"), + } + } #[test] - fn no_supported_protocol() {} + fn only_header_line_received() { + let mut local_protocols = vec![ + ProtocolName::from("/13371338/proto/1"), + ProtocolName::from("/sup/proto/1"), + ProtocolName::from("/13371338/proto/2"), + ProtocolName::from("/13371338/proto/3"), + ProtocolName::from("/13371338/proto/4"), + ]; + + // send only header line + let mut bytes = BytesMut::with_capacity(32); + let message = Message::Header(HeaderLine::V1); + let _ = message.encode(&mut bytes).map_err(|_| Error::InvalidData).unwrap(); + + match listener_negotiate(&mut local_protocols.iter(), bytes.freeze()) { + Err(error) => assert!(std::matches!( + error, + Error::NegotiationError(error::NegotiationError::MultistreamSelectError( + NegotiationError::Failed + )) + )), + event => panic!("invalid event: {event:?}"), + } + } #[test] - fn multistream_select_header_missing() {} + fn header_line_missing() { + let mut local_protocols = vec![ + ProtocolName::from("/13371338/proto/1"), + ProtocolName::from("/sup/proto/1"), + ProtocolName::from("/13371338/proto/2"), + ProtocolName::from("/13371338/proto/3"), + ProtocolName::from("/13371338/proto/4"), + ]; + + // header line missing + let mut bytes = BytesMut::with_capacity(256); + let message = Message::Protocols(vec![ + Protocol::try_from(&b"/13371338/proto/1"[..]).unwrap(), + Protocol::try_from(&b"/sup/proto/1"[..]).unwrap(), + ]); + let _ = message.encode(&mut bytes).map_err(|_| Error::InvalidData).unwrap(); + + match listener_negotiate(&mut local_protocols.iter(), bytes.freeze()) { + Err(error) => assert!(std::matches!( + error, + Error::NegotiationError(error::NegotiationError::MultistreamSelectError( + NegotiationError::Failed + )) + )), + event => panic!("invalid event: {event:?}"), + } + } + + #[test] + fn protocol_not_supported() { + let mut local_protocols = vec![ + ProtocolName::from("/13371338/proto/1"), + ProtocolName::from("/sup/proto/1"), + ProtocolName::from("/13371338/proto/2"), + ProtocolName::from("/13371338/proto/3"), + ProtocolName::from("/13371338/proto/4"), + ]; + let message = encode_multistream_message( + vec![Message::Protocol( + Protocol::try_from(&b"/13371339/proto/1"[..]).unwrap(), + )] + .into_iter(), + ) + .unwrap() + .freeze(); + + match listener_negotiate(&mut local_protocols.iter(), message) { + Err(error) => panic!("error received: {error:?}"), + Ok(ListenerSelectResult::Rejected { message }) => { + assert_eq!( + message, + encode_multistream_message(std::iter::once(Message::NotAvailable)).unwrap() + ); + } + Ok(ListenerSelectResult::Accepted { protocol, message }) => panic!("message accepted"), + } + } } diff --git a/src/multistream_select/mod.rs b/src/multistream_select/mod.rs index 6271b526..86abe026 100644 --- a/src/multistream_select/mod.rs +++ b/src/multistream_select/mod.rs @@ -43,13 +43,12 @@ //! echoing the same protocol) or reject (by responding with a message stating //! "not available"). If a suggested protocol is not available, the dialer may //! suggest another protocol. This process continues until a protocol is agreed upon, -//! yielding a [`Negotiated`](self::Negotiated) stream, or the dialer has run out of +//! yielding a [`Negotiated`] stream, or the dialer has run out of //! alternatives. //! -//! See [`dialer_select_proto`](self::dialer_select_proto) and -//! [`listener_select_proto`](self::listener_select_proto). +//! See [`dialer_select_proto`] and [`listener_select_proto`]. //! -//! ## [`Negotiated`](self::Negotiated) +//! ## [`Negotiated`] //! //! A `Negotiated` represents an I/O stream that has settled on a protocol //! to use. By default, with [`Version::V1`], protocol negotiation is always @@ -58,7 +57,7 @@ //! a variant [`Version::V1Lazy`] that permits 0-RTT negotiation if the //! dialer only supports a single protocol. In that case, when a dialer //! settles on a protocol to use, the [`DialerSelectFuture`] yields a -//! [`Negotiated`](self::Negotiated) I/O stream before the negotiation +//! [`Negotiated`] I/O stream before the negotiation //! data has been flushed. It is then expecting confirmation for that protocol //! as the first messages read from the stream. This behaviour allows the dialer //! to immediately send data relating to the negotiated protocol together with the @@ -66,8 +65,7 @@ //! multiple 0-RTT negotiations in sequence for different protocols layered on //! top of each other may trigger undesirable behaviour for a listener not //! supporting one of the intermediate protocols. See -//! [`dialer_select_proto`](self::dialer_select_proto) and the documentation -//! of [`Version::V1Lazy`] for further details. +//! [`dialer_select_proto`] and the documentation of [`Version::V1Lazy`] for further details. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -77,14 +75,14 @@ mod listener_select; mod negotiated; mod protocol; -pub use crate::multistream_select::dialer_select::{ - dialer_select_proto, DialerSelectFuture, DialerState, HandshakeResult, +pub use crate::multistream_select::{ + dialer_select::{dialer_select_proto, DialerSelectFuture, DialerState, HandshakeResult}, + listener_select::{ + listener_negotiate, listener_select_proto, ListenerSelectFuture, ListenerSelectResult, + }, + negotiated::{Negotiated, NegotiatedComplete, NegotiationError}, + protocol::{HeaderLine, Message, Protocol, ProtocolError}, }; -pub use crate::multistream_select::listener_select::{ - listener_negotiate, listener_select_proto, ListenerSelectFuture, -}; -pub use crate::multistream_select::negotiated::{Negotiated, NegotiatedComplete, NegotiationError}; -pub use crate::multistream_select::protocol::{HeaderLine, Message, Protocol, ProtocolError}; /// Supported multistream-select versions. #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/src/multistream_select/protocol.rs b/src/multistream_select/protocol.rs index 93a429ef..cc196b6f 100644 --- a/src/multistream_select/protocol.rs +++ b/src/multistream_select/protocol.rs @@ -25,8 +25,14 @@ //! `Stream` and `Sink` implementations of `MessageIO` and //! `MessageReader`. -use crate::multistream_select::length_delimited::{LengthDelimited, LengthDelimitedReader}; -use crate::multistream_select::Version; +use crate::{ + codec::unsigned_varint::UnsignedVarint, + error::Error as Litep2pError, + multistream_select::{ + length_delimited::{LengthDelimited, LengthDelimitedReader}, + Version, + }, +}; use bytes::{BufMut, Bytes, BytesMut}; use futures::{io::IoSlice, prelude::*, ready}; @@ -221,6 +227,28 @@ impl Message { } } +/// Create `multistream-select` message from an iterator of `Message`s. +pub fn encode_multistream_message( + messages: impl IntoIterator, +) -> crate::Result { + // encode `/multistream-select/1.0.0` header + let mut bytes = BytesMut::with_capacity(32); + let message = Message::Header(HeaderLine::V1); + let _ = message.encode(&mut bytes).map_err(|_| Litep2pError::InvalidData)?; + let mut header = UnsignedVarint::encode(bytes)?; + + // encode each message + for message in messages { + let mut proto_bytes = BytesMut::with_capacity(256); + let _ = message.encode(&mut proto_bytes).map_err(|_| Litep2pError::InvalidData)?; + let proto_bytes = UnsignedVarint::encode(proto_bytes)?; + + header.append(&mut proto_bytes.into()); + } + + Ok(BytesMut::from(&header[..])) +} + /// A `MessageIO` implements a [`Stream`] and [`Sink`] of [`Message`]s. #[pin_project::pin_project] pub struct MessageIO { diff --git a/src/protocol/libp2p/identify.rs b/src/protocol/libp2p/identify.rs index 3782a245..dc02ed62 100644 --- a/src/protocol/libp2p/identify.rs +++ b/src/protocol/libp2p/identify.rs @@ -51,6 +51,9 @@ const PROTOCOL_NAME: &str = "/ipfs/id/1.0.0"; /// IPFS Identify push protocol name. const _PUSH_PROTOCOL_NAME: &str = "/ipfs/id/push/1.0.0"; +/// Default agent version. +const DEFAULT_AGENT: &str = "litep2p/1.0.0"; + /// Size for `/ipfs/ping/1.0.0` payloads. // TODO: what is the max size? const IDENTIFY_PAYLOAD_SIZE: usize = 4096; @@ -78,6 +81,12 @@ pub struct Config { /// Public addresses. pub(crate) public_addresses: Vec, + + /// Protocol version. + pub(crate) protocol_version: String, + + /// User agent. + pub(crate) user_agent: Option, } impl Config { @@ -86,6 +95,8 @@ impl Config { /// Returns a config that is given to `Litep2pConfig` and an event stream for /// [`IdentifyEvent`]s. pub fn new( + protocol_version: String, + user_agent: Option, public_addresses: Vec, ) -> (Self, Box + Send + Unpin>) { let (tx_event, rx_event) = channel(DEFAULT_CHANNEL_SIZE); @@ -95,6 +106,8 @@ impl Config { tx_event, public: None, public_addresses, + protocol_version, + user_agent, codec: ProtocolCodec::UnsignedVarint(Some(IDENTIFY_PAYLOAD_SIZE)), protocols: Vec::new(), protocol: ProtocolName::from(PROTOCOL_NAME), @@ -112,6 +125,12 @@ pub enum IdentifyEvent { /// Peer ID. peer: PeerId, + /// Protocol version. + protocol_version: Option, + + /// User agent. + user_agent: Option, + /// Supported protocols. supported_protocols: HashSet, @@ -123,6 +142,27 @@ pub enum IdentifyEvent { }, } +/// Identify response received from remote. +struct IdentifyResponse { + /// Remote peer ID. + peer: PeerId, + + /// Protocol version. + protocol_version: Option, + + /// User agent. + user_agent: Option, + + /// Protocols supported by remote. + supported_protocols: HashSet, + + /// Remote's listen addresses. + listen_addresses: Vec, + + /// Observed address. + observed_address: Option, +} + pub(crate) struct Identify { // Connection service. service: TransportService, @@ -136,6 +176,12 @@ pub(crate) struct Identify { // Public key of the local node, filled by `Litep2p`. public: PublicKey, + /// Protocol version. + protocol_version: String, + + /// User agent. + user_agent: String, + /// Public addresses. listen_addresses: HashSet, @@ -146,12 +192,7 @@ pub(crate) struct Identify { pending_opens: HashMap, /// Pending outbound substreams. - pending_outbound: FuturesUnordered< - BoxFuture< - 'static, - crate::Result<(PeerId, HashSet, Option, Vec)>, - >, - >, + pending_outbound: FuturesUnordered>>, /// Pending inbound substreams. pending_inbound: FuturesUnordered>, @@ -174,6 +215,8 @@ impl Identify { .chain(listen_addresses.into_iter()) .collect(), public: config.public.expect("public key to be supplied"), + protocol_version: config.protocol_version, + user_agent: config.user_agent.unwrap_or(DEFAULT_AGENT.to_string()), pending_opens: HashMap::new(), pending_inbound: FuturesUnordered::new(), pending_outbound: FuturesUnordered::new(), @@ -227,8 +270,8 @@ impl Identify { }; let identify = identify_schema::Identify { - protocol_version: None, - agent_version: None, + protocol_version: Some(self.protocol_version.clone()), + agent_version: Some(self.user_agent.clone()), public_key: Some(self.public.to_protobuf_encoding()), listen_addrs: self .listen_addresses @@ -313,13 +356,17 @@ impl Identify { .collect(); let observed_address = info.observed_addr.map(|address| Multiaddr::try_from(address).ok()).flatten(); + let protocol_version = info.protocol_version; + let user_agent = info.agent_version; - Ok(( + Ok(IdentifyResponse { peer, - HashSet::from_iter(info.protocols), + protocol_version, + user_agent, + supported_protocols: HashSet::from_iter(info.protocols), observed_address, listen_addresses, - )) + }) })); } @@ -351,13 +398,15 @@ impl Identify { }, _ = self.pending_inbound.next(), if !self.pending_inbound.is_empty() => {} event = self.pending_outbound.next(), if !self.pending_outbound.is_empty() => match event { - Some(Ok((peer, supported_protocols, observed_address, listen_addresses))) => { + Some(Ok(response)) => { let _ = self.tx .send(IdentifyEvent::PeerIdentified { - peer, - supported_protocols: supported_protocols.into_iter().map(From::from).collect(), - observed_address: observed_address.map_or(Multiaddr::empty(), |address| address), - listen_addresses, + peer: response.peer, + protocol_version: response.protocol_version, + user_agent: response.user_agent, + supported_protocols: response.supported_protocols.into_iter().map(From::from).collect(), + observed_address: response.observed_address.map_or(Multiaddr::empty(), |address| address), + listen_addresses: response.listen_addresses, }) .await; } diff --git a/src/protocol/libp2p/kademlia/handle.rs b/src/protocol/libp2p/kademlia/handle.rs index 2d8e913c..6d693cdb 100644 --- a/src/protocol/libp2p/kademlia/handle.rs +++ b/src/protocol/libp2p/kademlia/handle.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::{ - protocol::libp2p::kademlia::{QueryId, Record, RecordKey}, + protocol::libp2p::kademlia::{PeerRecord, QueryId, Record, RecordKey}, PeerId, }; @@ -90,6 +90,20 @@ pub(crate) enum KademliaCommand { query_id: QueryId, }, + /// Store record to DHT to the given peers. + /// + /// Similar to [`KademliaCommand::PutRecord`] but allows user to specify the peers. + PutRecordToPeers { + /// Record. + record: Record, + + /// Query ID for the query. + query_id: QueryId, + + /// Use the following peers for the put request. + peers: Vec, + }, + /// Get record from DHT. GetRecord { /// Record key. @@ -137,7 +151,7 @@ pub enum KademliaEvent { query_id: QueryId, /// Found record. - record: Record, + record: PeerRecord, }, /// `PUT_VALUE` query succeeded. @@ -207,6 +221,21 @@ impl KademliaHandle { query_id } + /// Store record to DHT to the given peers. + pub async fn put_record_to_peers(&mut self, record: Record, peers: Vec) -> QueryId { + let query_id = self.next_query_id(); + let _ = self + .cmd_tx + .send(KademliaCommand::PutRecordToPeers { + record, + query_id, + peers, + }) + .await; + + query_id + } + /// Get record from DHT. pub async fn get_record(&mut self, key: RecordKey, quorum: Quorum) -> QueryId { let query_id = self.next_query_id(); @@ -247,6 +276,24 @@ impl KademliaHandle { .map_err(|_| ()) } + /// Try to initiate `PUT_VALUE` query to the given peers and if the channel is clogged, + /// return an error. + pub fn try_put_record_to_peers( + &mut self, + record: Record, + peers: Vec, + ) -> Result { + let query_id = self.next_query_id(); + self.cmd_tx + .try_send(KademliaCommand::PutRecordToPeers { + record, + query_id, + peers, + }) + .map(|_| query_id) + .map_err(|_| ()) + } + /// Try to initiate `GET_VALUE` query and if the channel is clogged, return an error. pub fn try_get_record(&mut self, key: RecordKey, quorum: Quorum) -> Result { let query_id = self.next_query_id(); diff --git a/src/protocol/libp2p/kademlia/mod.rs b/src/protocol/libp2p/kademlia/mod.rs index 28a6e338..ed21dfb4 100644 --- a/src/protocol/libp2p/kademlia/mod.rs +++ b/src/protocol/libp2p/kademlia/mod.rs @@ -47,12 +47,10 @@ use tokio::sync::mpsc::{Receiver, Sender}; use std::collections::{hash_map::Entry, HashMap}; -pub use { - config::{Config, ConfigBuilder}, - handle::{KademliaEvent, KademliaHandle, Quorum, RoutingTableUpdateMode}, - query::QueryId, - record::{Key as RecordKey, Record}, -}; +pub use config::{Config, ConfigBuilder}; +pub use handle::{KademliaEvent, KademliaHandle, Quorum, RoutingTableUpdateMode}; +pub use query::QueryId; +pub use record::{Key as RecordKey, PeerRecord, Record}; /// Logging target for the file. const LOG_TARGET: &str = "litep2p::ipfs::kademlia"; @@ -641,7 +639,7 @@ impl Kademlia { Ok(()) } QueryAction::GetRecordQueryDone { query_id, record } => { - self.store.put(record.clone()); + self.store.put(record.record.clone()); let _ = self.event_tx.send(KademliaEvent::GetRecordSuccess { query_id, record }).await; @@ -743,15 +741,34 @@ impl Kademlia { Some(KademliaCommand::PutRecord { record, query_id }) => { tracing::debug!(target: LOG_TARGET, ?query_id, key = ?record.key, "store record to DHT"); - self.store.put(record.clone()); let key = Key::new(record.key.clone()); + self.store.put(record.clone()); + self.engine.start_put_record( query_id, record, self.routing_table.closest(key, self.replication_factor).into(), ); } + Some(KademliaCommand::PutRecordToPeers { record, query_id, peers }) => { + tracing::debug!(target: LOG_TARGET, ?query_id, key = ?record.key, "store record to DHT to specified peers"); + + // Put the record to the specified peers. + let peers = peers.into_iter().filter_map(|peer| { + match self.routing_table.entry(Key::from(peer)) { + KBucketEntry::Occupied(entry) => Some(entry.clone()), + KBucketEntry::Vacant(entry) if !entry.addresses.is_empty() => Some(entry.clone()), + _ => None, + } + }).collect(); + + self.engine.start_put_record_to_peers( + query_id, + record, + peers, + ); + } Some(KademliaCommand::GetRecord { key, quorum, query_id }) => { tracing::debug!(target: LOG_TARGET, ?key, "get record from DHT"); @@ -759,7 +776,7 @@ impl Kademlia { (Some(record), Quorum::One) => { let _ = self .event_tx - .send(KademliaEvent::GetRecordSuccess { query_id, record: record.clone() }) + .send(KademliaEvent::GetRecordSuccess { query_id, record: PeerRecord { record: record.clone(), peer: None } }) .await; } (record, _) => { diff --git a/src/protocol/libp2p/kademlia/query/find_many_nodes.rs b/src/protocol/libp2p/kademlia/query/find_many_nodes.rs new file mode 100644 index 00000000..ac61e93c --- /dev/null +++ b/src/protocol/libp2p/kademlia/query/find_many_nodes.rs @@ -0,0 +1,65 @@ +// Copyright 2023 litep2p developers +// +// 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. + +use crate::{ + protocol::libp2p::kademlia::{ + query::{QueryAction, QueryId}, + types::KademliaPeer, + }, + PeerId, +}; + +/// Context for multiple `FIND_NODE` queries. +// TODO: implement finding nodes not present in the routing table, +// see https://github.com/paritytech/litep2p/issues/80. +#[derive(Debug)] +pub struct FindManyNodesContext { + /// Query ID. + pub query: QueryId, + + /// The peers we are looking for. + pub peers_to_report: Vec, +} + +impl FindManyNodesContext { + /// Creates a new [`FindManyNodesContext`]. + pub fn new(query: QueryId, peers_to_report: Vec) -> Self { + Self { + query, + peers_to_report, + } + } + + /// Register response failure for `peer`. + pub fn register_response_failure(&mut self, _peer: PeerId) {} + + /// Register `FIND_NODE` response from `peer`. + pub fn register_response(&mut self, _peer: PeerId, _peers: Vec) {} + + /// Get next action for `peer`. + pub fn next_peer_action(&mut self, _peer: &PeerId) -> Option { + None + } + + /// Get next action for a `FIND_NODE` query. + pub fn next_action(&mut self) -> Option { + return Some(QueryAction::QuerySucceeded { query: self.query }); + } +} diff --git a/src/protocol/libp2p/kademlia/query/get_record.rs b/src/protocol/libp2p/kademlia/query/get_record.rs index eb9d8235..5f16e2b2 100644 --- a/src/protocol/libp2p/kademlia/query/get_record.rs +++ b/src/protocol/libp2p/kademlia/query/get_record.rs @@ -24,7 +24,7 @@ use crate::{ protocol::libp2p::kademlia::{ message::KademliaMessage, query::{QueryAction, QueryId}, - record::{Key as RecordKey, Record}, + record::{Key as RecordKey, PeerRecord, Record}, types::{Distance, KademliaPeer, Key}, Quorum, }, @@ -66,7 +66,7 @@ pub struct GetRecordContext { pub candidates: BTreeMap, /// Found records. - pub found_records: Vec, + pub found_records: Vec, /// Replication factor. pub replication_factor: usize, @@ -110,7 +110,7 @@ impl GetRecordContext { } /// Get the found record. - pub fn found_record(mut self) -> Record { + pub fn found_record(mut self) -> PeerRecord { self.found_records.pop().expect("record to exist since query succeeded") } @@ -138,7 +138,10 @@ impl GetRecordContext { // TODO: validate record if let Some(record) = record { - self.found_records.push(record); + self.found_records.push(PeerRecord { + record, + peer: Some(peer.peer), + }); } // add the queried peer to `queried` and all new peers which haven't been diff --git a/src/protocol/libp2p/kademlia/query/mod.rs b/src/protocol/libp2p/kademlia/query/mod.rs index 690ead05..4bba3e1b 100644 --- a/src/protocol/libp2p/kademlia/query/mod.rs +++ b/src/protocol/libp2p/kademlia/query/mod.rs @@ -22,7 +22,7 @@ use crate::{ protocol::libp2p::kademlia::{ message::KademliaMessage, query::{find_node::FindNodeContext, get_record::GetRecordContext}, - record::{Key as RecordKey, Record}, + record::{Key as RecordKey, PeerRecord, Record}, types::{KademliaPeer, Key}, Quorum, }, @@ -33,6 +33,9 @@ use bytes::Bytes; use std::collections::{HashMap, VecDeque}; +use self::find_many_nodes::FindManyNodesContext; + +mod find_many_nodes; mod find_node; mod get_record; @@ -63,6 +66,15 @@ enum QueryType { context: FindNodeContext, }, + /// `PUT_VALUE` query to specified peers. + PutRecordToPeers { + /// Record that needs to be stored. + record: Record, + + /// Context for finding peers. + context: FindManyNodesContext, + }, + /// `GET_VALUE` query. GetRecord { /// Context for the `GET_VALUE` query. @@ -97,7 +109,7 @@ pub enum QueryAction { peers: Vec, }, - /// Store the record to nodest closest to target key. + /// Store the record to nodes closest to target key. // TODO: horrible name PutRecordToFoundNodes { /// Target peer. @@ -113,7 +125,7 @@ pub enum QueryAction { query_id: QueryId, /// Found record. - record: Record, + record: PeerRecord, }, // TODO: remove @@ -227,6 +239,32 @@ impl QueryEngine { query_id } + /// Start `PUT_VALUE` query to specified peers. + pub fn start_put_record_to_peers( + &mut self, + query_id: QueryId, + record: Record, + peers_to_report: Vec, + ) -> QueryId { + tracing::debug!( + target: LOG_TARGET, + ?query_id, + target = ?record.key, + num_peers = ?peers_to_report.len(), + "start `PUT_VALUE` query to peers" + ); + + self.queries.insert( + query_id, + QueryType::PutRecordToPeers { + record, + context: FindManyNodesContext::new(query_id, peers_to_report), + }, + ); + + query_id + } + /// Start `GET_VALUE` query. pub fn start_get_record( &mut self, @@ -280,6 +318,9 @@ impl QueryEngine { Some(QueryType::PutRecord { context, .. }) => { context.register_response_failure(peer); } + Some(QueryType::PutRecordToPeers { context, .. }) => { + context.register_response_failure(peer); + } Some(QueryType::GetRecord { context }) => { context.register_response_failure(peer); } @@ -307,6 +348,12 @@ impl QueryEngine { } _ => unreachable!(), }, + Some(QueryType::PutRecordToPeers { context, .. }) => match message { + KademliaMessage::FindNode { peers, .. } => { + context.register_response(peer, peers); + } + _ => unreachable!(), + }, Some(QueryType::GetRecord { context }) => match message { KademliaMessage::GetRecord { record, peers, .. } => { context.register_response(peer, record, peers); @@ -323,11 +370,12 @@ impl QueryEngine { match self.queries.get_mut(query) { None => { tracing::trace!(target: LOG_TARGET, ?query, ?peer, "response failure for a stale query"); - return None; + None } - Some(QueryType::FindNode { context }) => return context.next_peer_action(peer), - Some(QueryType::PutRecord { context, .. }) => return context.next_peer_action(peer), - Some(QueryType::GetRecord { context }) => return context.next_peer_action(peer), + Some(QueryType::FindNode { context }) => context.next_peer_action(peer), + Some(QueryType::PutRecord { context, .. }) => context.next_peer_action(peer), + Some(QueryType::PutRecordToPeers { context, .. }) => context.next_peer_action(peer), + Some(QueryType::GetRecord { context }) => context.next_peer_action(peer), } } @@ -344,6 +392,10 @@ impl QueryEngine { record, peers: context.responses.into_iter().map(|(_, peer)| peer).collect::>(), }, + QueryType::PutRecordToPeers { record, context } => QueryAction::PutRecordToFoundNodes { + record, + peers: context.peers_to_report, + }, QueryType::GetRecord { context } => QueryAction::GetRecordQueryDone { query_id: context.query, record: context.found_record(), @@ -365,6 +417,7 @@ impl QueryEngine { let action = match state { QueryType::FindNode { context } => context.next_action(), QueryType::PutRecord { context, .. } => context.next_action(), + QueryType::PutRecordToPeers { context, .. } => context.next_action(), QueryType::GetRecord { context } => context.next_action(), }; @@ -571,7 +624,7 @@ mod tests { let mut engine = QueryEngine::new(PeerId::random(), 20usize, 3usize); let record_key = RecordKey::new(&vec![1, 2, 3, 4]); let target_key = Key::new(record_key.clone()); - let original_record = Record::new(record_key, vec![1, 3, 3, 7, 1, 3, 3, 8]); + let original_record = Record::new(record_key.clone(), vec![1, 3, 3, 7, 1, 3, 3, 8]); let distances = { let mut distances = std::collections::BTreeMap::new(); @@ -651,15 +704,58 @@ mod tests { } } - match engine.next_action() { + let peers = match engine.next_action() { Some(QueryAction::PutRecordToFoundNodes { peers, record }) => { assert_eq!(peers.len(), 4); assert_eq!(record.key, original_record.key); assert_eq!(record.value, original_record.value); + peers } _ => panic!("invalid event received"), - } + }; assert!(engine.next_action().is_none()); + + // get records from those peers. + let _query = engine.start_get_record( + QueryId(1341), + record_key.clone(), + vec![ + KademliaPeer::new(peers[0].peer, vec![], ConnectionType::NotConnected), + KademliaPeer::new(peers[1].peer, vec![], ConnectionType::NotConnected), + KademliaPeer::new(peers[2].peer, vec![], ConnectionType::NotConnected), + KademliaPeer::new(peers[3].peer, vec![], ConnectionType::NotConnected), + ] + .into(), + Quorum::All, + 3, + ); + + for _ in 0..4 { + match engine.next_action() { + Some(QueryAction::SendMessage { query, peer, .. }) => { + engine.register_response( + query, + peer, + KademliaMessage::GetRecord { + record: Some(original_record.clone()), + peers: vec![], + key: Some(record_key.clone()), + }, + ); + } + _ => panic!("invalid event received"), + } + } + + let peers: std::collections::HashSet<_> = peers.into_iter().map(|p| p.peer).collect(); + match engine.next_action() { + Some(QueryAction::GetRecordQueryDone { record, .. }) => { + assert!(peers.contains(&record.peer.expect("Peer Id must be provided"))); + assert_eq!(record.record.key, original_record.key); + assert_eq!(record.record.value, original_record.value); + } + _ => panic!("invalid event received"), + } } } diff --git a/src/protocol/libp2p/kademlia/record.rs b/src/protocol/libp2p/kademlia/record.rs index 619f6ebf..bc7dcc10 100644 --- a/src/protocol/libp2p/kademlia/record.rs +++ b/src/protocol/libp2p/kademlia/record.rs @@ -104,7 +104,18 @@ impl Record { } /// Checks whether the record is expired w.r.t. the given `Instant`. - pub fn _is_expired(&self, now: Instant) -> bool { + pub fn is_expired(&self, now: Instant) -> bool { self.expires.map_or(false, |t| now >= t) } } + +/// A record either received by the given peer or retrieved from the local +/// record store. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PeerRecord { + /// The peer from whom the record was received. `None` if the record was + /// retrieved from local storage. + pub peer: Option, + + pub record: Record, +} diff --git a/src/protocol/libp2p/kademlia/routing_table.rs b/src/protocol/libp2p/kademlia/routing_table.rs index 333abbfb..0077c861 100644 --- a/src/protocol/libp2p/kademlia/routing_table.rs +++ b/src/protocol/libp2p/kademlia/routing_table.rs @@ -176,7 +176,7 @@ impl RoutingTable { } } - /// Get `limit` closests peers to `target` from the k-buckets. + /// Get `limit` closest peers to `target` from the k-buckets. pub fn closest(&mut self, target: Key, limit: usize) -> Vec { ClosestBucketsIter::new(self.local_key.distance(&target)) .map(|index| self.buckets[index.get()].closest_iter(&target)) @@ -189,8 +189,9 @@ impl RoutingTable { /// An iterator over the bucket indices, in the order determined by the `Distance` of a target from /// the `local_key`, such that the entries in the buckets are incrementally further away from the /// target, starting with the bucket covering the target. -/// The original implementation is taken from `rust-libp2p`, see [1] for the explanation of the -/// algorithm used. +/// The original implementation is taken from `rust-libp2p`, see [issue#1117][1] for the explanation +/// of the algorithm used. +/// /// [1]: https://github.com/libp2p/rust-libp2p/pull/1117#issuecomment-494694635 struct ClosestBucketsIter { /// The distance to the `local_key`. diff --git a/src/protocol/libp2p/kademlia/store.rs b/src/protocol/libp2p/kademlia/store.rs index 6fd158e1..8d3168c7 100644 --- a/src/protocol/libp2p/kademlia/store.rs +++ b/src/protocol/libp2p/kademlia/store.rs @@ -23,7 +23,7 @@ #![allow(unused)] use crate::protocol::libp2p::kademlia::record::{Key, Record}; -use std::collections::HashMap; +use std::collections::{hash_map::Entry, HashMap}; /// Memory store events. pub enum MemoryStoreEvent {} @@ -32,6 +32,8 @@ pub enum MemoryStoreEvent {} pub struct MemoryStore { /// Records. records: HashMap, + /// Configuration. + config: MemoryStoreConfig, } impl MemoryStore { @@ -39,17 +41,62 @@ impl MemoryStore { pub fn new() -> Self { Self { records: HashMap::new(), + config: MemoryStoreConfig::default(), + } + } + + /// Create new [`MemoryStore`] with the provided configuration. + pub fn with_config(config: MemoryStoreConfig) -> Self { + Self { + records: HashMap::new(), + config, } } /// Try to get record from local store for `key`. - pub fn get(&self, key: &Key) -> Option<&Record> { - self.records.get(key) + pub fn get(&mut self, key: &Key) -> Option<&Record> { + let is_expired = self + .records + .get(key) + .map_or(false, |record| record.is_expired(std::time::Instant::now())); + + if is_expired { + self.records.remove(key); + None + } else { + self.records.get(key) + } } /// Store record. pub fn put(&mut self, record: Record) { - self.records.insert(record.key.clone(), record); + if record.value.len() >= self.config.max_record_size_bytes { + return; + } + + let len = self.records.len(); + match self.records.entry(record.key.clone()) { + Entry::Occupied(mut entry) => { + // Lean towards the new record. + if let (Some(stored_record_ttl), Some(new_record_ttl)) = + (entry.get().expires, record.expires) + { + if stored_record_ttl > new_record_ttl { + return; + } + } + + entry.insert(record); + } + + Entry::Vacant(entry) => { + if len >= self.config.max_records { + return; + } + + entry.insert(record); + } + } } /// Poll next event from the store. @@ -57,3 +104,112 @@ impl MemoryStore { None } } + +pub struct MemoryStoreConfig { + /// Maximum number of records to store. + pub max_records: usize, + + /// Maximum size of a record in bytes. + pub max_record_size_bytes: usize, +} + +impl Default for MemoryStoreConfig { + fn default() -> Self { + Self { + max_records: 1024, + max_record_size_bytes: 65 * 1024, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_memory_store() { + let mut store = MemoryStore::new(); + let key = Key::from(vec![1, 2, 3]); + let record = Record::new(key.clone(), vec![4, 5, 6]); + + store.put(record.clone()); + assert_eq!(store.get(&key), Some(&record)); + } + + #[test] + fn test_memory_store_length() { + let mut store = MemoryStore::with_config(MemoryStoreConfig { + max_records: 1, + max_record_size_bytes: 1024, + }); + + let key1 = Key::from(vec![1, 2, 3]); + let key2 = Key::from(vec![4, 5, 6]); + let record1 = Record::new(key1.clone(), vec![4, 5, 6]); + let record2 = Record::new(key2.clone(), vec![7, 8, 9]); + + store.put(record1.clone()); + store.put(record2.clone()); + + assert_eq!(store.get(&key1), Some(&record1)); + assert_eq!(store.get(&key2), None); + } + + #[test] + fn test_memory_store_remove_old_records() { + let mut store = MemoryStore::new(); + let key = Key::from(vec![1, 2, 3]); + let record = Record { + key: key.clone(), + value: vec![4, 5, 6], + publisher: None, + expires: Some(std::time::Instant::now() - std::time::Duration::from_secs(5)), + }; + // Record is already expired. + assert!(record.is_expired(std::time::Instant::now())); + + store.put(record.clone()); + assert_eq!(store.get(&key), None); + } + + #[test] + fn test_memory_store_replace_new_records() { + let mut store = MemoryStore::new(); + let key = Key::from(vec![1, 2, 3]); + let record1 = Record { + key: key.clone(), + value: vec![4, 5, 6], + publisher: None, + expires: Some(std::time::Instant::now() + std::time::Duration::from_secs(100)), + }; + let record2 = Record { + key: key.clone(), + value: vec![4, 5, 6], + publisher: None, + expires: Some(std::time::Instant::now() + std::time::Duration::from_secs(1000)), + }; + + store.put(record1.clone()); + assert_eq!(store.get(&key), Some(&record1)); + + store.put(record2.clone()); + assert_eq!(store.get(&key), Some(&record2)); + } + + #[test] + fn test_memory_store_max_record_size() { + let mut store = MemoryStore::with_config(MemoryStoreConfig { + max_records: 1024, + max_record_size_bytes: 2, + }); + + let key = Key::from(vec![1, 2, 3]); + let record = Record::new(key.clone(), vec![4, 5]); + store.put(record.clone()); + assert_eq!(store.get(&key), None); + + let record = Record::new(key.clone(), vec![4]); + store.put(record.clone()); + assert_eq!(store.get(&key), Some(&record)); + } +} diff --git a/src/protocol/libp2p/kademlia/types.rs b/src/protocol/libp2p/kademlia/types.rs index eee03af9..fe9d04eb 100644 --- a/src/protocol/libp2p/kademlia/types.rs +++ b/src/protocol/libp2p/kademlia/types.rs @@ -57,8 +57,8 @@ impl Key { /// Constructs a new `Key` by running the given value through a random /// oracle. /// - /// The preimage of type `T` is preserved. See [`Key::preimage`] and - /// [`Key::into_preimage`]. + /// The preimage of type `T` is preserved. + /// See [`Key::into_preimage`] for more details. pub fn new(_preimage: T) -> Key where T: Borrow<[u8]>, diff --git a/src/protocol/mdns.rs b/src/protocol/mdns.rs index 119ff05c..5e7270ec 100644 --- a/src/protocol/mdns.rs +++ b/src/protocol/mdns.rs @@ -128,9 +128,8 @@ impl Mdns { ) -> crate::Result { let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?; socket.set_reuse_address(true)?; - // TODO: fix - // #[cfg(unix)] - // socket.set_reuse_port(true)?; + #[cfg(unix)] + socket.set_reuse_port(true)?; socket.bind( &SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), IPV4_MULTICAST_PORT).into(), )?; diff --git a/src/protocol/notification/mod.rs b/src/protocol/notification/mod.rs index 3126b78f..b820c68f 100644 --- a/src/protocol/notification/mod.rs +++ b/src/protocol/notification/mod.rs @@ -508,7 +508,7 @@ impl NotificationProtocol { /// substream is a result of a valid state transition. /// /// For the third case, if the nodes have opened substreams at the same time, the outbound state - /// must be [`OutbounState::OutboundInitiated`] to ascertain that the an outbound substream was + /// must be [`OutboundState::OutboundInitiated`] to ascertain that the an outbound substream was /// actually opened. Any other state would be a state mismatch and would mean that the /// connection is opening substreams without the permission of the protocol handler. async fn on_outbound_substream( @@ -1652,6 +1652,29 @@ impl NotificationProtocol { .report_notification_stream_open_failure(peer, NotificationError::Rejected) .await; + // NOTE: this is used to work around an issue in Substrate where the protocol + // is not notified if an inbound substream is closed. That indicates that remote + // wishes the close the connection but `Notifications` still keeps the substream state + // as `Open` until the outbound substream is closed (even though the outbound substream + // is also closed at that point). This causes a further issue: inbound substreams + // are automatically opened when state is `Open`, even if the inbound substream belongs + // to a new "connection" (pair of substreams). + // + // basically what happens (from Substrate's PoV) is there are pair of substreams (`inbound1`, `outbound1`), + // litep2p closes both substreams so both `inbound1` and outbound1 become non-readable/writable. + // Substrate doesn't detect this an instead only marks `inbound1` is closed while still keeping + // the (now-closed) `outbound1` active and it will be detected closed only when Substrate tries to + // write something into that substream. If now litep2p tries to open a new connection to Substrate, + // the outbound substream from litep2p's PoV will be automatically accepted (https://github.com/paritytech/polkadot-sdk/blob/59b2661444de2a25f2125a831bd786035a9fac4b/substrate/client/network/src/protocol/notifications/handler.rs#L544-L556) + // but since Substrate thinks `outbound1` is still active, it won't open a new outbound substream + // and it ends up having (`inbound2`, `outbound1`) as its pair of substreams which doens't make sense. + // + // since litep2p is expecting to receive an inbound substream from Substrate and never receives it, + // it basically can't make progress with the substream open request because litep2p can't force Substrate + // to detect that `outbound1` is closed. Easiest (and very hacky at the same time) way to reset the substream + // state is to close the connection. This is not an appropriate way to fix the issue and causes issues with, + // e.g., smoldot which at the time of writing this doesn't support the transaction protocol. The only way to fix + // this cleanly is to make Substrate detect closed substreams correctly. if let Err(error) = self.service.force_close(peer) { tracing::debug!( target: LOG_TARGET, diff --git a/src/protocol/notification/types.rs b/src/protocol/notification/types.rs index 45cf1487..114751e8 100644 --- a/src/protocol/notification/types.rs +++ b/src/protocol/notification/types.rs @@ -198,7 +198,7 @@ pub enum NotificationEvent { }, } -/// Notification commands sent by the [`NotificationService`] to the protocol. +/// Notification commands sent to the protocol. pub(crate) enum NotificationCommand { /// Open substreams to one or more peers. OpenSubstream { diff --git a/src/protocol/request_response/tests.rs b/src/protocol/request_response/tests.rs index fe277f14..524b3b2d 100644 --- a/src/protocol/request_response/tests.rs +++ b/src/protocol/request_response/tests.rs @@ -20,8 +20,7 @@ use crate::{ crypto::ed25519::Keypair, - mock::substream::DummySubstream, - mock::substream::MockSubstream, + mock::substream::{DummySubstream, MockSubstream}, protocol::{ request_response::{ ConfigBuilder, DialOptions, RequestResponseError, RequestResponseEvent, diff --git a/src/substream/mod.rs b/src/substream/mod.rs index 514b0c02..8818ac55 100644 --- a/src/substream/mod.rs +++ b/src/substream/mod.rs @@ -24,7 +24,7 @@ use crate::{ codec::ProtocolCodec, error::{Error, SubstreamError}, - transport::{quic, tcp, websocket}, + transport::{quic, tcp, webrtc, websocket}, types::SubstreamId, PeerId, }; @@ -44,7 +44,7 @@ use std::{ }; /// Logging target for the file. -const LOG_TARGET: &str = "substream"; +const LOG_TARGET: &str = "litep2p::substream"; macro_rules! poll_flush { ($substream:expr, $cx:ident) => {{ @@ -52,6 +52,7 @@ macro_rules! poll_flush { SubstreamType::Tcp(substream) => Pin::new(substream).poll_flush($cx), SubstreamType::WebSocket(substream) => Pin::new(substream).poll_flush($cx), SubstreamType::Quic(substream) => Pin::new(substream).poll_flush($cx), + SubstreamType::WebRtc(substream) => Pin::new(substream).poll_flush($cx), #[cfg(test)] SubstreamType::Mock(_) => unreachable!(), } @@ -64,6 +65,7 @@ macro_rules! poll_write { SubstreamType::Tcp(substream) => Pin::new(substream).poll_write($cx, $frame), SubstreamType::WebSocket(substream) => Pin::new(substream).poll_write($cx, $frame), SubstreamType::Quic(substream) => Pin::new(substream).poll_write($cx, $frame), + SubstreamType::WebRtc(substream) => Pin::new(substream).poll_write($cx, $frame), #[cfg(test)] SubstreamType::Mock(_) => unreachable!(), } @@ -76,6 +78,7 @@ macro_rules! poll_read { SubstreamType::Tcp(substream) => Pin::new(substream).poll_read($cx, $buffer), SubstreamType::WebSocket(substream) => Pin::new(substream).poll_read($cx, $buffer), SubstreamType::Quic(substream) => Pin::new(substream).poll_read($cx, $buffer), + SubstreamType::WebRtc(substream) => Pin::new(substream).poll_read($cx, $buffer), #[cfg(test)] SubstreamType::Mock(_) => unreachable!(), } @@ -88,6 +91,7 @@ macro_rules! poll_shutdown { SubstreamType::Tcp(substream) => Pin::new(substream).poll_shutdown($cx), SubstreamType::WebSocket(substream) => Pin::new(substream).poll_shutdown($cx), SubstreamType::Quic(substream) => Pin::new(substream).poll_shutdown($cx), + SubstreamType::WebRtc(substream) => Pin::new(substream).poll_shutdown($cx), #[cfg(test)] SubstreamType::Mock(substream) => { let _ = Pin::new(substream).poll_close($cx); @@ -148,6 +152,7 @@ enum SubstreamType { Tcp(tcp::Substream), WebSocket(websocket::Substream), Quic(quic::Substream), + WebRtc(webrtc::Substream), #[cfg(test)] Mock(Box), } @@ -158,6 +163,7 @@ impl fmt::Debug for SubstreamType { Self::Tcp(_) => write!(f, "Tcp"), Self::WebSocket(_) => write!(f, "WebSocket"), Self::Quic(_) => write!(f, "Quic"), + Self::WebRtc(_) => write!(f, "WebRtc"), #[cfg(test)] Self::Mock(_) => write!(f, "Mock"), } @@ -276,6 +282,18 @@ impl Substream { Self::new(peer, substream_id, SubstreamType::Quic(substream), codec) } + /// Create new [`Substream`] for WebRTC. + pub(crate) fn new_webrtc( + peer: PeerId, + substream_id: SubstreamId, + substream: webrtc::Substream, + codec: ProtocolCodec, + ) -> Self { + tracing::trace!(target: LOG_TARGET, ?peer, ?codec, "create new substream for webrtc"); + + Self::new(peer, substream_id, SubstreamType::WebRtc(substream), codec) + } + /// Create new [`Substream`] for mocking. #[cfg(test)] pub(crate) fn new_mock( @@ -299,6 +317,7 @@ impl Substream { SubstreamType::Tcp(mut substream) => substream.shutdown().await, SubstreamType::WebSocket(mut substream) => substream.shutdown().await, SubstreamType::Quic(mut substream) => substream.shutdown().await, + SubstreamType::WebRtc(mut substream) => substream.shutdown().await, #[cfg(test)] SubstreamType::Mock(mut substream) => { let _ = futures::SinkExt::close(&mut substream).await; @@ -409,6 +428,29 @@ impl Substream { substream.write_all_chunks(&mut [len.freeze(), bytes]).await } }, + SubstreamType::WebRtc(ref mut substream) => match self.codec { + ProtocolCodec::Unspecified => panic!("codec is unspecified"), + ProtocolCodec::Identity(payload_size) => + Self::send_identity_payload(substream, payload_size, bytes).await, + ProtocolCodec::UnsignedVarint(max_size) => { + check_size!(max_size, bytes.len()); + + let mut buffer = [0u8; 10]; + let len = unsigned_varint::encode::usize(bytes.len(), &mut buffer); + let mut offset = 0; + + while offset < len.len() { + offset += substream.write(&len[offset..]).await?; + } + + while bytes.has_remaining() { + let nwritten = substream.write(&bytes).await?; + bytes.advance(nwritten); + } + + substream.flush().await.map_err(From::from) + } + }, } } } @@ -625,6 +667,14 @@ impl Sink for Substream { // `MockSubstream` implements `Sink` so calls to `start_send()` must be delegated delegate_start_send!(&mut self.substream, item); + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + substream_id = ?self.substream_id, + data_len = item.len(), + "Substream::start_send()", + ); + match self.codec { ProtocolCodec::Identity(payload_size) => { if item.len() != payload_size { diff --git a/src/transport/manager/address.rs b/src/transport/manager/address.rs index 35daac59..5ff527a3 100644 --- a/src/transport/manager/address.rs +++ b/src/transport/manager/address.rs @@ -211,7 +211,7 @@ impl AddressStore { self.by_score.push(record); } - /// Pop address with the highest score from [`AddressScore`]. + /// Pop address with the highest score from [`AddressStore`]. pub fn pop(&mut self) -> Option { self.by_score.pop().map(|record| { self.by_address.remove(&record.address); diff --git a/src/transport/manager/handle.rs b/src/transport/manager/handle.rs index 66413385..c732296c 100644 --- a/src/transport/manager/handle.rs +++ b/src/transport/manager/handle.rs @@ -44,7 +44,8 @@ use std::{ }, }; -/// Inner commands sent from [`TransportManagerHandle`] to [`TransportManager`]. +/// Inner commands sent from [`TransportManagerHandle`] to +/// [`crate::transport::manager::TransportManager`]. pub enum InnerTransportManagerCommand { /// Dial peer. DialPeer { @@ -59,7 +60,7 @@ pub enum InnerTransportManagerCommand { }, } -/// Handle for communicating with [`TransportManager`]. +/// Handle for communicating with [`crate::transport::manager::TransportManager`]. #[derive(Debug, Clone)] pub struct TransportManagerHandle { /// Local peer ID. @@ -68,7 +69,7 @@ pub struct TransportManagerHandle { /// Peers. peers: Arc>>, - /// TX channel for sending commands to [`TransportManager`]. + /// TX channel for sending commands to [`crate::transport::manager::TransportManager`]. cmd_tx: Sender, /// Supported transports. diff --git a/src/transport/manager/mod.rs b/src/transport/manager/mod.rs index 46811924..a125b5f6 100644 --- a/src/transport/manager/mod.rs +++ b/src/transport/manager/mod.rs @@ -84,7 +84,7 @@ enum ConnectionEstablishedResult { Reject, } -/// [`TransportManager`] events. +/// [`crate::transport::manager::TransportManager`] events. pub enum TransportManagerEvent { /// Connection closed to remote peer. ConnectionClosed { @@ -228,7 +228,7 @@ pub struct TransportManager { /// Peers peers: Arc>>, - /// Handle to [`TransportManager`]. + /// Handle to [`crate::transport::manager::TransportManager`]. transport_manager_handle: TransportManagerHandle, /// RX channel for receiving events from installed transports. @@ -245,7 +245,7 @@ pub struct TransportManager { } impl TransportManager { - /// Create new [`TransportManager`]. + /// Create new [`crate::transport::manager::TransportManager`]. // TODO: don't return handle here pub fn new( keypair: Keypair, @@ -306,7 +306,7 @@ impl TransportManager { ConnectionId::from(connection_id) } - /// Register protocol to the [`TransportManager`]. + /// Register protocol to the [`crate::transport::manager::TransportManager`]. /// /// This allocates new context for the protocol and returns a handle /// which the protocol can use the interact with the transport subsystem. @@ -1338,7 +1338,7 @@ impl TransportManager { } } - /// Poll next event from [`TransportManager`]. + /// Poll next event from [`crate::transport::manager::TransportManager`]. pub async fn next(&mut self) -> Option { loop { tokio::select! { @@ -1576,7 +1576,7 @@ impl TransportManager { Ok(None) => {} } } - _ => panic!("event not supported"), + event => panic!("event not supported: {event:?}"), } }, } diff --git a/src/transport/manager/types.rs b/src/transport/manager/types.rs index 6cf25050..8bcdcdb9 100644 --- a/src/transport/manager/types.rs +++ b/src/transport/manager/types.rs @@ -84,8 +84,9 @@ pub enum PeerState { /// /// While the local node was dialing a remote peer, the remote peer might've dialed /// the local node and connection was established successfully. The connection might've - /// been closed before the dial concluded which means that [`TransportManager`] must be - /// prepared to handle the dial failure even after the connection has been closed. + /// been closed before the dial concluded which means that + /// [`crate::transport::manager::TransportManager`] must be prepared to handle the dial + /// failure even after the connection has been closed. dial_record: Option, }, } diff --git a/src/transport/quic/connection.rs b/src/transport/quic/connection.rs index 93767967..ec14d772 100644 --- a/src/transport/quic/connection.rs +++ b/src/transport/quic/connection.rs @@ -113,7 +113,7 @@ pub struct QuicConnection { } impl QuicConnection { - /// Create new [`Connection`]. + /// Creates a new [`QuicConnection`]. pub fn new( peer: PeerId, endpoint: Endpoint, @@ -230,7 +230,7 @@ impl QuicConnection { }) } - /// Start event loop for [`Connection`]. + /// Start event loop for [`QuicConnection`]. pub async fn start(mut self) -> crate::Result<()> { self.protocol_set .report_connection_established(self.peer, self.endpoint.clone()) diff --git a/src/transport/tcp/config.rs b/src/transport/tcp/config.rs index dc41fdc3..cd4926b2 100644 --- a/src/transport/tcp/config.rs +++ b/src/transport/tcp/config.rs @@ -30,9 +30,17 @@ use crate::{ pub struct Config { /// Listen address for the transport. /// - /// Default listen addres is `/ip6/::1/tcp`. + /// Default listen addresses are ["/ip4/0.0.0.0/tcp/0", "/ip6/::/tcp/0"]. pub listen_addresses: Vec, + /// Whether to set `SO_REUSEPORT` and bind a socket to the listen address port for outbound + /// connections. + /// + /// Note that `SO_REUSEADDR` is always set on listening sockets. + /// + /// Defaults to `true`. + pub reuse_port: bool, + /// Yamux configuration. pub yamux_config: crate::yamux::Config, @@ -76,6 +84,7 @@ impl Default for Config { "/ip4/0.0.0.0/tcp/0".parse().expect("valid address"), "/ip6/::/tcp/0".parse().expect("valid address"), ], + reuse_port: true, yamux_config: Default::default(), noise_read_ahead_frame_count: MAX_READ_AHEAD_FACTOR, noise_write_buffer_size: MAX_WRITE_BUFFER_SIZE, diff --git a/src/transport/tcp/listener.rs b/src/transport/tcp/listener.rs index 551551ba..2a1ddb72 100644 --- a/src/transport/tcp/listener.rs +++ b/src/transport/tcp/listener.rs @@ -56,40 +56,62 @@ pub struct TcpListener { listeners: Vec, } -#[derive(Clone, Default)] -pub struct DialAddresses { - /// Listen addresses. - listen_addresses: Arc>, +/// Local addresses to use for outbound connections. +#[derive(Clone)] +pub enum DialAddresses { + /// Reuse port from listen addresses. + Reuse { + listen_addresses: Arc>, + }, + /// Do not reuse port. + NoReuse, +} + +impl Default for DialAddresses { + fn default() -> Self { + DialAddresses::NoReuse + } } impl DialAddresses { /// Get local dial address for an outbound connection. - pub(super) fn local_dial_address(&self, remote_address: &IpAddr) -> Option { - for address in self.listen_addresses.iter() { - if remote_address.is_ipv4() == address.is_ipv4() - && remote_address.is_loopback() == address.ip().is_loopback() - { - if remote_address.is_ipv4() { - return Some(SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - address.port(), - )); - } else { - return Some(SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - address.port(), - )); + pub(super) fn local_dial_address( + &self, + remote_address: &IpAddr, + ) -> Result, ()> { + match self { + DialAddresses::Reuse { listen_addresses } => { + for address in listen_addresses.iter() { + if remote_address.is_ipv4() == address.is_ipv4() + && remote_address.is_loopback() == address.ip().is_loopback() + { + if remote_address.is_ipv4() { + return Ok(Some(SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + address.port(), + ))); + } else { + return Ok(Some(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + address.port(), + ))); + } + } } + + Err(()) } + DialAddresses::NoReuse => Ok(None), } - - None } } impl TcpListener { /// Create new [`TcpListener`] - pub fn new(addresses: Vec) -> (Self, Vec, DialAddresses) { + pub fn new( + addresses: Vec, + reuse_port: bool, + ) -> (Self, Vec, DialAddresses) { let (listeners, listen_addresses): (_, Vec>) = addresses .into_iter() .filter_map(|address| { @@ -117,7 +139,9 @@ impl TcpListener { socket.set_nonblocking(true).ok()?; socket.set_reuse_address(true).ok()?; #[cfg(unix)] - socket.set_reuse_port(true).ok()?; + if reuse_port { + socket.set_reuse_port(true).ok()?; + } socket.bind(&address.into()).ok()?; socket.listen(1024).ok()?; @@ -176,14 +200,15 @@ impl TcpListener { .with(Protocol::Tcp(address.port())) }) .collect(); - - ( - Self { listeners }, - listen_multi_addresses, - DialAddresses { + let dial_addresses = if reuse_port { + DialAddresses::Reuse { listen_addresses: Arc::new(listen_addresses), - }, - ) + } + } else { + DialAddresses::NoReuse + }; + + (Self { listeners }, listen_multi_addresses, dial_addresses) } /// Extract socket address and `PeerId`, if found, from `address`. @@ -319,7 +344,7 @@ mod tests { #[tokio::test] async fn no_listeners() { - let (mut listener, _, _) = TcpListener::new(Vec::new()); + let (mut listener, _, _) = TcpListener::new(Vec::new(), true); futures::future::poll_fn(|cx| match listener.poll_next_unpin(cx) { Poll::Pending => Poll::Ready(()), @@ -331,7 +356,7 @@ mod tests { #[tokio::test] async fn one_listener() { let address: Multiaddr = "/ip6/::1/tcp/0".parse().unwrap(); - let (mut listener, listen_addresses, _) = TcpListener::new(vec![address.clone()]); + let (mut listener, listen_addresses, _) = TcpListener::new(vec![address.clone()], true); let Some(Protocol::Tcp(port)) = listen_addresses.iter().next().unwrap().clone().iter().skip(1).next() else { @@ -348,7 +373,7 @@ mod tests { async fn two_listeners() { let address1: Multiaddr = "/ip6/::1/tcp/0".parse().unwrap(); let address2: Multiaddr = "/ip4/127.0.0.1/tcp/0".parse().unwrap(); - let (mut listener, listen_addresses, _) = TcpListener::new(vec![address1, address2]); + let (mut listener, listen_addresses, _) = TcpListener::new(vec![address1, address2], true); let Some(Protocol::Tcp(port1)) = listen_addresses.iter().next().unwrap().clone().iter().skip(1).next() else { @@ -373,7 +398,7 @@ mod tests { #[tokio::test] async fn local_dial_address() { - let dial_addresses = DialAddresses { + let dial_addresses = DialAddresses::Reuse { listen_addresses: Arc::new(vec![ "[2001:7d0:84aa:3900:2a5d:9e85::]:8888".parse().unwrap(), "92.168.127.1:9999".parse().unwrap(), @@ -382,12 +407,18 @@ mod tests { assert_eq!( dial_addresses.local_dial_address(&IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))), - Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9999)) + Ok(Some(SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + 9999 + ))), ); assert_eq!( dial_addresses.local_dial_address(&IpAddr::V6(Ipv6Addr::new(0, 1, 2, 3, 4, 5, 6, 7))), - Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8888)) + Ok(Some(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + 8888 + ))), ); } @@ -395,7 +426,7 @@ mod tests { async fn show_all_addresses() { let address1: Multiaddr = "/ip6/::/tcp/0".parse().unwrap(); let address2: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); - let (_, listen_addresses, _) = TcpListener::new(vec![address1, address2]); + let (_, listen_addresses, _) = TcpListener::new(vec![address1, address2], true); println!("{listen_addresses:#?}"); } diff --git a/src/transport/tcp/mod.rs b/src/transport/tcp/mod.rs index fb9312f2..0936a946 100644 --- a/src/transport/tcp/mod.rs +++ b/src/transport/tcp/mod.rs @@ -200,13 +200,14 @@ impl TcpTransport { socket.set_nonblocking(true)?; match dial_addresses.local_dial_address(&remote_address.ip()) { - Some(dial_address) => { + Ok(Some(dial_address)) => { socket.set_reuse_address(true)?; #[cfg(unix)] socket.set_reuse_port(true)?; socket.bind(&dial_address.into())?; } - None => { + Ok(None) => {} + Err(()) => { tracing::debug!( target: LOG_TARGET, ?remote_address, @@ -257,8 +258,10 @@ impl TransportBuilder for TcpTransport { ); // start tcp listeners for all listen addresses - let (listener, listen_addresses, dial_addresses) = - TcpListener::new(std::mem::replace(&mut config.listen_addresses, Vec::new())); + let (listener, listen_addresses, dial_addresses) = TcpListener::new( + std::mem::replace(&mut config.listen_addresses, Vec::new()), + config.reuse_port, + ); Ok(( Self { diff --git a/src/transport/webrtc/config.rs b/src/transport/webrtc/config.rs index 526829d2..b9314010 100644 --- a/src/transport/webrtc/config.rs +++ b/src/transport/webrtc/config.rs @@ -27,4 +27,20 @@ use multiaddr::Multiaddr; pub struct Config { /// WebRTC listening address. pub listen_addresses: Vec, + + /// Connection datagram buffer size. + /// + /// How many datagrams can the buffer between `WebRtcTransport` and a connection handler hold. + pub datagram_buffer_size: usize, +} + +impl Default for Config { + fn default() -> Self { + Self { + listen_addresses: vec!["/ip4/127.0.0.1/udp/8888/webrtc-direct" + .parse() + .expect("valid multiaddress")], + datagram_buffer_size: 2048, + } + } } diff --git a/src/transport/webrtc/connection.rs b/src/transport/webrtc/connection.rs index bf49fded..ef820000 100644 --- a/src/transport/webrtc/connection.rs +++ b/src/transport/webrtc/connection.rs @@ -18,33 +18,27 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -#![allow(unused)] - use crate::{ - config::Role, - crypto::{ed25519::Keypair, noise::NoiseContext}, error::Error, - multistream_select::{listener_negotiate, DialerState, HandshakeResult}, + multistream_select::{listener_negotiate, DialerState, HandshakeResult, ListenerSelectResult}, protocol::{Direction, Permit, ProtocolCommand, ProtocolSet}, substream::Substream, transport::{ webrtc::{ - substream::SubstreamBackend, - util::{SubstreamContext, WebRtcMessage}, - WebRtcEvent, + substream::{Event as SubstreamEvent, Substream as WebRtcSubstream, SubstreamHandle}, + util::WebRtcMessage, }, Endpoint, }, - types::{protocol::ProtocolName, ConnectionId, SubstreamId}, + types::{protocol::ProtocolName, SubstreamId}, PeerId, }; -use futures::StreamExt; -use multiaddr::{multihash::Multihash, Multiaddr, Protocol}; +use futures::{Stream, StreamExt}; +use indexmap::IndexMap; use str0m::{ - change::Fingerprint, - channel::{ChannelConfig, ChannelData, ChannelId}, - net::Receive, + channel::{ChannelConfig, ChannelId}, + net::{Protocol as Str0mProtocol, Receive}, Event, IceConnectionState, Input, Output, Rtc, }; use tokio::{net::UdpSocket, sync::mpsc::Receiver}; @@ -52,84 +46,117 @@ use tokio::{net::UdpSocket, sync::mpsc::Receiver}; use std::{ collections::HashMap, net::SocketAddr, + pin::Pin, sync::Arc, - time::{Duration, Instant}, + task::{Context, Poll}, + time::Instant, }; /// Logging target for the file. const LOG_TARGET: &str = "litep2p::webrtc::connection"; -/// Create Noise prologue. -fn noise_prologue_new(local_fingerprint: Vec, remote_fingerprint: Vec) -> Vec { - const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; - let mut prologue = - Vec::with_capacity(PREFIX.len() + local_fingerprint.len() + remote_fingerprint.len()); - prologue.extend_from_slice(PREFIX); - prologue.extend_from_slice(&remote_fingerprint); - prologue.extend_from_slice(&local_fingerprint); +/// Channel context. +#[derive(Debug)] +struct ChannelContext { + /// Protocol name. + protocol: ProtocolName, + + /// Fallback names. + fallback_names: Vec, + + /// Substream ID. + substream_id: SubstreamId, - prologue + /// Permit which keeps the connection open. + permit: Permit, } -/// WebRTC connection state. -#[derive(Debug)] -enum State { - /// Connection state is poisoned. - Poisoned, +/// Set of [`SubstreamHandle`]s. +struct SubstreamHandleSet { + /// Current index. + index: usize, - /// Connection state is closed. - Closed, + /// Substream handles. + handles: IndexMap, +} - /// Connection state is opened. - Opened { - /// Noise handshaker. - handshaker: NoiseContext, - }, +impl SubstreamHandleSet { + /// Create new [`SubstreamHandleSet`]. + pub fn new() -> Self { + Self { + index: 0usize, + handles: IndexMap::new(), + } + } - /// Handshake has been sent - HandshakeSent { - /// Noise handshaker. - handshaker: NoiseContext, - }, + /// Get mutable access to `SubstreamHandle`. + pub fn get_mut(&mut self, key: &ChannelId) -> Option<&mut SubstreamHandle> { + self.handles.get_mut(key) + } - /// Connection is open. - Open { - /// Remote peer ID. - peer: PeerId, - }, + /// Insert new handle to [`SubstreamHandleSet`]. + pub fn insert(&mut self, key: ChannelId, handle: SubstreamHandle) { + assert!(self.handles.insert(key, handle).is_none()); + } + + /// Remove handle from [`SubstreamHandleSet`]. + pub fn remove(&mut self, key: &ChannelId) -> Option { + self.handles.shift_remove(key) + } } -/// Substream state. +impl Stream for SubstreamHandleSet { + type Item = (ChannelId, Option); + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let len = match self.handles.len() { + 0 => return Poll::Pending, + len => len, + }; + let start_index = self.index; + + loop { + let index = self.index % len; + self.index += 1; + + let (key, stream) = self.handles.get_index_mut(index).expect("handle to exist"); + match stream.poll_next_unpin(cx) { + Poll::Pending => {} + Poll::Ready(event) => return Poll::Ready(Some((*key, event))), + } + + if self.index == start_index + len { + break Poll::Pending; + } + } + } +} + +/// Channel state. #[derive(Debug)] -enum SubstreamState { - /// Substream state is poisoned. - Poisoned, +enum ChannelState { + /// Channel is closing. + Closing, - /// Substream (outbound) is opening. - Opening { - /// Protocol. - protocol: ProtocolName, + /// Inbound channel is opening. + InboundOpening, - /// Negotiated fallback. - fallback: Option, + /// Outbound channel is opening. + OutboundOpening { + /// Channel context. + context: ChannelContext, /// `multistream-select` dialer state. dialer_state: DialerState, - - /// Substream ID, - substream_id: SubstreamId, - - /// Connection permit. - permit: Permit, }, - /// Substream is open. + /// Channel is open. Open { /// Substream ID. substream_id: SubstreamId, - /// Substream. - substream: SubstreamContext, + /// Channel ID. + channel_id: ChannelId, /// Connection permit. permit: Permit, @@ -137,26 +164,19 @@ enum SubstreamState { } /// WebRTC connection. -// TODO: too much stuff, refactor? -pub(super) struct WebRtcConnection { - /// Connection ID. - pub(super) connection_id: ConnectionId, - +pub struct WebRtcConnection { /// `str0m` WebRTC object. - pub(super) rtc: Rtc, - - /// Noise channel ID. - _noise_channel_id: ChannelId, - - /// Identity keypair. - id_keypair: Keypair, - - /// Connection state. - state: State, + rtc: Rtc, /// Protocol set. protocol_set: ProtocolSet, + /// Remote peer ID. + peer: PeerId, + + /// Endpoint. + endpoint: Endpoint, + /// Peer address peer_address: SocketAddr, @@ -169,430 +189,423 @@ pub(super) struct WebRtcConnection { /// RX channel for receiving datagrams from the transport. dgram_rx: Receiver>, - /// Substream backend. - backend: SubstreamBackend, - - /// Next substream ID. - substream_id: SubstreamId, + /// Pending outbound channels. + pending_outbound: HashMap, - /// Pending outbound substreams. - pending_outbound: HashMap, SubstreamId, Permit)>, + /// Open channels. + channels: HashMap, - /// Open substreams. - substreams: HashMap, + /// Substream handles. + handles: SubstreamHandleSet, } impl WebRtcConnection { - pub(super) fn new( + /// Create new [`WebRtcConnection`]. + pub fn new( rtc: Rtc, - connection_id: ConnectionId, - _noise_channel_id: ChannelId, - id_keypair: Keypair, - protocol_set: ProtocolSet, + peer: PeerId, peer_address: SocketAddr, local_address: SocketAddr, socket: Arc, + protocol_set: ProtocolSet, + endpoint: Endpoint, dgram_rx: Receiver>, - ) -> WebRtcConnection { - WebRtcConnection { + ) -> Self { + Self { rtc, - socket, - dgram_rx, protocol_set, - id_keypair, + peer, peer_address, local_address, - connection_id, - _noise_channel_id, - state: State::Closed, - substreams: HashMap::new(), - backend: SubstreamBackend::new(), - substream_id: SubstreamId::new(), + socket, + endpoint, + dgram_rx, pending_outbound: HashMap::new(), + channels: HashMap::new(), + handles: SubstreamHandleSet::new(), } } - pub(super) async fn poll_output(&mut self) -> crate::Result { - match self.rtc.poll_output() { - Ok(output) => self.handle_output(output).await, - Err(error) => { - tracing::debug!( - target: LOG_TARGET, - connection_id = ?self.connection_id, - ?error, - "`WebRtcConnection::poll_output()` failed", - ); - return Err(Error::WebRtc(error)); - } - } - } - - /// Handle data received from peer. - pub(super) async fn on_input(&mut self, buffer: Vec) -> crate::Result<()> { - let message = Input::Receive( - Instant::now(), - Receive { - source: self.peer_address, - destination: self.local_address, - contents: buffer.as_slice().try_into().map_err(|_| Error::InvalidData)?, - }, + /// Handle opened channel. + /// + /// If the channel is inbound, nothing is done because we have to wait for data + /// `multistream-select` handshake to be received from remote peer before anything + /// else can be done. + /// + /// If the channel is outbound, send `multistream-select` handshake to remote peer. + async fn on_channel_opened( + &mut self, + channel_id: ChannelId, + channel_name: String, + ) -> crate::Result<()> { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + ?channel_name, + "channel opened", ); - match self.rtc.accepts(&message) { - true => self.rtc.handle_input(message).map_err(|error| { - tracing::debug!(target: LOG_TARGET, source = ?self.peer_address, ?error, "failed to handle data"); - Error::InputRejected - }), - false => return Err(Error::InputRejected), - } - } + let Some(mut context) = self.pending_outbound.remove(&channel_id) else { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "inbound channel opened, wait for `multistream-select` message", + ); - async fn handle_output(&mut self, output: Output) -> crate::Result { - match output { - Output::Transmit(transmit) => { - self.socket - .send_to(&transmit.contents, transmit.destination) - .await - .expect("send to succeed"); - Ok(WebRtcEvent::Noop) - } - Output::Timeout(t) => Ok(WebRtcEvent::Timeout(t)), - Output::Event(e) => match e { - Event::IceConnectionStateChange(v) => { - if v == IceConnectionState::Disconnected { - tracing::debug!(target: LOG_TARGET, "ice connection closed"); - return Err(Error::Disconnected); - } - Ok(WebRtcEvent::Noop) - } - Event::ChannelOpen(cid, name) => { - // TODO: remove, report issue to smoldot - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - self.on_channel_open(cid, name).map(|_| WebRtcEvent::Noop) - } - Event::ChannelData(data) => self.on_channel_data(data).await, - Event::ChannelClose(channel_id) => { - // TODO: notify the protocol - tracing::debug!(target: LOG_TARGET, ?channel_id, "channel closed"); - Ok(WebRtcEvent::Noop) - } - Event::Connected => { - match std::mem::replace(&mut self.state, State::Poisoned) { - State::Closed => { - let remote_fingerprint = self.remote_fingerprint(); - let local_fingerprint = self.local_fingerprint(); - - let handshaker = NoiseContext::with_prologue( - &self.id_keypair, - noise_prologue_new(local_fingerprint, remote_fingerprint), - ); - - self.state = State::Opened { handshaker }; - } - state => { - tracing::debug!( - target: LOG_TARGET, - ?state, - "invalid state for connection" - ); - return Err(Error::InvalidState); - } - } - Ok(WebRtcEvent::Noop) - } - event => { - tracing::warn!(target: LOG_TARGET, ?event, "unhandled event"); - Ok(WebRtcEvent::Noop) - } - }, - } - } - - /// Get remote fingerprint to bytes. - fn remote_fingerprint(&mut self) -> Vec { - let fingerprint = self - .rtc - .direct_api() - .remote_dtls_fingerprint() - .clone() - .expect("fingerprint to exist"); - Self::fingerprint_to_bytes(&fingerprint) - } - - /// Get local fingerprint as bytes. - fn local_fingerprint(&mut self) -> Vec { - Self::fingerprint_to_bytes(&self.rtc.direct_api().local_dtls_fingerprint()) - } - - /// Convert `Fingerprint` to bytes. - fn fingerprint_to_bytes(fingerprint: &Fingerprint) -> Vec { - const MULTIHASH_SHA256_CODE: u64 = 0x12; - Multihash::wrap(MULTIHASH_SHA256_CODE, &fingerprint.bytes) - .expect("fingerprint's len to be 32 bytes") - .to_bytes() - } - - fn on_noise_channel_open(&mut self) -> crate::Result<()> { - tracing::trace!(target: LOG_TARGET, "send initial noise handshake"); - - let State::Opened { mut handshaker } = std::mem::replace(&mut self.state, State::Poisoned) - else { - return Err(Error::InvalidState); + self.channels.insert(channel_id, ChannelState::InboundOpening); + return Ok(()); }; - // create first noise handshake and send it to remote peer - let payload = WebRtcMessage::encode(handshaker.first_message(Role::Dialer), None); + let fallback_names = std::mem::replace(&mut context.fallback_names, Vec::new()); + let (dialer_state, message) = + DialerState::propose(context.protocol.clone(), fallback_names)?; + let message = WebRtcMessage::encode(message); self.rtc - .channel(self._noise_channel_id) + .channel(channel_id) .ok_or(Error::ChannelDoesntExist)? - .write(true, payload.as_slice()) + .write(true, message.as_ref()) .map_err(|error| Error::WebRtc(error))?; - self.state = State::HandshakeSent { handshaker }; + self.channels.insert( + channel_id, + ChannelState::OutboundOpening { + context, + dialer_state, + }, + ); + Ok(()) } - fn on_channel_open(&mut self, channel_id: ChannelId, name: String) -> crate::Result<()> { - tracing::debug!(target: LOG_TARGET, ?channel_id, channel_name = ?name, "channel opened"); + /// Handle closed channel. + async fn on_channel_closed(&mut self, channel_id: ChannelId) -> crate::Result<()> { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "channel closed", + ); - if channel_id == self._noise_channel_id { - return self.on_noise_channel_open(); - } - - match self.pending_outbound.remove(&channel_id) { - None => { - tracing::trace!(target: LOG_TARGET, ?channel_id, "remote opened a substream"); - } - Some((protocol, fallback_names, substream_id, permit)) => { - tracing::trace!(target: LOG_TARGET, ?channel_id, "dialer negotiate protocol"); - - let (dialer_state, message) = - DialerState::propose(protocol.clone(), fallback_names)?; - let message = WebRtcMessage::encode(message, None); - - self.rtc - .channel(channel_id) - .ok_or(Error::ChannelDoesntExist)? - .write(true, message.as_ref()) - .map_err(|error| Error::WebRtc(error))?; - - self.substreams.insert( - channel_id, - SubstreamState::Opening { - protocol, - fallback: None, - substream_id, - dialer_state, - permit, - }, - ); - } - } + self.pending_outbound.remove(&channel_id); + self.channels.remove(&channel_id); + self.handles.remove(&channel_id); Ok(()) } - async fn on_noise_channel_data(&mut self, data: Vec) -> crate::Result { - tracing::trace!(target: LOG_TARGET, "handle noise handshake reply"); + /// Handle data received to an opening inbound channel. + /// + /// The first message received over an inbound channel is the `multistream-select` handshake. + /// This handshake contains the protocol (and potentially fallbacks for that protocol) that + /// remote peer wants to use for this channel. Parse the handshake and check if any of the + /// proposed protocols are supported by the local node. If not, send rejection to remote peer + /// and close the channel. If the local node supports one of the protocols, send confirmation + /// for the protocol to remote peer and report an opened substream to the selected protocol. + async fn on_inbound_opening_channel_data( + &mut self, + channel_id: ChannelId, + data: Vec, + ) -> crate::Result<(SubstreamId, SubstreamHandle, Permit)> { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "handle opening inbound substream", + ); - let State::HandshakeSent { mut handshaker } = - std::mem::replace(&mut self.state, State::Poisoned) - else { - return Err(Error::InvalidState); - }; + let payload = WebRtcMessage::decode(&data)?.payload.ok_or(Error::InvalidData)?; + let (response, negotiated) = + match listener_negotiate(&mut self.protocol_set.protocols().iter(), payload.into())? { + ListenerSelectResult::Accepted { protocol, message } => (message, Some(protocol)), + ListenerSelectResult::Rejected { message } => (message, None), + }; - let message = WebRtcMessage::decode(&data)?.payload.ok_or(Error::InvalidData)?; - let public_key = handshaker.get_remote_public_key(&message)?; - let remote_peer_id = PeerId::from_public_key(&public_key); + self.rtc + .channel(channel_id) + .ok_or(Error::ChannelDoesntExist)? + .write(true, WebRtcMessage::encode(response.to_vec()).as_ref()) + .map_err(|error| Error::WebRtc(error))?; + + let protocol = negotiated.ok_or(Error::SubstreamDoesntExist)?; + let substream_id = self.protocol_set.next_substream_id(); + let codec = self.protocol_set.protocol_codec(&protocol); + let permit = self.protocol_set.try_get_permit().ok_or(Error::ConnectionClosed)?; + let (substream, handle) = WebRtcSubstream::new(); + let substream = Substream::new_webrtc(self.peer, substream_id, substream, codec); tracing::trace!( target: LOG_TARGET, - ?remote_peer_id, - "remote reply parsed successfully" + peer = ?self.peer, + ?channel_id, + ?substream_id, + ?protocol, + "inbound substream opened", ); - // create second noise handshake message and send it to remote - let payload = WebRtcMessage::encode(handshaker.second_message(), None); + self.protocol_set + .report_substream_open(self.peer, protocol.clone(), Direction::Inbound, substream) + .await + .map(|_| (substream_id, handle, permit)) + } - let mut channel = - self.rtc.channel(self._noise_channel_id).ok_or(Error::ChannelDoesntExist)?; + /// Handle data received to an opening outbound channel. + /// + /// When an outbound channel is opened, the first message the local node sends it the + /// `multistream-select` handshake which contains the protocol (and any fallbacks for that + /// protocol) that the local node wants to use to negotiate for the channel. When a message is + /// received from a remote peer for a channel in state [`ChannelState::OutboundOpening`], parse + /// the `multistream-select` handshake response. The response either contains a rejection which + /// causes the substream to be closed, a partial response, or a full response. If a partial + /// response is heard, e.g., only the header line is received, the handshake cannot be concluded + /// and the channel is placed back in the [`ChannelState::OutboundOpening`] state to wait for + /// the rest of the handshake. If a full response is received (or rest of the partial response), + /// the protocol confirmation is verified and the substream is reported to the protocol. + /// + /// If the substream fails to open for whatever reason, since this is an outbound substream, + /// the protocol is notified of the failure. + async fn on_outbound_opening_channel_data( + &mut self, + channel_id: ChannelId, + data: Vec, + mut dialer_state: DialerState, + context: ChannelContext, + ) -> crate::Result> { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "handle opening outbound substream", + ); - channel.write(true, payload.as_slice()).map_err(|error| Error::WebRtc(error))?; + let message = WebRtcMessage::decode(&data)?.payload.ok_or(Error::InvalidData)?; + let HandshakeResult::Succeeded(protocol) = dialer_state.register_response(message)? else { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "multisteam-select handshake not ready", + ); + + self.channels.insert( + channel_id, + ChannelState::OutboundOpening { + context, + dialer_state, + }, + ); - let remote_fingerprint = self - .rtc - .direct_api() - .remote_dtls_fingerprint() - .clone() - .expect("fingerprint to exist") - .bytes; + return Ok(None); + }; - const MULTIHASH_SHA256_CODE: u64 = 0x12; - let certificate = Multihash::wrap(MULTIHASH_SHA256_CODE, &remote_fingerprint) - .expect("fingerprint's len to be 32 bytes"); + let ChannelContext { + substream_id, + permit, + .. + } = context; + let codec = self.protocol_set.protocol_codec(&protocol); + let (substream, handle) = WebRtcSubstream::new(); + let substream = Substream::new_webrtc(self.peer, substream_id, substream, codec); - let address = Multiaddr::empty() - .with(Protocol::from(self.peer_address.ip())) - .with(Protocol::Udp(self.peer_address.port())) - .with(Protocol::WebRTC) - .with(Protocol::Certhash(certificate)) - .with(Protocol::P2p(PeerId::from(public_key).into())); + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + ?substream_id, + ?protocol, + "outbound substream opened", + ); self.protocol_set - .report_connection_established( - remote_peer_id, - Endpoint::listener(address, self.connection_id), + .report_substream_open( + self.peer, + protocol.clone(), + Direction::Outbound(substream_id), + substream, ) - .await?; - - self.state = State::Open { - peer: remote_peer_id, - }; - - Ok(WebRtcEvent::Noop) + .await + .map(|_| Some((substream_id, handle, permit))) } - /// Report open substream to the protocol. - async fn report_open_substream( + /// Handle data received from an open channel. + async fn on_open_channel_data( &mut self, channel_id: ChannelId, - protocol: ProtocolName, - ) -> crate::Result { - // let substream_id = self.substream_id.next(); - // let (mut substream, tx) = self.backend.substream(channel_id); - // let substream: Box = { - // substream.apply_codec(self.protocol_set.protocol_codec(&protocol)); - // Box::new(substream) - // }; - // let permit = self.protocol_set.try_get_permit().ok_or(Error::ConnectionClosed)?; - - // self.substreams.insert( - // channel_id, - // SubstreamState::Open { - // substream_id, - // substream: SubstreamContext::new(channel_id, tx), - // permit, - // }, - // ); - // TODO: fix - - if let State::Open { peer, .. } = &mut self.state { - // let _ = self - // .protocol_set - // .report_substream_open(*peer, protocol.clone(), Direction::Inbound, substream) - // .await; - todo!(); - } - - Ok(WebRtcEvent::Noop) - } - - /// Negotiate protocol for the channel - async fn listener_negotiate_protocol(&mut self, d: ChannelData) -> crate::Result { - tracing::trace!(target: LOG_TARGET, channel_id = ?d.id, "negotiate protocol for the channel"); - - let payload = WebRtcMessage::decode(&d.data)?.payload.ok_or(Error::InvalidData)?; + data: Vec, + ) -> crate::Result<()> { + let message = WebRtcMessage::decode(&data)?; - let (protocol, response) = - listener_negotiate(&mut self.protocol_set.protocols().iter(), payload.into())?; - - let message = WebRtcMessage::encode(response.to_vec(), None); - - self.rtc - .channel(d.id) - .ok_or(Error::ChannelDoesntExist)? - .write(true, message.as_ref()) - .map_err(|error| Error::WebRtc(error))?; + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + flags = message.flags, + data_len = message.payload.as_ref().map_or(0usize, |payload| payload.len()), + "handle inbound message", + ); - self.report_open_substream(d.id, protocol).await - - // let substream_id = self.substream_id.next(); - // let (mut substream, tx) = self.backend.substream(d.id); - // let substream: Box = { - // substream.apply_codec(self.protocol_set.protocol_codec(&protocol)); - // Box::new(substream) - // }; - // let permit = self.protocol_set.try_get_permit().ok_or(Error::ConnectionClosed)?; - - // self.substreams.insert( - // d.id, - // SubstreamState::Open { - // substream_id, - // substream: SubstreamContext::new(d.id, tx), - // permit, - // }, - // ); - - // if let State::Open { peer, .. } = &mut self.state { - // let _ = self - // .protocol_set - // .report_substream_open(*peer, protocol.clone(), Direction::Inbound, substream) - // .await; - // } - // Ok(WebRtcEvent::Noop) + self.handles + .get_mut(&channel_id) + .ok_or_else(|| { + tracing::warn!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "data received from an unknown channel", + ); + debug_assert!(false); + Error::InvalidState + })? + .on_message(message) + .await } - async fn on_channel_data(&mut self, d: ChannelData) -> crate::Result { - match &self.state { - State::HandshakeSent { .. } => self.on_noise_channel_data(d.data).await, - State::Open { .. } => { - match self.substreams.get_mut(&d.id) { - None => match self.listener_negotiate_protocol(d).await { - Ok(_) => { - tracing::debug!(target: LOG_TARGET, "protocol negotiated for the channel"); - - Ok(WebRtcEvent::Noop) - } - Err(error) => { - tracing::debug!(target: LOG_TARGET, ?error, "failed to negotiate protocol"); + /// Handle data received from a channel. + async fn on_inbound_data(&mut self, channel_id: ChannelId, data: Vec) -> crate::Result<()> { + let Some(state) = self.channels.remove(&channel_id) else { + tracing::warn!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "data received over a channel that doesn't exist", + ); + debug_assert!(false); + return Err(Error::InvalidState); + }; - // TODO: close channel - Ok(WebRtcEvent::Noop) - } - }, - Some(SubstreamState::Poisoned) => return Err(Error::ConnectionClosed), - Some(SubstreamState::Opening { - ref mut dialer_state, - .. - }) => { - tracing::info!(target: LOG_TARGET, "try to decode message"); - let message = - WebRtcMessage::decode(&d.data)?.payload.ok_or(Error::InvalidData)?; - tracing::info!(target: LOG_TARGET, "decoded successfully"); - - match dialer_state.register_response(message) { - Ok(HandshakeResult::NotReady) => {} - Ok(HandshakeResult::Succeeded(protocol)) => { - tracing::warn!(target: LOG_TARGET, ?protocol, "protocol negotiated, inform protocol handler"); - - return self.report_open_substream(d.id, protocol).await; - } - Err(error) => { - tracing::error!(target: LOG_TARGET, ?error, "failed to negotiate protocol"); - // TODO: close channel - } - } + match state { + ChannelState::InboundOpening => + match self.on_inbound_opening_channel_data(channel_id, data).await { + Ok((substream_id, handle, permit)) => { + self.handles.insert(channel_id, handle); + self.channels.insert( + channel_id, + ChannelState::Open { + substream_id, + channel_id, + permit, + }, + ); + } + Err(error) => { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + ?error, + "failed to handle opening inbound substream", + ); - Ok(WebRtcEvent::Noop) + self.channels.insert(channel_id, ChannelState::Closing); + self.rtc.direct_api().close_data_channel(channel_id); + } + }, + ChannelState::OutboundOpening { + context, + dialer_state, + } => { + let protocol = context.protocol.clone(); + let substream_id = context.substream_id; + + match self + .on_outbound_opening_channel_data(channel_id, data, dialer_state, context) + .await + { + Ok(Some((substream_id, handle, permit))) => { + self.handles.insert(channel_id, handle); + self.channels.insert( + channel_id, + ChannelState::Open { + substream_id, + channel_id, + permit, + }, + ); } - Some(SubstreamState::Open { substream, .. }) => { - // TODO: might be empty message with flags - // TODO: if decoding fails, close the substream - let message = - WebRtcMessage::decode(&d.data)?.payload.ok_or(Error::InvalidData)?; - let _ = substream.tx.send(message).await; - - Ok(WebRtcEvent::Noop) + Ok(None) => {} + Err(error) => { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + ?error, + "failed to handle opening outbound substream", + ); + + let _ = self + .protocol_set + .report_substream_open_failure(protocol, substream_id, error) + .await; + + self.rtc.direct_api().close_data_channel(channel_id); + self.channels.insert(channel_id, ChannelState::Closing); } } } - _ => Err(Error::InvalidState), + ChannelState::Open { + substream_id, + channel_id, + permit, + } => match self.on_open_channel_data(channel_id, data).await { + Ok(()) => { + self.channels.insert( + channel_id, + ChannelState::Open { + substream_id, + channel_id, + permit, + }, + ); + } + Err(error) => { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + ?error, + "failed to handle data for an open channel", + ); + + self.rtc.direct_api().close_data_channel(channel_id); + self.channels.insert(channel_id, ChannelState::Closing); + } + }, + ChannelState::Closing => { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "channel closing, discarding received data", + ); + self.channels.insert(channel_id, ChannelState::Closing); + } } + + Ok(()) + } + + /// Handle outbound data. + fn on_outbound_data(&mut self, channel_id: ChannelId, data: Vec) -> crate::Result<()> { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + data_len = ?data.len(), + "send data", + ); + + self.rtc + .channel(channel_id) + .ok_or(Error::ChannelDoesntExist)? + .write(true, WebRtcMessage::encode(data).as_ref()) + .map_err(|error| Error::WebRtc(error)) + .map(|_| ()) } /// Open outbound substream. - fn open_substream( + fn on_open_substream( &mut self, protocol: ProtocolName, fallback_names: Vec, @@ -600,7 +613,7 @@ impl WebRtcConnection { permit: Permit, ) { let channel_id = self.rtc.direct_api().create_data_channel(ChannelConfig { - label: protocol.to_string(), + label: "".to_string(), ordered: false, reliability: Default::default(), negotiated: None, @@ -609,114 +622,201 @@ impl WebRtcConnection { tracing::trace!( target: LOG_TARGET, + peer = ?self.peer, ?channel_id, ?substream_id, ?protocol, ?fallback_names, - "open data channel" + "open data channel", ); - self.pending_outbound - .insert(channel_id, (protocol, fallback_names, substream_id, permit)); + self.pending_outbound.insert( + channel_id, + ChannelContext { + protocol, + fallback_names, + substream_id, + permit, + }, + ); } - /// Run the event loop of a negotiated WebRTC connection. - pub(super) async fn run(mut self) -> crate::Result<()> { - loop { - if !self.rtc.is_alive() { - tracing::debug!( - target: LOG_TARGET, - "`Rtc` is not alive, closing `WebRtcConnection`" - ); - return Ok(()); - } + /// Connection to peer has been closed. + async fn on_connection_closed(&mut self) { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + "connection closed", + ); - let duration = match self.poll_output().await { - Ok(WebRtcEvent::Timeout(timeout)) => { - let timeout = - std::cmp::min(timeout, Instant::now() + Duration::from_millis(100)); - (timeout - Instant::now()).max(Duration::from_millis(1)) - } - Ok(WebRtcEvent::Noop) => continue, - Err(error) => { - tracing::debug!( + let _ = self + .protocol_set + .report_connection_closed(self.peer, self.endpoint.connection_id()) + .await; + } + + /// Start running event loop of [`WebRtcConnection`]. + pub async fn run(mut self) { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + "start webrtc connection event loop", + ); + + let _ = self + .protocol_set + .report_connection_established(self.peer, self.endpoint.clone()) + .await; + + loop { + // poll output until we get a timeout + let timeout = match self.rtc.poll_output().unwrap() { + Output::Timeout(v) => v, + Output::Transmit(v) => { + tracing::trace!( target: LOG_TARGET, - ?error, - "error occurred, closing connection" + peer = ?self.peer, + datagram_len = ?v.contents.len(), + "transmit data", ); - self.rtc.disconnect(); - return Ok(()); + + self.socket.try_send_to(&v.contents, v.destination).unwrap(); + continue; } + Output::Event(v) => match v { + Event::IceConnectionStateChange(IceConnectionState::Disconnected) => { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + "ice connection state changed to closed", + ); + return self.on_connection_closed().await; + } + Event::ChannelOpen(channel_id, name) => { + if let Err(error) = self.on_channel_opened(channel_id, name).await { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + ?error, + "failed to handle opened channel", + ); + } + + continue; + } + Event::ChannelClose(channel_id) => { + if let Err(error) = self.on_channel_closed(channel_id).await { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + ?error, + "failed to handle closed channel", + ); + } + + continue; + } + Event::ChannelData(info) => { + if let Err(error) = self.on_inbound_data(info.id, info.data).await { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + channel_id = ?info.id, + ?error, + "failed to handle channel data", + ); + } + + continue; + } + event => { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer, + ?event, + "unhandled event", + ); + continue; + } + }, }; + let duration = timeout - Instant::now(); + if duration.is_zero() { + self.rtc.handle_input(Input::Timeout(Instant::now())).unwrap(); + continue; + } + tokio::select! { - message = self.dgram_rx.recv() => match message { - Some(message) => match self.on_input(message).await { - Ok(_) | Err(Error::InputRejected) => {}, - Err(error) => { - tracing::debug!(target: LOG_TARGET, ?error, "failed to handle input"); - return Err(error) - } + biased; + datagram = self.dgram_rx.recv() => match datagram { + Some(datagram) => { + let input = Input::Receive( + Instant::now(), + Receive { + proto: Str0mProtocol::Udp, + source: self.peer_address, + destination: self.local_address, + contents: datagram.as_slice().try_into().unwrap(), + }, + ); + + self.rtc.handle_input(input).unwrap(); } None => { - tracing::debug!( + tracing::trace!( target: LOG_TARGET, - source = ?self.peer_address, - "transport shut down, shutting down connection", + peer = ?self.peer, + "read `None` from `dgram_rx`", ); - return Ok(()); + return self.on_connection_closed().await; } }, - event = self.backend.next_event() => { - let (channel_id, message) = event.ok_or(Error::EssentialTaskClosed)?; + event = self.handles.next() => match event { + None => unreachable!(), + Some((channel_id, None | Some(SubstreamEvent::Close))) => { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?channel_id, + "channel closed", + ); - match self.substreams.get_mut(&channel_id) { - None => { - tracing::debug!(target: LOG_TARGET, "protocol tried to send message over substream that doesn't exist"); - } - Some(SubstreamState::Poisoned) => {}, - Some(SubstreamState::Opening { .. }) => { - tracing::debug!(target: LOG_TARGET, "protocol tried to send message over substream that isn't open"); - } - Some(SubstreamState::Open { .. }) => { - tracing::trace!(target: LOG_TARGET, ?channel_id, ?message, "send message to remote peer"); - - self.rtc - .channel(channel_id) - .ok_or(Error::ChannelDoesntExist)? - .write(true, message.as_ref()) - .map_err(|error| Error::WebRtc(error))?; - } + self.rtc.direct_api().close_data_channel(channel_id); + self.channels.insert(channel_id, ChannelState::Closing); + self.handles.remove(&channel_id); } - } - event = self.protocol_set.next() => match event { - Some(event) => match event { - ProtocolCommand::OpenSubstream { protocol, fallback_names, substream_id, permit } => { - self.open_substream(protocol, fallback_names, substream_id, permit); - } - ProtocolCommand::ForceClose => { - tracing::debug!(target: LOG_TARGET, "force closing connection"); - return Ok(()); + Some((channel_id, Some(SubstreamEvent::Message(data)))) => { + if let Err(error) = self.on_outbound_data(channel_id, data) { + tracing::debug!( + target: LOG_TARGET, + ?channel_id, + ?error, + "failed to send data to remote peer", + ); } } - None => { - tracing::debug!(target: LOG_TARGET, "handle to protocol closed, closing connection"); - return Ok(()); + Some((_, Some(SubstreamEvent::RecvClosed))) => {} + }, + command = self.protocol_set.next() => match command { + None | Some(ProtocolCommand::ForceClose) => { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer, + ?command, + "`ProtocolSet` instructed to close connection", + ); + return self.on_connection_closed().await; + } + Some(ProtocolCommand::OpenSubstream { protocol, fallback_names, substream_id, permit }) => { + self.on_open_substream(protocol, fallback_names, substream_id, permit); } }, - _ = tokio::time::sleep(duration) => {} - } - - // drive time forward in the client - if let Err(error) = self.rtc.handle_input(Input::Timeout(Instant::now())) { - tracing::debug!( - target: LOG_TARGET, - ?error, - "failed to handle timeout for `Rtc`" - ); - - self.rtc.disconnect(); - return Err(Error::Disconnected); + _ = tokio::time::sleep(duration) => { + self.rtc.handle_input(Input::Timeout(Instant::now())).unwrap(); + } } } } diff --git a/src/transport/webrtc/mod.rs b/src/transport/webrtc/mod.rs index 5ee3616f..b0192d65 100644 --- a/src/transport/webrtc/mod.rs +++ b/src/transport/webrtc/mod.rs @@ -20,50 +20,52 @@ //! WebRTC transport. -#![allow(unused)] - use crate::{ error::{AddressError, Error}, transport::{ manager::TransportHandle, - webrtc::{config::Config, connection::WebRtcConnection}, - Transport, TransportBuilder, TransportEvent, + webrtc::{config::Config, connection::WebRtcConnection, opening::OpeningWebRtcConnection}, + Endpoint, Transport, TransportBuilder, TransportEvent, }, types::ConnectionId, PeerId, }; -use futures::{Stream, StreamExt}; +use futures::{future::BoxFuture, Future, Stream}; +use futures_timer::Delay; use multiaddr::{multihash::Multihash, Multiaddr, Protocol}; use socket2::{Domain, Socket, Type}; use str0m::{ change::{DtlsCert, IceCreds}, channel::{ChannelConfig, ChannelId}, - net::{DatagramRecv, Receive}, + net::{DatagramRecv, Protocol as Str0mProtocol, Receive}, Candidate, Input, Rtc, }; use tokio::{ io::ReadBuf, net::UdpSocket, - sync::mpsc::{channel, Sender}, + sync::mpsc::{channel, error::TrySendError, Sender}, }; use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, net::{IpAddr, SocketAddr}, pin::Pin, sync::Arc, task::{Context, Poll}, - time::Instant, + time::{Duration, Instant}, }; -pub mod config; +pub(crate) use substream::Substream; mod connection; +mod opening; mod substream; mod util; -mod schema { +pub mod config; + +pub(super) mod schema { pub(super) mod webrtc { include!(concat!(env!("OUT_DIR"), "/webrtc.rs")); } @@ -80,6 +82,40 @@ const LOG_TARGET: &str = "litep2p::webrtc"; const REMOTE_FINGERPRINT: &str = "sha-256 FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF"; +/// Connection context. +struct ConnectionContext { + /// Remote peer ID. + peer: PeerId, + + /// Connection ID. + connection_id: ConnectionId, + + /// TX channel for sending datagrams to the connection event loop. + tx: Sender>, +} + +/// Events received from opening connections that are handled +/// by the [`WebRtcTransport`] event loop. +enum ConnectionEvent { + /// Connection established. + ConnectionEstablished { + /// Remote peer ID. + peer: PeerId, + + /// Endpoint. + endpoint: Endpoint, + }, + + /// Connection to peer closed. + ConnectionClosed, + + /// Timeout. + Timeout { + /// Timeout duration. + duration: Duration, + }, +} + /// WebRTC transport. pub(crate) struct WebRtcTransport { /// Transport context. @@ -94,8 +130,23 @@ pub(crate) struct WebRtcTransport { /// Assigned listen addresss. listen_address: SocketAddr, + /// Datagram buffer size. + datagram_buffer_size: usize, + /// Connected peers. - peers: HashMap>>, + open: HashMap, + + /// OpeningWebRtc connections. + opening: HashMap, + + /// `ConnectionId -> SocketAddr` mappings. + connections: HashMap, + + /// Pending timeouts. + timeouts: HashMap>, + + /// Pending events. + pending_events: VecDeque, } impl WebRtcTransport { @@ -174,8 +225,8 @@ impl WebRtcTransport { .set_dtls_cert(self.dtls_cert.clone()) .set_fingerprint_verification(false) .build(); - rtc.add_local_candidate(Candidate::host(destination).unwrap()); - rtc.add_remote_candidate(Candidate::host(source).unwrap()); + rtc.add_local_candidate(Candidate::host(destination, Str0mProtocol::Udp).unwrap()); + rtc.add_remote_candidate(Candidate::host(source, Str0mProtocol::Udp).unwrap()); rtc.direct_api() .set_remote_fingerprint(REMOTE_FINGERPRINT.parse().expect("parse() to succeed")); rtc.direct_api().set_remote_ice_credentials(IceCreds { @@ -201,18 +252,88 @@ impl WebRtcTransport { (rtc, noise_channel_id) } + /// Poll opening connection. + fn poll_connection(&mut self, source: &SocketAddr) -> ConnectionEvent { + let Some(connection) = self.opening.get_mut(source) else { + tracing::warn!( + target: LOG_TARGET, + ?source, + "connection doesn't exist", + ); + return ConnectionEvent::ConnectionClosed; + }; + + loop { + match connection.poll_process() { + opening::WebRtcEvent::Timeout { timeout } => { + let duration = timeout - Instant::now(); + + match duration.is_zero() { + true => match connection.on_timeout() { + Ok(()) => continue, + Err(error) => { + tracing::debug!( + target: LOG_TARGET, + ?source, + ?error, + "failed to handle timeout", + ); + + return ConnectionEvent::ConnectionClosed; + } + }, + false => return ConnectionEvent::Timeout { duration }, + } + } + opening::WebRtcEvent::Transmit { + destination, + datagram, + } => + if let Err(error) = self.socket.try_send_to(&datagram, destination) { + tracing::warn!( + target: LOG_TARGET, + ?source, + ?error, + "failed to send datagram", + ); + }, + opening::WebRtcEvent::ConnectionClosed => return ConnectionEvent::ConnectionClosed, + opening::WebRtcEvent::ConnectionOpened { peer, endpoint } => { + return ConnectionEvent::ConnectionEstablished { peer, endpoint }; + } + } + } + } + /// Handle socket input. - fn on_socket_input(&mut self, source: SocketAddr, buffer: Vec) -> crate::Result<()> { - // if the `Rtc` object already exists for `souce`, pass the message directly to that - // connection. - if let Some(tx) = self.peers.get_mut(&source) { - // TODO: implement properly + /// + /// If the datagram was received from an active client, it's dispatched to the connection + /// handler, if there is space in the queue. If the datagram opened a new connection or it + /// belonged to a client who is opening, the event loop is instructed to poll the client + /// until it timeouts. + /// + /// Returns `true` if the client should be polled. + fn on_socket_input(&mut self, source: SocketAddr, buffer: Vec) -> crate::Result { + if let Some(ConnectionContext { + peer, + connection_id, + tx, + }) = self.open.get_mut(&source) + { match tx.try_send(buffer) { - Ok(()) => return Ok(()), - Err(error) => { - tracing::warn!(target: LOG_TARGET, ?error, "failed to send datagram to connection"); - return Ok(()); + Ok(_) => return Ok(false), + Err(TrySendError::Full(_)) => { + tracing::warn!( + target: LOG_TARGET, + ?source, + ?peer, + ?connection_id, + "channel full, dropping datagram", + ); + + return Ok(false); } + Err(TrySendError::Closed(_)) => return Ok(false), } } @@ -222,7 +343,7 @@ impl WebRtcTransport { buffer.as_slice().try_into().map_err(|_| Error::InvalidData)?; match contents { - DatagramRecv::Stun(message) => { + DatagramRecv::Stun(message) if !self.opening.contains_key(&source) => { if let Some((ufrag, pass)) = message.split_username() { tracing::debug!( target: LOG_TARGET, @@ -244,44 +365,38 @@ impl WebRtcTransport { Instant::now(), Receive { source, + proto: Str0mProtocol::Udp, destination: self.socket.local_addr().unwrap(), contents: DatagramRecv::Stun(message.clone()), }, )) .expect("client to handle input successfully"); - let (tx, rx) = channel(64); let connection_id = self.context.next_connection_id(); - - let connection = WebRtcConnection::new( + let connection = OpeningWebRtcConnection::new( rtc, connection_id, noise_channel_id, self.context.keypair.clone(), - self.context.protocol_set(connection_id), source, self.listen_address, - Arc::clone(&self.socket), - rx, ); - - self.context.executor.run(Box::pin(async move { - let _ = connection.run().await; - })); - self.peers.insert(source, tx); + self.opening.insert(source, connection); } } - message => { - tracing::error!( - target: LOG_TARGET, - ?source, - ?message, - "received unexpected message for a connection that doesn't eixst" - ); + msg => { + if let Err(error) = self.opening.get_mut(&source).expect("to exist").on_input(msg) { + tracing::error!( + target: LOG_TARGET, + ?error, + ?source, + "failed to handle inbound datagram" + ); + } } } - Ok(()) + Ok(true) } } @@ -303,18 +418,18 @@ impl TransportBuilder for WebRtcTransport { let (listen_address, _) = Self::get_socket_address(&config.listen_addresses[0])?; let socket = match listen_address.is_ipv4() { true => { - let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(socket2::Protocol::UDP))?; + let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(socket2::Protocol::UDP))?; socket.bind(&listen_address.into())?; socket } false => { - let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(socket2::Protocol::UDP))?; + let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(socket2::Protocol::UDP))?; socket.set_only_v6(true)?; socket.bind(&listen_address.into())?; socket } }; - socket.listen(1024)?; + socket.set_reuse_address(true)?; socket.set_nonblocking(true)?; #[cfg(unix)] @@ -343,8 +458,13 @@ impl TransportBuilder for WebRtcTransport { context, dtls_cert, listen_address, - peers: HashMap::new(), + open: HashMap::new(), + opening: HashMap::new(), + connections: HashMap::new(), socket: Arc::new(socket), + timeouts: HashMap::new(), + pending_events: VecDeque::new(), + datagram_buffer_size: config.datagram_buffer_size, }, listen_multi_addresses, )) @@ -363,12 +483,94 @@ impl Transport for WebRtcTransport { Err(Error::NotSupported(format!("webrtc cannot dial peers"))) } - fn accept(&mut self, _connection_id: ConnectionId) -> crate::Result<()> { + fn accept(&mut self, connection_id: ConnectionId) -> crate::Result<()> { + tracing::trace!( + target: LOG_TARGET, + ?connection_id, + "inbound connection accepted", + ); + + let (peer, source, endpoint) = + self.connections.remove(&connection_id).ok_or_else(|| { + tracing::warn!( + target: LOG_TARGET, + ?connection_id, + "pending connection doens't exist", + ); + + Error::InvalidState + })?; + + let connection = self.opening.remove(&source).ok_or_else(|| { + tracing::warn!( + target: LOG_TARGET, + ?connection_id, + "pending connection doens't exist", + ); + + Error::InvalidState + })?; + + let rtc = connection.on_accept()?; + let (tx, rx) = channel(self.datagram_buffer_size); + let protocol_set = self.context.protocol_set(connection_id); + let connection_id = endpoint.connection_id(); + + let connection = WebRtcConnection::new( + rtc, + peer, + source, + self.listen_address, + Arc::clone(&self.socket), + protocol_set, + endpoint, + rx, + ); + self.open.insert( + source, + ConnectionContext { + tx, + peer, + connection_id, + }, + ); + + self.context.executor.run(Box::pin(async move { + connection.run().await; + })); + Ok(()) } - fn reject(&mut self, _connection_id: ConnectionId) -> crate::Result<()> { - Ok(()) + fn reject(&mut self, connection_id: ConnectionId) -> crate::Result<()> { + tracing::trace!( + target: LOG_TARGET, + ?connection_id, + "inbound connection rejected", + ); + + let (_, source, _) = self.connections.remove(&connection_id).ok_or_else(|| { + tracing::warn!( + target: LOG_TARGET, + ?connection_id, + "pending connection doens't exist", + ); + + Error::InvalidState + })?; + + self.opening + .remove(&source) + .ok_or_else(|| { + tracing::warn!( + target: LOG_TARGET, + ?connection_id, + "pending connection doens't exist", + ); + + Error::InvalidState + }) + .map(|_| ()) } fn open( @@ -383,51 +585,130 @@ impl Transport for WebRtcTransport { Ok(()) } - /// Cancel opening connections. fn cancel(&mut self, _connection_id: ConnectionId) {} } impl Stream for WebRtcTransport { type Item = TransportEvent; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - // TODO: optimizations - let mut buf = vec![0u8; 16384]; - let mut read_buf = ReadBuf::new(&mut buf); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = Pin::into_inner(self); - match self.socket.poll_recv_from(cx, &mut read_buf) { - Poll::Pending => {} - Poll::Ready(Ok(source)) => { - let nread = read_buf.filled().len(); - buf.truncate(nread); + if let Some(event) = this.pending_events.pop_front() { + return Poll::Ready(Some(event)); + } - if let Err(error) = self.on_socket_input(source, buf) { - tracing::error!(target: LOG_TARGET, ?error, "failed to handle input"); - } - } - Poll::Ready(Err(error)) => { - tracing::debug!( - target: LOG_TARGET, - ?error, - "failed to read from webrtc socket", - ); + loop { + let mut buf = vec![0u8; 16384]; + let mut read_buf = ReadBuf::new(&mut buf); - return Poll::Ready(None); + match this.socket.poll_recv_from(cx, &mut read_buf) { + Poll::Pending => break, + Poll::Ready(Err(error)) => { + tracing::info!( + target: LOG_TARGET, + ?error, + "webrtc udp socket closed", + ); + + return Poll::Ready(None); + } + Poll::Ready(Ok(source)) => { + let nread = read_buf.filled().len(); + buf.truncate(nread); + + match this.on_socket_input(source, buf) { + Ok(false) => {} + Ok(true) => loop { + match this.poll_connection(&source) { + ConnectionEvent::ConnectionEstablished { peer, endpoint } => { + this.connections.insert( + endpoint.connection_id(), + (peer, source, endpoint.clone()), + ); + + // keep polling the connection until it registers a timeout + this.pending_events.push_back( + TransportEvent::ConnectionEstablished { peer, endpoint }, + ); + } + ConnectionEvent::ConnectionClosed => { + this.opening.remove(&source); + this.timeouts.remove(&source); + + break; + } + ConnectionEvent::Timeout { duration } => { + this.timeouts.insert( + source, + Box::pin(async move { Delay::new(duration).await }), + ); + + break; + } + } + }, + Err(error) => { + tracing::debug!( + target: LOG_TARGET, + ?source, + ?error, + "failed to handle datagram", + ); + } + } + } } } - Poll::Pending - } -} + // go over all pending timeouts to see if any of them have expired + // and if any of them have, poll the connection until it registers another timeout + let pending_events = this + .timeouts + .iter_mut() + .filter_map(|(source, mut delay)| match Pin::new(&mut delay).poll(cx) { + Poll::Pending => None, + Poll::Ready(_) => Some(*source), + }) + .collect::>() + .into_iter() + .filter_map(|source| { + let mut pending_event = None; + + loop { + match this.poll_connection(&source) { + ConnectionEvent::ConnectionEstablished { peer, endpoint } => { + this.connections + .insert(endpoint.connection_id(), (peer, source, endpoint.clone())); + + // keep polling the connection until it registers a timeout + pending_event = + Some(TransportEvent::ConnectionEstablished { peer, endpoint }); + } + ConnectionEvent::ConnectionClosed => { + this.opening.remove(&source); + return None; + } + ConnectionEvent::Timeout { duration } => { + this.timeouts.insert( + source, + Box::pin(async move { + Delay::new(duration); + }), + ); + break; + } + } + } -// TODO: remove -/// Events propagated between client. -#[allow(clippy::large_enum_variant)] -#[derive(Debug)] -enum WebRtcEvent { - /// When we have nothing to propagate. - Noop, + return pending_event; + }) + .collect::>(); - /// Poll client has reached timeout. - Timeout(Instant), + this.timeouts.retain(|source, _| this.opening.contains_key(source)); + this.pending_events.extend(pending_events); + this.pending_events + .pop_front() + .map_or(Poll::Pending, |event| Poll::Ready(Some(event))) + } } diff --git a/src/transport/webrtc/opening.rs b/src/transport/webrtc/opening.rs new file mode 100644 index 00000000..e4e39ea9 --- /dev/null +++ b/src/transport/webrtc/opening.rs @@ -0,0 +1,473 @@ +// Copyright 2023-2024 litep2p developers +// +// 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. + +//! WebRTC handshaking code for an opening connection. + +use crate::{ + config::Role, + crypto::{ed25519::Keypair, noise::NoiseContext}, + transport::{webrtc::util::WebRtcMessage, Endpoint}, + types::ConnectionId, + Error, PeerId, +}; + +use multiaddr::{multihash::Multihash, Multiaddr, Protocol}; +use str0m::{ + change::Fingerprint, + channel::ChannelId, + net::{DatagramRecv, DatagramSend, Protocol as Str0mProtocol, Receive}, + Event, IceConnectionState, Input, Output, Rtc, +}; + +use std::{net::SocketAddr, time::Instant}; + +/// Logging target for the file. +const LOG_TARGET: &str = "litep2p::webrtc::connection"; + +/// Create Noise prologue. +fn noise_prologue(local_fingerprint: Vec, remote_fingerprint: Vec) -> Vec { + const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; + let mut prologue = + Vec::with_capacity(PREFIX.len() + local_fingerprint.len() + remote_fingerprint.len()); + prologue.extend_from_slice(PREFIX); + prologue.extend_from_slice(&remote_fingerprint); + prologue.extend_from_slice(&local_fingerprint); + + prologue +} + +/// WebRTC connection event. +#[derive(Debug)] +pub enum WebRtcEvent { + /// Register timeout for the connection. + Timeout { + /// Timeout. + timeout: Instant, + }, + + /// Transmit data to remote peer. + Transmit { + /// Destination. + destination: SocketAddr, + + /// Datagram to transmit. + datagram: DatagramSend, + }, + + /// Connection closed. + ConnectionClosed, + + /// Connection established. + ConnectionOpened { + /// Remote peer ID. + peer: PeerId, + + /// Endpoint. + endpoint: Endpoint, + }, +} + +/// Opening WebRTC connection. +/// +/// This object is used to track an opening connection which starts with a Noise handshake. +/// After the handshake is done, this object is destroyed and a new WebRTC connection object +/// is created which implements a normal connection event loop dealing with substreams. +pub struct OpeningWebRtcConnection { + /// WebRTC object + rtc: Rtc, + + /// Connection state. + state: State, + + /// Connection ID. + connection_id: ConnectionId, + + /// Noise channel ID. + noise_channel_id: ChannelId, + + /// Local keypair. + id_keypair: Keypair, + + /// Peer address + peer_address: SocketAddr, + + /// Local address. + local_address: SocketAddr, +} + +/// Connection state. +#[derive(Debug)] +enum State { + /// Connection is poisoned. + Poisoned, + + /// Connection is closed. + Closed, + + /// Connection has been opened. + Opened { + /// Noise context. + context: NoiseContext, + }, + + /// Local Noise handshake has been sent to peer and the connection + /// is waiting for an answer. + HandshakeSent { + /// Noise context. + context: NoiseContext, + }, + + /// Response to local Noise handshake has been received and the connection + /// is being validated by `TransportManager`. + Validating { + /// Noise context. + context: NoiseContext, + }, +} + +impl OpeningWebRtcConnection { + /// Create new [`OpeningWebRtcConnection`]. + pub fn new( + rtc: Rtc, + connection_id: ConnectionId, + noise_channel_id: ChannelId, + id_keypair: Keypair, + peer_address: SocketAddr, + local_address: SocketAddr, + ) -> OpeningWebRtcConnection { + tracing::trace!( + target: LOG_TARGET, + ?connection_id, + ?peer_address, + "new connection opened", + ); + + Self { + rtc, + state: State::Closed, + connection_id, + noise_channel_id, + id_keypair, + peer_address, + local_address, + } + } + + /// Get remote fingerprint to bytes. + fn remote_fingerprint(&mut self) -> Vec { + let fingerprint = self + .rtc + .direct_api() + .remote_dtls_fingerprint() + .clone() + .expect("fingerprint to exist"); + Self::fingerprint_to_bytes(&fingerprint) + } + + /// Get local fingerprint as bytes. + fn local_fingerprint(&mut self) -> Vec { + Self::fingerprint_to_bytes(&self.rtc.direct_api().local_dtls_fingerprint()) + } + + /// Convert `Fingerprint` to bytes. + fn fingerprint_to_bytes(fingerprint: &Fingerprint) -> Vec { + const MULTIHASH_SHA256_CODE: u64 = 0x12; + Multihash::wrap(MULTIHASH_SHA256_CODE, &fingerprint.bytes) + .expect("fingerprint's len to be 32 bytes") + .to_bytes() + } + + /// Once a Noise data channel has been opened, even though the light client was the dialer, + /// the WebRTC server will act as the dialer as per the specification. + /// + /// Create the first Noise handshake message and send it to remote peer. + fn on_noise_channel_open(&mut self) -> crate::Result<()> { + tracing::trace!(target: LOG_TARGET, "send initial noise handshake"); + + let State::Opened { mut context } = std::mem::replace(&mut self.state, State::Poisoned) + else { + return Err(Error::InvalidState); + }; + + // create first noise handshake and send it to remote peer + let payload = WebRtcMessage::encode(context.first_message(Role::Dialer)); + + self.rtc + .channel(self.noise_channel_id) + .ok_or(Error::ChannelDoesntExist)? + .write(true, payload.as_slice()) + .map_err(|error| Error::WebRtc(error))?; + + self.state = State::HandshakeSent { context }; + Ok(()) + } + + /// Handle timeout. + pub fn on_timeout(&mut self) -> crate::Result<()> { + if let Err(error) = self.rtc.handle_input(Input::Timeout(Instant::now())) { + tracing::error!( + target: LOG_TARGET, + ?error, + "failed to handle timeout for `Rtc`" + ); + + self.rtc.disconnect(); + return Err(Error::Disconnected); + } + + Ok(()) + } + + /// Handle Noise handshake response. + /// + /// The message contains remote's peer ID which is used by the `TransportManager` to validate + /// the connection. Note the Noise handshake requires one more messages to be sent by the dialer + /// (us) but the inbound connection must first be verified by the `TransportManager` which will + /// either accept or reject the connection. + /// + /// If the peer is accepted, [`OpeningWebRtcConnection::on_accept()`] is called which creates + /// the final Noise message and sends it to the remote peer, concluding the handshake. + fn on_noise_channel_data(&mut self, data: Vec) -> crate::Result { + tracing::trace!(target: LOG_TARGET, "handle noise handshake reply"); + + let State::HandshakeSent { mut context } = + std::mem::replace(&mut self.state, State::Poisoned) + else { + return Err(Error::InvalidState); + }; + + let message = WebRtcMessage::decode(&data)?.payload.ok_or(Error::InvalidData)?; + let public_key = context.get_remote_public_key(&message)?; + let remote_peer_id = PeerId::from_public_key(&public_key); + + tracing::trace!( + target: LOG_TARGET, + ?remote_peer_id, + "remote reply parsed successfully", + ); + + self.state = State::Validating { context }; + + let remote_fingerprint = self + .rtc + .direct_api() + .remote_dtls_fingerprint() + .clone() + .expect("fingerprint to exist") + .bytes; + + const MULTIHASH_SHA256_CODE: u64 = 0x12; + let certificate = Multihash::wrap(MULTIHASH_SHA256_CODE, &remote_fingerprint) + .expect("fingerprint's len to be 32 bytes"); + + let address = Multiaddr::empty() + .with(Protocol::from(self.peer_address.ip())) + .with(Protocol::Udp(self.peer_address.port())) + .with(Protocol::WebRTC) + .with(Protocol::Certhash(certificate)) + .with(Protocol::P2p(PeerId::from(public_key).into())); + + Ok(WebRtcEvent::ConnectionOpened { + peer: remote_peer_id, + endpoint: Endpoint::listener(address, self.connection_id), + }) + } + + /// Accept connection by sending the final Noise handshake message + /// and return the `Rtc` object for further use. + pub fn on_accept(mut self) -> crate::Result { + tracing::trace!(target: LOG_TARGET, "accept webrtc connection"); + + let State::Validating { mut context } = std::mem::replace(&mut self.state, State::Poisoned) + else { + return Err(Error::InvalidState); + }; + + // create second noise handshake message and send it to remote + let payload = WebRtcMessage::encode(context.second_message()); + + let mut channel = + self.rtc.channel(self.noise_channel_id).ok_or(Error::ChannelDoesntExist)?; + + channel.write(true, payload.as_slice()).map_err(|error| Error::WebRtc(error))?; + self.rtc.direct_api().close_data_channel(self.noise_channel_id); + + Ok(self.rtc) + } + + /// Handle input from peer. + pub fn on_input(&mut self, buffer: DatagramRecv) -> crate::Result<()> { + tracing::trace!( + target: LOG_TARGET, + peer = ?self.peer_address, + "handle input from peer", + ); + + let message = Input::Receive( + Instant::now(), + Receive { + source: self.peer_address, + proto: Str0mProtocol::Udp, + destination: self.local_address, + contents: buffer, + }, + ); + + match self.rtc.accepts(&message) { + true => self.rtc.handle_input(message).map_err(|error| { + tracing::debug!(target: LOG_TARGET, source = ?self.peer_address, ?error, "failed to handle data"); + Error::InputRejected + }), + false => { + tracing::warn!( + target: LOG_TARGET, + peer = ?self.peer_address, + "input rejected", + ); + return Err(Error::InputRejected); + } + } + } + + /// Progress the state of [`OpeningWebRtcConnection`]. + pub fn poll_process(&mut self) -> WebRtcEvent { + if !self.rtc.is_alive() { + tracing::debug!( + target: LOG_TARGET, + "`Rtc` is not alive, closing `WebRtcConnection`" + ); + + return WebRtcEvent::ConnectionClosed; + } + + loop { + let output = match self.rtc.poll_output() { + Ok(output) => output, + Err(error) => { + tracing::debug!( + target: LOG_TARGET, + connection_id = ?self.connection_id, + ?error, + "`WebRtcConnection::poll_process()` failed", + ); + + return WebRtcEvent::ConnectionClosed; + } + }; + + match output { + Output::Transmit(transmit) => { + tracing::trace!( + target: LOG_TARGET, + "transmit data", + ); + + return WebRtcEvent::Transmit { + destination: transmit.destination, + datagram: transmit.contents, + }; + } + Output::Timeout(timeout) => return WebRtcEvent::Timeout { timeout }, + Output::Event(e) => match e { + Event::IceConnectionStateChange(v) => + if v == IceConnectionState::Disconnected { + tracing::trace!(target: LOG_TARGET, "ice connection closed"); + return WebRtcEvent::ConnectionClosed; + }, + Event::ChannelOpen(channel_id, name) => { + tracing::trace!( + target: LOG_TARGET, + connection_id = ?self.connection_id, + ?channel_id, + ?name, + "channel opened", + ); + + if channel_id != self.noise_channel_id { + tracing::warn!( + target: LOG_TARGET, + connection_id = ?self.connection_id, + ?channel_id, + "ignoring opened channel", + ); + continue; + } + + // TODO: no expect + self.on_noise_channel_open().expect("to succeed"); + } + Event::ChannelData(data) => { + tracing::trace!( + target: LOG_TARGET, + "data received over channel", + ); + + if data.id != self.noise_channel_id { + tracing::warn!( + target: LOG_TARGET, + channel_id = ?data.id, + connection_id = ?self.connection_id, + "ignoring data from channel", + ); + continue; + } + + // TODO: no expect + return self.on_noise_channel_data(data.data).expect("to succeed"); + } + Event::ChannelClose(channel_id) => { + tracing::debug!(target: LOG_TARGET, ?channel_id, "channel closed"); + } + Event::Connected => match std::mem::replace(&mut self.state, State::Poisoned) { + State::Closed => { + let remote_fingerprint = self.remote_fingerprint(); + let local_fingerprint = self.local_fingerprint(); + + let context = NoiseContext::with_prologue( + &self.id_keypair, + noise_prologue(local_fingerprint, remote_fingerprint), + ); + + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer_address, + "connection opened", + ); + + self.state = State::Opened { context }; + } + state => { + tracing::debug!( + target: LOG_TARGET, + peer = ?self.peer_address, + ?state, + "invalid state for connection" + ); + return WebRtcEvent::ConnectionClosed; + } + }, + event => { + tracing::warn!(target: LOG_TARGET, ?event, "unhandled event"); + } + }, + } + } + } +} diff --git a/src/transport/webrtc/substream.rs b/src/transport/webrtc/substream.rs index dad62cd4..b027b8af 100644 --- a/src/transport/webrtc/substream.rs +++ b/src/transport/webrtc/substream.rs @@ -1,4 +1,4 @@ -// Copyright 2023 litep2p developers +// Copyright 2024 litep2p developers // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), @@ -18,132 +18,447 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Channel-backed substream. - use crate::{ - codec::{identity::Identity, unsigned_varint::UnsignedVarint, ProtocolCodec}, - error::Error, + transport::webrtc::{schema::webrtc::message::Flag, util::WebRtcMessage}, + Error, }; -use bytes::BytesMut; -use futures::{Sink, Stream}; -use str0m::channel::ChannelId; +use bytes::{Buf, BufMut, BytesMut}; +use futures::{Future, Stream}; +use parking_lot::Mutex; use tokio::sync::mpsc::{channel, Receiver, Sender}; -use tokio_stream::wrappers::ReceiverStream; -use tokio_util::sync::PollSender; use std::{ pin::Pin, + sync::Arc, task::{Context, Poll}, }; -// TODO: use substream id +/// Maximum frame size. +const MAX_FRAME_SIZE: usize = 16384; + +/// Substream event. +#[derive(Debug, PartialEq, Eq)] +pub enum Event { + /// Receiver closed. + RecvClosed, + + /// Send/receive message. + Message(Vec), + + /// Close substream. + Close, +} + +/// Substream stream. +enum State { + /// Substream is fully open. + Open, -/// Channel-backed substream. -#[derive(Debug)] + /// Remote is no longer interested in receiving anything. + SendClosed, +} + +/// Channel-backedn substream. pub struct Substream { - /// Channel ID. - id: ChannelId, + /// Substream state. + state: Arc>, - /// TX channel for sending messages to transport. - tx: PollSender<(ChannelId, Vec)>, + /// Read buffer. + read_buffer: BytesMut, - /// RX channel for receiving messages from transport. - rx: ReceiverStream>, + /// TX channel for sending messages to `peer`. + tx: Sender, - /// Protocol codec. - codec: Option, + /// RX channel for receiving messages from `peer`. + rx: Receiver, } impl Substream { /// Create new [`Substream`]. - pub fn new(id: ChannelId, tx: Sender<(ChannelId, Vec)>) -> (Self, Sender>) { - let (to_protocol, rx) = channel(64); + pub fn new() -> (Self, SubstreamHandle) { + let (outbound_tx, outbound_rx) = channel(256); + let (inbound_tx, inbound_rx) = channel(256); + let state = Arc::new(Mutex::new(State::Open)); + let handle = SubstreamHandle { + tx: inbound_tx, + rx: outbound_rx, + state: Arc::clone(&state), + }; ( Self { - id, - codec: None, - tx: PollSender::new(tx), - rx: ReceiverStream::new(rx), + state, + tx: outbound_tx, + rx: inbound_rx, + read_buffer: BytesMut::new(), }, - to_protocol, + handle, ) } +} + +/// Substream handle that is given to the transport backend. +pub struct SubstreamHandle { + state: Arc>, + + /// TX channel for sending messages to `peer`. + tx: Sender, - /// Apply codec for the substream. - pub fn apply_codec(&mut self, codec: ProtocolCodec) { - self.codec = Some(codec); + /// RX channel for receiving messages from `peer`. + rx: Receiver, +} + +impl SubstreamHandle { + /// Handle message received from a remote peer. + /// + /// If the message contains any flags, handle them first and appropriately close the correct + /// side of the substream. If the message contained any payload, send it to the protocol for + /// further processing. + pub async fn on_message(&self, message: WebRtcMessage) -> crate::Result<()> { + if let Some(flags) = message.flags { + if flags == Flag::Fin as i32 { + let _ = self.tx.send(Event::RecvClosed).await?; + } + + if flags & 1 == Flag::StopSending as i32 { + *self.state.lock() = State::SendClosed; + } + + if flags & 2 == Flag::ResetStream as i32 { + return Err(Error::ConnectionClosed); + } + } + + if let Some(payload) = message.payload { + if !payload.is_empty() { + return self.tx.send(Event::Message(payload)).await.map_err(From::from); + } + } + + Ok(()) } } -impl Sink for Substream { - type Error = Error; +impl Stream for SubstreamHandle { + type Item = Event; - fn poll_ready<'a>(mut self: Pin<&mut Self>, cx: &mut Context<'a>) -> Poll> { - let pinned = Pin::new(&mut self.tx); - pinned.poll_ready(cx).map_err(|_| Error::Unknown) + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.rx.poll_recv(cx) } +} + +impl tokio::io::AsyncRead for Substream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + // if there are any remaining bytes from a previous read, consume them first + if self.read_buffer.remaining() > 0 { + let num_bytes = std::cmp::min(self.read_buffer.remaining(), buf.remaining()); - fn start_send(mut self: Pin<&mut Self>, item: bytes::Bytes) -> Result<(), Error> { - let item: Vec = match self.codec.as_ref().expect("codec to exist") { - ProtocolCodec::Identity(_) => Identity::encode(item)?.into(), - ProtocolCodec::UnsignedVarint(_) => UnsignedVarint::encode(item)?.into(), - ProtocolCodec::Unspecified => unreachable!(), // TODO: may not be correct + buf.put_slice(&self.read_buffer[..num_bytes]); + self.read_buffer.advance(num_bytes); + + // TODO: optimize by trying to read more data from substream and not exiting early + return Poll::Ready(Ok(())); + } + + loop { + match futures::ready!(self.rx.poll_recv(cx)) { + None | Some(Event::Close) | Some(Event::RecvClosed) => { + return Poll::Ready(Err(std::io::ErrorKind::BrokenPipe.into())); + } + Some(Event::Message(message)) => { + if message.len() > MAX_FRAME_SIZE { + return Poll::Ready(Err(std::io::ErrorKind::PermissionDenied.into())); + } + + match buf.remaining() >= message.len() { + true => buf.put_slice(&message), + false => { + let remaining = buf.remaining(); + buf.put_slice(&message[..remaining]); + self.read_buffer.put_slice(&message[remaining..]); + } + } + + return Poll::Ready(Ok(())); + } + } + } + } +} + +impl tokio::io::AsyncWrite for Substream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + if let State::SendClosed = *self.state.lock() { + return Poll::Ready(Err(std::io::ErrorKind::BrokenPipe.into())); + } + + // TODO: try to coalesce multiple calls to `poll_write()` into single `Event::Message` + + let num_bytes = std::cmp::min(MAX_FRAME_SIZE, buf.len()); + let future = self.tx.reserve(); + futures::pin_mut!(future); + + let permit = match futures::ready!(future.poll(cx)) { + Err(_) => return Poll::Ready(Err(std::io::ErrorKind::BrokenPipe.into())), + Ok(permit) => permit, }; - let id = self.id; - Pin::new(&mut self.tx).start_send((id, item)).map_err(|_| Error::Unknown) + let frame = buf[..num_bytes].to_vec(); + permit.send(Event::Message(frame)); + + Poll::Ready(Ok(num_bytes)) } - fn poll_flush<'a>(mut self: Pin<&mut Self>, cx: &mut Context<'a>) -> Poll> { - Pin::new(&mut self.tx).poll_flush(cx).map_err(|_| Error::Unknown) + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) } - fn poll_close<'a>(mut self: Pin<&mut Self>, cx: &mut Context<'a>) -> Poll> { - Pin::new(&mut self.tx).poll_close(cx).map_err(|_| Error::Unknown) + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let future = self.tx.reserve(); + futures::pin_mut!(future); + + let permit = match futures::ready!(future.poll(cx)) { + Err(_) => return Poll::Ready(Err(std::io::ErrorKind::BrokenPipe.into())), + Ok(permit) => permit, + }; + permit.send(Event::Close); + + Poll::Ready(Ok(())) } } -impl Stream for Substream { - type Item = crate::Result; +#[cfg(test)] +mod tests { + use super::*; + use futures::StreamExt; + use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; - fn poll_next<'a>( - mut self: Pin<&mut Self>, - cx: &mut Context<'a>, - ) -> Poll>> { - match Pin::new(&mut self.rx).poll_next(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(None) => Poll::Ready(None), - Poll::Ready(Some(value)) => Poll::Ready(Some(Ok(BytesMut::from(value.as_slice())))), + #[tokio::test] + async fn write_small_frame() { + let (mut substream, mut handle) = Substream::new(); + + substream.write_all(&vec![0u8; 1337]).await.unwrap(); + + assert_eq!(handle.next().await, Some(Event::Message(vec![0u8; 1337]))); + + futures::future::poll_fn(|cx| match handle.poll_next_unpin(cx) { + Poll::Pending => Poll::Ready(()), + Poll::Ready(_) => panic!("invalid event"), + }) + .await; + } + + #[tokio::test] + async fn write_large_frame() { + let (mut substream, mut handle) = Substream::new(); + + substream.write_all(&vec![0u8; (2 * MAX_FRAME_SIZE) + 1]).await.unwrap(); + + assert_eq!( + handle.rx.recv().await, + Some(Event::Message(vec![0u8; MAX_FRAME_SIZE])) + ); + assert_eq!( + handle.rx.recv().await, + Some(Event::Message(vec![0u8; MAX_FRAME_SIZE])) + ); + assert_eq!(handle.rx.recv().await, Some(Event::Message(vec![0u8; 1]))); + + futures::future::poll_fn(|cx| match handle.poll_next_unpin(cx) { + Poll::Pending => Poll::Ready(()), + Poll::Ready(_) => panic!("invalid event"), + }) + .await; + } + + #[tokio::test] + async fn try_to_write_to_closed_substream() { + let (mut substream, handle) = Substream::new(); + *handle.state.lock() = State::SendClosed; + + match substream.write_all(&vec![0u8; 1337]).await { + Err(error) => assert_eq!(error.kind(), std::io::ErrorKind::BrokenPipe), + _ => panic!("invalid event"), } } -} -// TODO: rename? -pub struct SubstreamBackend { - /// TX channel for creating new [`Substream`] objects. - tx: Sender<(ChannelId, Vec)>, + #[tokio::test] + async fn substream_shutdown() { + let (mut substream, mut handle) = Substream::new(); - /// RX channel for receiving messages from protocols. - rx: Receiver<(ChannelId, Vec)>, -} + substream.write_all(&vec![1u8; 1337]).await.unwrap(); + substream.shutdown().await.unwrap(); + + assert_eq!(handle.next().await, Some(Event::Message(vec![1u8; 1337]))); + assert_eq!(handle.next().await, Some(Event::Close)); + } + + #[tokio::test] + async fn try_to_read_from_closed_substream() { + let (mut substream, handle) = Substream::new(); + handle + .on_message(WebRtcMessage { + payload: None, + flags: Some(0i32), + }) + .await + .unwrap(); + + match substream.read(&mut vec![0u8; 256]).await { + Err(error) => assert_eq!(error.kind(), std::io::ErrorKind::BrokenPipe), + _ => panic!("invalid event"), + } + } + + #[tokio::test] + async fn read_small_frame() { + let (mut substream, handle) = Substream::new(); + handle.tx.send(Event::Message(vec![1u8; 256])).await.unwrap(); + + let mut buf = vec![0u8; 2048]; + + match substream.read(&mut buf).await { + Ok(nread) => { + assert_eq!(nread, 256); + assert_eq!(buf[..nread], vec![1u8; 256]); + } + Err(error) => panic!("invalid event: {error:?}"), + } + + let mut read_buf = ReadBuf::new(&mut buf); + futures::future::poll_fn(|cx| { + match Pin::new(&mut substream).poll_read(cx, &mut read_buf) { + Poll::Pending => Poll::Ready(()), + _ => panic!("invalid event"), + } + }) + .await; + } + + #[tokio::test] + async fn read_small_frame_in_two_reads() { + let (mut substream, handle) = Substream::new(); + let mut first = vec![1u8; 256]; + first.extend_from_slice(&vec![2u8; 256]); -impl SubstreamBackend { - /// Create new [`SubstreamBackend`]. - pub fn new() -> Self { - let (tx, rx) = channel(1024); + handle.tx.send(Event::Message(first)).await.unwrap(); - Self { tx, rx } + let mut buf = vec![0u8; 256]; + + match substream.read(&mut buf).await { + Ok(nread) => { + assert_eq!(nread, 256); + assert_eq!(buf[..nread], vec![1u8; 256]); + } + Err(error) => panic!("invalid event: {error:?}"), + } + + match substream.read(&mut buf).await { + Ok(nread) => { + assert_eq!(nread, 256); + assert_eq!(buf[..nread], vec![2u8; 256]); + } + Err(error) => panic!("invalid event: {error:?}"), + } + + let mut read_buf = ReadBuf::new(&mut buf); + futures::future::poll_fn(|cx| { + match Pin::new(&mut substream).poll_read(cx, &mut read_buf) { + Poll::Pending => Poll::Ready(()), + _ => panic!("invalid event"), + } + }) + .await; } - /// Create new substream. - pub fn substream(&mut self, id: ChannelId) -> (Substream, Sender>) { - Substream::new(id, self.tx.clone()) + #[tokio::test] + async fn read_frames() { + let (mut substream, handle) = Substream::new(); + let mut first = vec![1u8; 256]; + first.extend_from_slice(&vec![2u8; 256]); + + handle.tx.send(Event::Message(first)).await.unwrap(); + handle.tx.send(Event::Message(vec![4u8; 2048])).await.unwrap(); + + let mut buf = vec![0u8; 256]; + + match substream.read(&mut buf).await { + Ok(nread) => { + assert_eq!(nread, 256); + assert_eq!(buf[..nread], vec![1u8; 256]); + } + Err(error) => panic!("invalid event: {error:?}"), + } + + let mut buf = vec![0u8; 128]; + + match substream.read(&mut buf).await { + Ok(nread) => { + assert_eq!(nread, 128); + assert_eq!(buf[..nread], vec![2u8; 128]); + } + Err(error) => panic!("invalid event: {error:?}"), + } + + let mut buf = vec![0u8; 128]; + + match substream.read(&mut buf).await { + Ok(nread) => { + assert_eq!(nread, 128); + assert_eq!(buf[..nread], vec![2u8; 128]); + } + Err(error) => panic!("invalid event: {error:?}"), + } + + let mut buf = vec![0u8; MAX_FRAME_SIZE]; + + match substream.read(&mut buf).await { + Ok(nread) => { + assert_eq!(nread, 2048); + assert_eq!(buf[..nread], vec![4u8; 2048]); + } + Err(error) => panic!("invalid event: {error:?}"), + } + + let mut read_buf = ReadBuf::new(&mut buf); + futures::future::poll_fn(|cx| { + match Pin::new(&mut substream).poll_read(cx, &mut read_buf) { + Poll::Pending => Poll::Ready(()), + _ => panic!("invalid event"), + } + }) + .await; } - /// Poll next event. - pub async fn next_event(&mut self) -> Option<(ChannelId, Vec)> { - self.rx.recv().await + #[tokio::test] + async fn backpressure_works() { + let (mut substream, _handle) = Substream::new(); + + // use all available bandwidth which by default is `256 * MAX_FRAME_SIZE`, + for _ in 0..128 { + substream.write_all(&vec![0u8; 2 * MAX_FRAME_SIZE]).await.unwrap(); + } + + // try to write one more byte but since all available bandwidth + // is taken the call will block + futures::future::poll_fn(|cx| { + match Pin::new(&mut substream).poll_write(cx, &vec![0u8; 1]) { + Poll::Pending => Poll::Ready(()), + _ => panic!("invalid event"), + } + }) + .await; } } diff --git a/src/transport/webrtc/util.rs b/src/transport/webrtc/util.rs index e985f4ae..82939d73 100644 --- a/src/transport/webrtc/util.rs +++ b/src/transport/webrtc/util.rs @@ -21,27 +21,8 @@ use crate::{codec::unsigned_varint::UnsignedVarint, error::Error, transport::webrtc::schema}; use prost::Message; -use str0m::channel::ChannelId; -use tokio::sync::mpsc::Sender; use tokio_util::codec::{Decoder, Encoder}; -/// Substream context. -#[derive(Debug)] -pub struct SubstreamContext { - /// `str0m` channel id. - pub channel_id: ChannelId, - - /// TX channel for sending messages to the protocol. - pub tx: Sender>, -} - -impl SubstreamContext { - /// Create new [`SubstreamContext`]. - pub fn new(channel_id: ChannelId, tx: Sender>) -> Self { - Self { channel_id, tx } - } -} - /// WebRTC mesage. #[derive(Debug)] pub struct WebRtcMessage { @@ -54,10 +35,29 @@ pub struct WebRtcMessage { impl WebRtcMessage { /// Encode WebRTC message. - pub fn encode(payload: Vec, flag: Option) -> Vec { + pub fn encode(payload: Vec) -> Vec { let protobuf_payload = schema::webrtc::Message { message: (!payload.is_empty()).then_some(payload), - flag, + flag: None, + }; + let mut payload = Vec::with_capacity(protobuf_payload.encoded_len()); + protobuf_payload + .encode(&mut payload) + .expect("Vec to provide needed capacity"); + + let mut out_buf = bytes::BytesMut::with_capacity(payload.len() + 4); + let mut codec = UnsignedVarint::new(None); + let _result = codec.encode(payload.into(), &mut out_buf); + + out_buf.into() + } + + /// Encode WebRTC message with flags. + #[allow(unused)] + pub fn encode_with_flags(payload: Vec, flags: i32) -> Vec { + let protobuf_payload = schema::webrtc::Message { + message: (!payload.is_empty()).then_some(payload), + flag: Some(flags), }; let mut payload = Vec::with_capacity(protobuf_payload.encoded_len()); protobuf_payload @@ -65,7 +65,6 @@ impl WebRtcMessage { .expect("Vec to provide needed capacity"); let mut out_buf = bytes::BytesMut::with_capacity(payload.len() + 4); - // TODO: set correct size let mut codec = UnsignedVarint::new(None); let _result = codec.encode(payload.into(), &mut out_buf); @@ -95,7 +94,7 @@ mod tests { #[test] fn with_payload_no_flags() { - let message = WebRtcMessage::encode("Hello, world!".as_bytes().to_vec(), None); + let message = WebRtcMessage::encode("Hello, world!".as_bytes().to_vec()); let decoded = WebRtcMessage::decode(&message).unwrap(); assert_eq!(decoded.payload, Some("Hello, world!".as_bytes().to_vec())); @@ -104,7 +103,7 @@ mod tests { #[test] fn with_payload_and_flags() { - let message = WebRtcMessage::encode("Hello, world!".as_bytes().to_vec(), Some(1i32)); + let message = WebRtcMessage::encode_with_flags("Hello, world!".as_bytes().to_vec(), 1i32); let decoded = WebRtcMessage::decode(&message).unwrap(); assert_eq!(decoded.payload, Some("Hello, world!".as_bytes().to_vec())); @@ -113,7 +112,7 @@ mod tests { #[test] fn no_payload_with_flags() { - let message = WebRtcMessage::encode(vec![], Some(2i32)); + let message = WebRtcMessage::encode_with_flags(vec![], 2i32); let decoded = WebRtcMessage::decode(&message).unwrap(); assert_eq!(decoded.payload, None); diff --git a/src/transport/websocket/config.rs b/src/transport/websocket/config.rs index 77f7622d..1ec113a6 100644 --- a/src/transport/websocket/config.rs +++ b/src/transport/websocket/config.rs @@ -30,9 +30,17 @@ use crate::{ pub struct Config { /// Listen address address for the transport. /// - /// Default listen addres is `/ip6/::1/tcp/ws`. + /// Default listen addreses are ["/ip4/0.0.0.0/tcp/0/ws", "/ip6/::/tcp/0/ws"]. pub listen_addresses: Vec, + /// Whether to set `SO_REUSEPORT` and bind a socket to the listen address port for outbound + /// connections. + /// + /// Note that `SO_REUSEADDR` is always set on listening sockets. + /// + /// Defaults to `true`. + pub reuse_port: bool, + /// Yamux configuration. pub yamux_config: crate::yamux::Config, @@ -76,6 +84,7 @@ impl Default for Config { "/ip4/0.0.0.0/tcp/0/ws".parse().expect("valid address"), "/ip6/::/tcp/0/ws".parse().expect("valid address"), ], + reuse_port: true, yamux_config: Default::default(), noise_read_ahead_frame_count: MAX_READ_AHEAD_FACTOR, noise_write_buffer_size: MAX_WRITE_BUFFER_SIZE, diff --git a/src/transport/websocket/listener.rs b/src/transport/websocket/listener.rs index 597e5c7d..0a9ea37b 100644 --- a/src/transport/websocket/listener.rs +++ b/src/transport/websocket/listener.rs @@ -55,41 +55,62 @@ pub struct WebSocketListener { listeners: Vec, } -#[derive(Clone, Default)] -pub(super) struct DialAddresses { - /// Listen addresses. - listen_addresses: Arc>, +/// Local addresses to use for outbound connections. +#[derive(Clone)] +pub enum DialAddresses { + /// Reuse port from listen addresses. + Reuse { + listen_addresses: Arc>, + }, + /// Do not reuse port. + NoReuse, +} + +impl Default for DialAddresses { + fn default() -> Self { + DialAddresses::NoReuse + } } impl DialAddresses { /// Get local dial address for an outbound connection. - #[allow(unused)] - pub(super) fn local_dial_address(&self, remote_address: &IpAddr) -> Option { - for address in self.listen_addresses.iter() { - if remote_address.is_ipv4() == address.is_ipv4() - && remote_address.is_loopback() == address.ip().is_loopback() - { - if remote_address.is_ipv4() { - return Some(SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - address.port(), - )); - } else { - return Some(SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - address.port(), - )); + pub(super) fn local_dial_address( + &self, + remote_address: &IpAddr, + ) -> Result, ()> { + match self { + DialAddresses::Reuse { listen_addresses } => { + for address in listen_addresses.iter() { + if remote_address.is_ipv4() == address.is_ipv4() + && remote_address.is_loopback() == address.ip().is_loopback() + { + if remote_address.is_ipv4() { + return Ok(Some(SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + address.port(), + ))); + } else { + return Ok(Some(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + address.port(), + ))); + } + } } + + Err(()) } + DialAddresses::NoReuse => Ok(None), } - - None } } impl WebSocketListener { /// Create new [`WebSocketListener`] - pub fn new(addresses: Vec) -> (Self, Vec, DialAddresses) { + pub fn new( + addresses: Vec, + reuse_port: bool, + ) -> (Self, Vec, DialAddresses) { let (listeners, listen_addresses): (_, Vec>) = addresses .into_iter() .filter_map(|address| { @@ -122,7 +143,9 @@ impl WebSocketListener { socket.set_nonblocking(true).ok()?; socket.set_reuse_address(true).ok()?; #[cfg(unix)] - socket.set_reuse_port(true).ok()?; + if reuse_port { + socket.set_reuse_port(true).ok()?; + } socket.bind(&address.into()).ok()?; socket.listen(1024).ok()?; @@ -182,14 +205,15 @@ impl WebSocketListener { .with(Protocol::Ws(std::borrow::Cow::Owned("/".to_string()))) }) .collect(); - - ( - Self { listeners }, - listen_multi_addresses, - DialAddresses { + let dial_addresses = if reuse_port { + DialAddresses::Reuse { listen_addresses: Arc::new(listen_addresses), - }, - ) + } + } else { + DialAddresses::NoReuse + }; + + (Self { listeners }, listen_multi_addresses, dial_addresses) } /// Extract socket address and `PeerId`, if found, from `address`. @@ -379,7 +403,7 @@ mod tests { #[tokio::test] async fn no_listeners() { - let (mut listener, _, _) = WebSocketListener::new(Vec::new()); + let (mut listener, _, _) = WebSocketListener::new(Vec::new(), true); futures::future::poll_fn(|cx| match listener.poll_next_unpin(cx) { Poll::Pending => Poll::Ready(()), @@ -391,7 +415,8 @@ mod tests { #[tokio::test] async fn one_listener() { let address: Multiaddr = "/ip6/::1/tcp/0/ws".parse().unwrap(); - let (mut listener, listen_addresses, _) = WebSocketListener::new(vec![address.clone()]); + let (mut listener, listen_addresses, _) = + WebSocketListener::new(vec![address.clone()], true); let Some(Protocol::Tcp(port)) = listen_addresses.iter().next().unwrap().clone().iter().skip(1).next() else { @@ -408,7 +433,8 @@ mod tests { async fn two_listeners() { let address1: Multiaddr = "/ip6/::1/tcp/0/ws".parse().unwrap(); let address2: Multiaddr = "/ip4/127.0.0.1/tcp/0/ws".parse().unwrap(); - let (mut listener, listen_addresses, _) = WebSocketListener::new(vec![address1, address2]); + let (mut listener, listen_addresses, _) = + WebSocketListener::new(vec![address1, address2], true); let Some(Protocol::Tcp(port1)) = listen_addresses.iter().next().unwrap().clone().iter().skip(1).next() @@ -434,7 +460,7 @@ mod tests { #[tokio::test] async fn local_dial_address() { - let dial_addresses = DialAddresses { + let dial_addresses = DialAddresses::Reuse { listen_addresses: Arc::new(vec![ "[2001:7d0:84aa:3900:2a5d:9e85::]:8888".parse().unwrap(), "92.168.127.1:9999".parse().unwrap(), @@ -443,12 +469,18 @@ mod tests { assert_eq!( dial_addresses.local_dial_address(&IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))), - Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9999)) + Ok(Some(SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + 9999 + ))), ); assert_eq!( dial_addresses.local_dial_address(&IpAddr::V6(Ipv6Addr::new(0, 1, 2, 3, 4, 5, 6, 7))), - Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8888)) + Ok(Some(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + 8888 + ))), ); } } diff --git a/src/transport/websocket/mod.rs b/src/transport/websocket/mod.rs index eddfcb38..35a4ce58 100644 --- a/src/transport/websocket/mod.rs +++ b/src/transport/websocket/mod.rs @@ -247,13 +247,14 @@ impl WebSocketTransport { socket.set_nonblocking(true)?; match dial_addresses.local_dial_address(&remote_address.ip()) { - Some(dial_address) => { + Ok(Some(dial_address)) => { socket.set_reuse_address(true)?; #[cfg(unix)] socket.set_reuse_port(true)?; socket.bind(&dial_address.into())?; } - None => { + Ok(None) => {} + Err(()) => { tracing::debug!( target: LOG_TARGET, ?remote_address, @@ -311,8 +312,10 @@ impl TransportBuilder for WebSocketTransport { listen_addresses = ?config.listen_addresses, "start websocket transport", ); - let (listener, listen_addresses, dial_addresses) = - WebSocketListener::new(std::mem::replace(&mut config.listen_addresses, Vec::new())); + let (listener, listen_addresses, dial_addresses) = WebSocketListener::new( + std::mem::replace(&mut config.listen_addresses, Vec::new()), + config.reuse_port, + ); Ok(( Self { diff --git a/src/yamux/connection.rs b/src/yamux/connection.rs index e13e4d00..6ae85009 100644 --- a/src/yamux/connection.rs +++ b/src/yamux/connection.rs @@ -84,23 +84,33 @@ mod cleanup; mod closing; mod stream; -use crate::yamux::tagged_stream::TaggedStream; use crate::yamux::{ error::ConnectionError, - frame::header::{self, Data, GoAway, Header, Ping, StreamId, Tag, WindowUpdate, CONNECTION_ID}, - frame::{self, Frame}, - Config, WindowUpdateMode, DEFAULT_CREDIT, + frame::{ + self, + header::{self, Data, GoAway, Header, Ping, StreamId, Tag, WindowUpdate, CONNECTION_ID}, + Frame, + }, + tagged_stream::TaggedStream, + Config, Result, WindowUpdateMode, DEFAULT_CREDIT, MAX_ACK_BACKLOG, }; -use crate::yamux::{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 futures::{ + channel::mpsc, + future::Either, + prelude::*, + sink::SinkExt, + stream::{Fuse, SelectAll}, +}; use nohash_hasher::IntMap; use parking_lot::Mutex; -use std::collections::VecDeque; -use std::task::{Context, Waker}; -use std::{fmt, sync::Arc, task::Poll}; +use std::{ + collections::VecDeque, + fmt, + sync::Arc, + task::{Context, Poll, Waker}, +}; pub use stream::{Packet, State, Stream}; @@ -343,7 +353,7 @@ impl fmt::Debug for ConnectionState { /// A Yamux connection object. /// /// Wraps the underlying I/O resource and makes progress via its -/// [`Connection::next_stream`] method which must be called repeatedly +/// [`Connection::poll_next_inbound`] method which must be called repeatedly /// until `Ok(None)` signals EOF or an error is encountered. struct Active { id: Id, diff --git a/src/yamux/connection/cleanup.rs b/src/yamux/connection/cleanup.rs index 30379755..d1be682c 100644 --- a/src/yamux/connection/cleanup.rs +++ b/src/yamux/connection/cleanup.rs @@ -1,12 +1,12 @@ -use crate::yamux::connection::StreamCommand; -use crate::yamux::tagged_stream::TaggedStream; -use crate::yamux::{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}; +use crate::yamux::{ + connection::StreamCommand, tagged_stream::TaggedStream, ConnectionError, StreamId, +}; +use futures::{channel::mpsc, stream::SelectAll, StreamExt}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; /// A [`Future`] that cleans up resources in case of an error. #[must_use] diff --git a/src/yamux/connection/closing.rs b/src/yamux/connection/closing.rs index c2935310..6ae541e4 100644 --- a/src/yamux/connection/closing.rs +++ b/src/yamux/connection/closing.rs @@ -1,15 +1,18 @@ -use crate::yamux::connection::StreamCommand; -use crate::yamux::frame::Frame; -use crate::yamux::tagged_stream::TaggedStream; -use crate::yamux::Result; -use crate::yamux::{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}; +use crate::yamux::{ + connection::StreamCommand, frame, frame::Frame, tagged_stream::TaggedStream, Result, StreamId, +}; +use futures::{ + channel::mpsc, + ready, + stream::{Fuse, SelectAll}, + AsyncRead, AsyncWrite, SinkExt, StreamExt, +}; +use std::{ + collections::VecDeque, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; /// A [`Future`] that gracefully closes the yamux connection. #[must_use] diff --git a/src/yamux/connection/stream.rs b/src/yamux/connection/stream.rs index f3321c79..03e3fa39 100644 --- a/src/yamux/connection/stream.rs +++ b/src/yamux/connection/stream.rs @@ -8,12 +8,11 @@ // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. -use crate::yamux::frame::header::ACK; use crate::yamux::{ chunks::Chunks, connection::{self, StreamCommand}, frame::{ - header::{Data, Header, StreamId, WindowUpdate}, + header::{Data, Header, StreamId, WindowUpdate, ACK}, Frame, }, Config, WindowUpdateMode, DEFAULT_CREDIT, @@ -25,8 +24,8 @@ use futures::{ ready, SinkExt, }; use parking_lot::{Mutex, MutexGuard}; -use std::convert::TryInto; use std::{ + convert::TryInto, fmt, io, pin::Pin, sync::Arc, @@ -85,10 +84,10 @@ pub(crate) enum Flag { /// A multiplexed Yamux stream. /// -/// Streams are created either outbound via [`crate::Control::open_stream`] -/// or inbound via [`crate::Connection::next_stream`]. +/// Streams are created either outbound via [`crate::yamux::Control::open_stream`] +/// or inbound via [`crate::yamux::Connection::poll_next_inbound`]. /// -/// `Stream` implements [`AsyncRead`] and [`AsyncWrite`] and also +/// [`Stream`] implements [`AsyncRead`] and [`AsyncWrite`] and also /// [`futures::stream::Stream`]. pub struct Stream { id: StreamId, diff --git a/src/yamux/control.rs b/src/yamux/control.rs index 7be11380..89e3c350 100644 --- a/src/yamux/control.rs +++ b/src/yamux/control.rs @@ -8,14 +8,15 @@ // at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license // at https://opensource.org/licenses/MIT. -use crate::yamux::MAX_ACK_BACKLOG; -use crate::yamux::{error::ConnectionError, Connection, Result, Stream}; +use crate::yamux::{error::ConnectionError, Connection, Result, Stream, MAX_ACK_BACKLOG}; use futures::{ channel::{mpsc, oneshot}, prelude::*, }; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; /// A Yamux [`Connection`] controller. /// @@ -223,7 +224,7 @@ enum State { /// /// Closing connection involves two steps: /// -/// 1. Draining and answered all remaining [`ControlCommands`]. +/// 1. Draining and answered all remaining [`Closing::DrainingControlCommands`]. /// 1. Closing the underlying [`Connection`]. enum Closing { DrainingControlCommands { connection: Connection }, diff --git a/src/yamux/mod.rs b/src/yamux/mod.rs index 0da13dd9..2671e937 100644 --- a/src/yamux/mod.rs +++ b/src/yamux/mod.rs @@ -31,12 +31,14 @@ mod frame; pub(crate) mod connection; mod tagged_stream; -pub use crate::yamux::connection::{Connection, Mode, Packet, Stream}; -pub use crate::yamux::control::{Control, ControlledConnection}; -pub use crate::yamux::error::ConnectionError; -pub use crate::yamux::frame::{ - header::{HeaderDecodeError, StreamId}, - FrameDecodeError, +pub use crate::yamux::{ + connection::{Connection, Mode, Packet, Stream}, + control::{Control, ControlledConnection}, + error::ConnectionError, + frame::{ + header::{HeaderDecodeError, StreamId}, + FrameDecodeError, + }, }; pub const DEFAULT_CREDIT: u32 = 256 * 1024; // as per yamux specification @@ -60,7 +62,7 @@ const MAX_ACK_BACKLOG: usize = 256; /// 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 * 1024; /// Specifies when window update frames are sent. diff --git a/src/yamux/tagged_stream.rs b/src/yamux/tagged_stream.rs index 5c6035aa..5583a5b7 100644 --- a/src/yamux/tagged_stream.rs +++ b/src/yamux/tagged_stream.rs @@ -1,6 +1,8 @@ use futures::Stream; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; /// A stream that yields its tag with every item. #[pin_project::pin_project] diff --git a/tests/conformance/golang/mod.rs b/tests/conformance/golang/mod.rs deleted file mode 100644 index 322ce997..00000000 --- a/tests/conformance/golang/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(test)] -mod tcp_ping_identify; diff --git a/tests/conformance/golang/tcp_ping_identify/code/go.mod b/tests/conformance/golang/tcp_ping_identify/code/go.mod deleted file mode 100644 index c8cd7830..00000000 --- a/tests/conformance/golang/tcp_ping_identify/code/go.mod +++ /dev/null @@ -1,95 +0,0 @@ -module litep2p/ping - -go 1.20 - -require ( - github.com/libp2p/go-libp2p v0.28.1 - github.com/libp2p/go-libp2p-core v0.20.1 - github.com/multiformats/go-multiaddr v0.9.0 -) - -require ( - github.com/benbjohnson/clock v1.3.5 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/containerd/cgroups v1.1.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/elastic/gosigar v0.14.2 // indirect - github.com/flynn/noise v1.0.0 // indirect - github.com/francoispqt/gojay v1.2.13 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/huin/goupnp v1.2.0 // indirect - github.com/ipfs/go-cid v0.4.1 // indirect - github.com/ipfs/go-log/v2 v2.5.1 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect - github.com/klauspost/compress v1.16.5 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/koron/go-ssdp v0.0.4 // indirect - github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/libp2p/go-cidranger v1.1.0 // indirect - github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect - github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-nat v0.2.0 // indirect - github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/libp2p/go-reuseport v0.3.0 // indirect - github.com/libp2p/go-yamux/v4 v4.0.0 // indirect - github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.54 // indirect - github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect - github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect - github.com/minio/sha256-simd v1.0.1 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect - github.com/multiformats/go-base32 v0.1.0 // indirect - github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect - github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.9.0 // indirect - github.com/multiformats/go-multihash v0.2.2 // indirect - github.com/multiformats/go-multistream v0.4.1 // indirect - github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.9.7 // indirect - github.com/opencontainers/runtime-spec v1.0.2 // indirect - github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.2 // indirect - github.com/quic-go/qtls-go1-20 v0.2.2 // indirect - github.com/quic-go/quic-go v0.33.0 // indirect - github.com/quic-go/webtransport-go v0.5.3 // indirect - github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/dig v1.17.0 // indirect - go.uber.org/fx v1.19.2 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/tools v0.9.1 // indirect - google.golang.org/protobuf v1.30.0 // indirect - lukechampine.com/blake3 v1.2.1 // indirect -) diff --git a/tests/conformance/golang/tcp_ping_identify/code/go.sum b/tests/conformance/golang/tcp_ping_identify/code/go.sum deleted file mode 100644 index bf74ea67..00000000 --- a/tests/conformance/golang/tcp_ping_identify/code/go.sum +++ /dev/null @@ -1,802 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= -dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= -dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= -github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= -github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= -github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= -github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= -github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= -github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= -github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= -github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= -github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= -github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= -github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= -github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= -github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= -github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= -github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= -github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= -github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= -github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= -github.com/libp2p/go-libp2p v0.28.1 h1:YurK+ZAI6cKfASLJBVFkpVBdl3wGhFi6fusOt725ii8= -github.com/libp2p/go-libp2p v0.28.1/go.mod h1:s3Xabc9LSwOcnv9UD4nORnXKTsWkPMkIMB/JIGXVnzk= -github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= -github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= -github.com/libp2p/go-libp2p-core v0.20.1 h1:fQz4BJyIFmSZAiTbKV8qoYhEH5Dtv/cVhZbG3Ib/+Cw= -github.com/libp2p/go-libp2p-core v0.20.1/go.mod h1:6zR8H7CvQWgYLsbG4on6oLNSGcyKaYFSEYyDt51+bIY= -github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= -github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= -github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= -github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= -github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= -github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjPyvUw= -github.com/libp2p/go-reuseport v0.3.0/go.mod h1:laea40AimhtfEqysZ71UpYj4S+R9VpH8PgqLo7L+SwI= -github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= -github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= -github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= -github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= -github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= -github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= -github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= -github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= -github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= -github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= -github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= -github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= -github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= -github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= -github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= -github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= -github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= -github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= -github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= -github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= -github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= -github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= -github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= -github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= -github.com/multiformats/go-multihash v0.2.2 h1:Uu7LWs/PmWby1gkj1S1DXx3zyd3aVabA4FiMKn/2tAc= -github.com/multiformats/go-multihash v0.2.2/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= -github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= -github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= -github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= -github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= -github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= -github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= -github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= -github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= -github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= -github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= -github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= -github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= -github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= -github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= -github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= -github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= -github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= -github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= -github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= -github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= -github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= -github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= -github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= -go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= -go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= -go.uber.org/fx v1.19.2/go.mod h1:43G1VcqSzbIv77y00p1DRAsyZS8WdzuYdhZXmEUkMyQ= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= -lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/tests/conformance/golang/tcp_ping_identify/code/main.go b/tests/conformance/golang/tcp_ping_identify/code/main.go deleted file mode 100644 index 1ec18cad..00000000 --- a/tests/conformance/golang/tcp_ping_identify/code/main.go +++ /dev/null @@ -1,123 +0,0 @@ -package main - -import ( - "context" - "fmt" - "net" - "os" - "time" - - "encoding/binary" - "github.com/libp2p/go-libp2p" - "github.com/libp2p/go-libp2p/p2p/protocol/ping" - multiaddr "github.com/multiformats/go-multiaddr" - peerstore "github.com/libp2p/go-libp2p/core/peer" -) - -func main() { - // golog.SetAllLoggers(golog.LevelDebug) - - socketFile := "/tmp/ping-test.sock" - - // Remove the socket file if it already exists - _ = os.Remove(socketFile) - - // Create a Unix domain socket listener - listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: socketFile, Net: "unix"}) - socket, err := listener.Accept() - - // start libpp2p node - node, err := libp2p.New( - libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"), - libp2p.Ping(false), - ) - if err != nil { - panic(err) - } - - // configure our own ping protocol - pingService := &ping.PingService { Host: node } - node.SetStreamHandler(ping.ID, pingService.PingHandler) - - peerInfo := peerstore.AddrInfo { - ID: node.ID(), - Addrs: node.Addrs(), - } - addrs, err := peerstore.AddrInfoToP2pAddrs(&peerInfo) - fmt.Println("libp2p node address:", addrs[0]) - - // addrString := addrs[0].String() - // addrLen := len(addrString) - - // // Convert the length to a 2-byte vector - // lenBytes := make([]byte, 2) - // lenBytes[0] = byte(addrLen >> 8) // Most significant byte - // lenBytes[1] = byte(addrLen & 0xFF) // Least significant byte - - // _, err = socket.Write(lenBytes) - // if err != nil { - // fmt.Println("Error sending length:", err) - // return - // } - - // _, err = socket.Write([]byte(addrString)) - // if err != nil { - // fmt.Println("Error sending string:", err) - // return - // } - - // read litep2p node's address - lenBytes := make([]byte, 2) - _, err = socket.Read(lenBytes) - if err != nil { - fmt.Println("Error reading length:", err) - return - } - - // Convert the length bytes to uint16 - strLen := binary.BigEndian.Uint16(lenBytes) - - // Read the string bytes from the socket - strBytes := make([]byte, strLen) - _, err = socket.Read(strBytes) - if err != nil { - fmt.Println("Error reading string:", err) - return - } - - // Convert the string bytes to a string - str := string(strBytes) - - // monitor until the litep2p node closes the socket - socketClosed := make(chan bool) - go func() { - buf := make([]byte, 1024) - _, err := socket.Read(buf) - if err != nil { - socketClosed <- true - } - }() - - addr, err := multiaddr.NewMultiaddr(str) - if err != nil { - panic(err) - } - peer, err := peerstore.AddrInfoFromP2pAddr(addr) - if err != nil { - panic(err) - } - if err := node.Connect(context.Background(), *peer); err != nil { - panic(err) - } - - select { - case <- socketClosed: - listener.Close() - os.Remove("/tmp/ping-test.sock"); - case <- time.After(20 * time.Second): - fmt.Println("timeout, test failed"); - } - - listener.Close() - os.Remove("/tmp/ping-test.sock") -} diff --git a/tests/conformance/golang/tcp_ping_identify/mod.rs b/tests/conformance/golang/tcp_ping_identify/mod.rs deleted file mode 100644 index a074f598..00000000 --- a/tests/conformance/golang/tcp_ping_identify/mod.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2023 litep2p developers -// -// 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. - -use futures::StreamExt; -use litep2p::{ - config::ConfigBuilder, - crypto::ed25519::Keypair, - protocol::libp2p::{identify::Config as IdentifyConfig, ping::Config as PingConfig}, - transport::tcp::config::Config as TcpConfig, - Litep2p, -}; - -use std::{io::Write, os::unix::net::UnixStream, path::PathBuf}; - -#[tokio::test] -#[cfg(debug_assertions)] -async fn go_libp2p_dials() { - let _ = tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init(); - - let mut file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - file_path.push("tests/conformance/golang/tcp_ping_identify/ping_test"); - - let mut ping_test = std::process::Command::new(file_path.clone()).spawn().expect("to succeed"); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - - if ping_test.try_wait().unwrap().is_some() { - panic!("{file_path:?} terminated early. Is libc dependency satisfied?"); - } - - let mut stream = UnixStream::connect("/tmp/ping-test.sock").unwrap(); - - let keypair = Keypair::generate(); - let (ping_config, mut ping_event_stream) = PingConfig::default(); - let (identify_config, mut identify_event_stream) = IdentifyConfig::new(Vec::new()); - - let mut litep2p = Litep2p::new( - ConfigBuilder::new() - .with_keypair(keypair) - .with_tcp(TcpConfig { - ..Default::default() - }) - .with_libp2p_ping(ping_config) - .with_libp2p_identify(identify_config) - .build(), - ) - .unwrap(); - - let peer = *litep2p.local_peer_id(); - let mut address = litep2p.listen_addresses().next().unwrap().clone(); - address.push(multiaddr::Protocol::P2p((peer).into())); - - tracing::info!("address: {address:?}"); - - let address = address.to_string(); - let address = address.as_bytes(); - - tracing::info!("length {}", address.len()); - - let length_bytes: [u8; 2] = [(address.len() >> 8) as u8, address.len() as u8]; - - stream.write_all(&length_bytes).unwrap(); - stream.write_all(address).unwrap(); - - // stream.read(&mut len).unwrap(); - // let addr_len = u16::from_be_bytes([len[0], len[1]]); - // tracing::info!("string length {addr_len}"); - - // let mut address = vec![0u8; addr_len as usize]; - // stream.read_exact(&mut address).unwrap(); - - // tracing::info!("{:?}", std::str::from_utf8(&address).unwrap()); - // tracing::info!("{:?}", address); - - // litep2p.connect(address).unwrap(); - - tokio::spawn(async move { - loop { - let _ = litep2p.next_event().await; - } - }); - - let mut ping_received = false; - let mut identify_received = false; - - loop { - tokio::select! { - _event = ping_event_stream.next() => { - ping_received = true; - } - _event = identify_event_stream.next() => { - identify_received = true; - } - _ = tokio::time::sleep(std::time::Duration::from_secs(5)) => { - panic!("failed to receive ping in time"); - } - } - - if identify_received && ping_received { - break; - } - } - - // drop the UDS connection and give the Golang program a little time to clean up resources - drop(stream); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; -} diff --git a/tests/conformance/golang/tcp_ping_identify/ping_test b/tests/conformance/golang/tcp_ping_identify/ping_test deleted file mode 100755 index 37f5832b..00000000 Binary files a/tests/conformance/golang/tcp_ping_identify/ping_test and /dev/null differ diff --git a/tests/conformance/mod.rs b/tests/conformance/mod.rs index 1bd56908..fa9771f9 100644 --- a/tests/conformance/mod.rs +++ b/tests/conformance/mod.rs @@ -18,8 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -#[cfg(test)] -mod golang; #[cfg(test)] mod rust; #[cfg(test)] diff --git a/tests/conformance/rust/identify.rs b/tests/conformance/rust/identify.rs index 97774fd8..c29ca882 100644 --- a/tests/conformance/rust/identify.rs +++ b/tests/conformance/rust/identify.rs @@ -71,7 +71,8 @@ fn initialize_litep2p() -> ( ) { let keypair = Keypair::generate(); let (ping_config, ping_event_stream) = PingConfig::default(); - let (identify_config, identify_event_stream) = IdentifyConfig::new(Vec::new()); + let (identify_config, identify_event_stream) = + IdentifyConfig::new("proto v1".to_string(), None, Vec::new()); let litep2p = Litep2p::new( ConfigBuilder::new() @@ -97,10 +98,10 @@ fn initialize_libp2p() -> Swarm { let transport = libp2p::tokio_development_transport(local_key.clone()).unwrap(); let behaviour = MyBehaviour { - identify: identify::Behaviour::new(identify::Config::new( - "/ipfs/1.0.0".into(), - local_key.public(), - )), + identify: identify::Behaviour::new( + identify::Config::new("/ipfs/1.0.0".into(), local_key.public()) + .with_agent_version("libp2p agent".to_string()), + ), ping: Default::default(), }; let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); @@ -139,23 +140,35 @@ async fn identify_works() { tracing::info!("Listening on {address:?}") } SwarmEvent::Behaviour(MyBehaviourEvent::Ping(_event)) => {}, - SwarmEvent::Behaviour(MyBehaviourEvent::Identify(_event)) => { - libp2p_done = true; + SwarmEvent::Behaviour(MyBehaviourEvent::Identify(event)) => match event { + identify::Event::Received { info, .. } => { + libp2p_done = true; - if libp2p_done && litep2p_done { - break + assert_eq!(info.protocol_version, "proto v1"); + assert_eq!(info.agent_version, "litep2p/1.0.0"); + + if libp2p_done && litep2p_done { + break + } } + _ => {} } _ => {} } }, - _event = identify_event_stream.next() => { - litep2p_done = true; + event = identify_event_stream.next() => match event { + Some(IdentifyEvent::PeerIdentified { protocol_version, user_agent, .. }) => { + litep2p_done = true; + + assert_eq!(protocol_version, Some("/ipfs/1.0.0".to_string())); + assert_eq!(user_agent, Some("libp2p agent".to_string())); - if libp2p_done && litep2p_done { - break + if libp2p_done && litep2p_done { + break + } } - } + None => panic!("identify exited"), + }, _ = tokio::time::sleep(std::time::Duration::from_secs(5)) => { panic!("failed to receive identify in time"); } diff --git a/tests/protocol/identify.rs b/tests/protocol/identify.rs index 8c1826bd..07f29d15 100644 --- a/tests/protocol/identify.rs +++ b/tests/protocol/identify.rs @@ -26,9 +26,10 @@ use litep2p::{ identify::{Config, IdentifyEvent}, ping::Config as PingConfig, }, - transport::quic::config::Config as QuicConfig, - transport::tcp::config::Config as TcpConfig, - transport::websocket::config::Config as WebSocketConfig, + transport::{ + quic::config::Config as QuicConfig, tcp::config::Config as TcpConfig, + websocket::config::Config as WebSocketConfig, + }, Litep2p, Litep2pEvent, }; @@ -70,7 +71,11 @@ async fn identify_supported(transport1: Transport, transport2: Transport) { .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .try_init(); - let (identify_config1, mut identify_event_stream1) = Config::new(Vec::new()); + let (identify_config1, mut identify_event_stream1) = Config::new( + "/proto/1".to_string(), + Some("agent v1".to_string()), + Vec::new(), + ); let config_builder = ConfigBuilder::new() .with_keypair(Keypair::generate()) .with_libp2p_identify(identify_config1); @@ -82,7 +87,11 @@ async fn identify_supported(transport1: Transport, transport2: Transport) { } .build(); - let (identify_config2, mut identify_event_stream2) = Config::new(Vec::new()); + let (identify_config2, mut identify_event_stream2) = Config::new( + "/proto/2".to_string(), + Some("agent v2".to_string()), + Vec::new(), + ); let config_builder = ConfigBuilder::new() .with_keypair(Keypair::generate()) .with_libp2p_identify(identify_config2); @@ -113,9 +122,12 @@ async fn identify_supported(transport1: Transport, transport2: Transport) { _event = litep2p1.next_event() => {} _event = litep2p2.next_event() => {} event = identify_event_stream1.next() => { - let IdentifyEvent::PeerIdentified { observed_address, .. } = event.unwrap(); + let IdentifyEvent::PeerIdentified { observed_address, protocol_version, user_agent, .. } = event.unwrap(); tracing::info!("peer2 observed: {observed_address:?}"); + assert_eq!(protocol_version, Some("/proto/2".to_string())); + assert_eq!(user_agent, Some("agent v2".to_string())); + litep2p1_done = true; if litep2p1_done && litep2p2_done { @@ -123,9 +135,12 @@ async fn identify_supported(transport1: Transport, transport2: Transport) { } } event = identify_event_stream2.next() => { - let IdentifyEvent::PeerIdentified { observed_address, .. } = event.unwrap(); + let IdentifyEvent::PeerIdentified { observed_address, protocol_version, user_agent, .. } = event.unwrap(); tracing::info!("peer1 observed: {observed_address:?}"); + assert_eq!(protocol_version, Some("/proto/1".to_string())); + assert_eq!(user_agent, Some("agent v1".to_string())); + litep2p2_done = true; if litep2p1_done && litep2p2_done { @@ -198,7 +213,8 @@ async fn identify_not_supported(transport1: Transport, transport2: Transport) { .with_libp2p_ping(ping_config) .build(); - let (identify_config2, mut identify_event_stream2) = Config::new(Vec::new()); + let (identify_config2, mut identify_event_stream2) = + Config::new("litep2p".to_string(), None, Vec::new()); let config_builder = ConfigBuilder::new() .with_keypair(Keypair::generate()) .with_libp2p_identify(identify_config2); diff --git a/tests/protocol/ping.rs b/tests/protocol/ping.rs index 4ac0d864..32beb41d 100644 --- a/tests/protocol/ping.rs +++ b/tests/protocol/ping.rs @@ -20,9 +20,13 @@ use futures::StreamExt; use litep2p::{ - config::ConfigBuilder, protocol::libp2p::ping::ConfigBuilder as PingConfigBuilder, - transport::quic::config::Config as QuicConfig, transport::tcp::config::Config as TcpConfig, - transport::websocket::config::Config as WebSocketConfig, Litep2p, + config::ConfigBuilder, + protocol::libp2p::ping::ConfigBuilder as PingConfigBuilder, + transport::{ + quic::config::Config as QuicConfig, tcp::config::Config as TcpConfig, + websocket::config::Config as WebSocketConfig, + }, + Litep2p, }; enum Transport { diff --git a/tests/protocol/request_response.rs b/tests/protocol/request_response.rs index d483a460..4d15e42e 100644 --- a/tests/protocol/request_response.rs +++ b/tests/protocol/request_response.rs @@ -36,7 +36,8 @@ use litep2p::{ use futures::{channel, StreamExt}; use multiaddr::{Multiaddr, Protocol}; use multihash::Multihash; -use rand::Rng; +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng; use tokio::time::sleep; use std::{ @@ -2583,12 +2584,14 @@ async fn large_response(transport1: Transport, transport2: Transport) { } }); + // Generate the response first and use a fast insecure RNG to make the test not timeout on + // GitHub CI when generating 15 MB of data. + let mut rng = XorShiftRng::from_rng(rand::thread_rng()).expect("`thread_rng` to seed"); + let response = (0..15 * 1024 * 1024).map(|_| rng.gen::()).collect::>(); + let request_id = handle1.try_send_request(peer2, vec![1, 3, 3, 7], DialOptions::Reject).unwrap(); - let mut rng = rand::thread_rng(); - let response = (0..15 * 1024 * 1024).map(|_| rng.gen::()).collect::>(); - assert_eq!( handle2.next().await.unwrap(), RequestResponseEvent::RequestReceived { diff --git a/tests/substream.rs b/tests/substream.rs index b962344e..5682195b 100644 --- a/tests/substream.rs +++ b/tests/substream.rs @@ -23,9 +23,10 @@ use litep2p::{ config::ConfigBuilder, protocol::{Direction, TransportEvent, TransportService, UserProtocol}, substream::{Substream, SubstreamSet}, - transport::quic::config::Config as QuicConfig, - transport::tcp::config::Config as TcpConfig, - transport::websocket::config::Config as WebSocketConfig, + transport::{ + quic::config::Config as QuicConfig, tcp::config::Config as TcpConfig, + websocket::config::Config as WebSocketConfig, + }, types::{protocol::ProtocolName, SubstreamId}, Error, Litep2p, Litep2pEvent, PeerId, }; diff --git a/tests/webrtc.rs b/tests/webrtc.rs new file mode 100644 index 00000000..d80c6cb2 --- /dev/null +++ b/tests/webrtc.rs @@ -0,0 +1,80 @@ +// Copyright 2023 litep2p developers +// +// 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. + +use futures::StreamExt; +use litep2p::{ + config::ConfigBuilder as Litep2pConfigBuilder, + crypto::ed25519::Keypair, + protocol::{libp2p::ping, notification::ConfigBuilder}, + transport::webrtc::config::Config, + types::protocol::ProtocolName, + Litep2p, +}; + +#[tokio::test] +#[ignore] +async fn webrtc_test() { + let _ = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init(); + + let (ping_config, mut ping_event_stream) = ping::Config::default(); + let (notif_config, mut notif_event_stream) = ConfigBuilder::new(ProtocolName::from( + // Westend block-announces protocol name. + "/e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e/block-announces/1", + )) + .with_max_size(5 * 1024 * 1024) + .with_handshake(vec![1, 2, 3, 4]) + .with_auto_accept_inbound(true) + .build(); + + let config = Litep2pConfigBuilder::new() + .with_keypair(Keypair::generate()) + .with_webrtc(Config { + listen_addresses: vec!["/ip4/192.168.1.170/udp/8888/webrtc-direct".parse().unwrap()], + ..Default::default() + }) + .with_libp2p_ping(ping_config) + .with_notification_protocol(notif_config) + .build(); + + let mut litep2p = Litep2p::new(config).unwrap(); + let address = litep2p.listen_addresses().next().unwrap().clone(); + + tracing::info!("listen address: {address:?}"); + + loop { + tokio::select! { + event = litep2p.next_event() => { + tracing::debug!("litep2p event received: {event:?}"); + } + event = ping_event_stream.next() => { + if std::matches!(event, None) { + tracing::error!("ping event stream terminated"); + break + } + tracing::error!("ping event received: {event:?}"); + } + _event = notif_event_stream.next() => { + // tracing::error!("notification event received: {event:?}"); + } + } + } +}