Toralf Wittner 30de0b4d64
Add Throttled to libp2p-request-response. (#1696)
* Use a single exchange instead of two one_shots.

* Add `Throttled` to libp2p-request-response.

Wraps the existing `RequestResponse` behaviour and applies strict limits
to the number of inbound and outbound requests per peer.

The wrapper is opt-in and if not used, the protocol behaviour of
`RequestResponse` does not change. This PR also does not introduce
an extra protocol, hence the limits applied need to be known a priori
for all nodes which is not always possible or desirable. As mentioned
in #1687 I think that we should eventually augment the protocol with
metadata which allows a more dynamic exchange of requests and responses.

This PR also replaces the two oneshot channels with a single one from the
scambio crate which saves one allocation per request/response. If not
desirable because the crate has seen less testing the first commit could
be reverted.

* Fix rustdoc error.

* Remove some leftovers from development.

* Add docs to `NetworBehaviourAction::{map_in,map_out}`.

* Apply suggestions from code review

Co-authored-by: Roman Borschel <romanb@users.noreply.github.com>

* Add `ping_protocol_throttled` test.

* Add another test.

* Revert "Use a single exchange instead of two one_shots."

This reverts commit e34e1297d411298f6c69e238aa6c96e0b795d989.

# Conflicts:
#	protocols/request-response/Cargo.toml
#	protocols/request-response/src/handler/protocol.rs

* Update CHANGELOG.

* Update CHANGELOG.

Co-authored-by: Roman Borschel <romanb@users.noreply.github.com>
2020-08-12 16:04:54 +02:00

159 lines
5.3 KiB
Rust

// Copyright 2020 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//! The definition of a request/response protocol via inbound
//! and outbound substream upgrades. The inbound upgrade
//! receives a request and sends a response, whereas the
//! outbound upgrade send a request and receives a response.
use crate::RequestId;
use crate::codec::RequestResponseCodec;
use futures::{channel::oneshot, future::BoxFuture, prelude::*};
use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo};
use libp2p_swarm::NegotiatedSubstream;
use smallvec::SmallVec;
use std::io;
/// The level of support for a particular protocol.
#[derive(Debug, Clone)]
pub enum ProtocolSupport {
/// The protocol is only supported for inbound requests.
Inbound,
/// The protocol is only supported for outbound requests.
Outbound,
/// The protocol is supported for inbound and outbound requests.
Full
}
impl ProtocolSupport {
/// Whether inbound requests are supported.
pub fn inbound(&self) -> bool {
match self {
ProtocolSupport::Inbound | ProtocolSupport::Full => true,
ProtocolSupport::Outbound => false,
}
}
/// Whether outbound requests are supported.
pub fn outbound(&self) -> bool {
match self {
ProtocolSupport::Outbound | ProtocolSupport::Full => true,
ProtocolSupport::Inbound => false,
}
}
}
/// Response substream upgrade protocol.
///
/// Receives a request and sends a response.
#[derive(Debug)]
pub struct ResponseProtocol<TCodec>
where
TCodec: RequestResponseCodec
{
pub(crate) codec: TCodec,
pub(crate) protocols: SmallVec<[TCodec::Protocol; 2]>,
pub(crate) request_sender: oneshot::Sender<TCodec::Request>,
pub(crate) response_receiver: oneshot::Receiver<TCodec::Response>
}
impl<TCodec> UpgradeInfo for ResponseProtocol<TCodec>
where
TCodec: RequestResponseCodec
{
type Info = TCodec::Protocol;
type InfoIter = smallvec::IntoIter<[Self::Info; 2]>;
fn protocol_info(&self) -> Self::InfoIter {
self.protocols.clone().into_iter()
}
}
impl<TCodec> InboundUpgrade<NegotiatedSubstream> for ResponseProtocol<TCodec>
where
TCodec: RequestResponseCodec + Send + 'static,
{
type Output = ();
type Error = io::Error;
type Future = BoxFuture<'static, Result<Self::Output, Self::Error>>;
fn upgrade_inbound(mut self, mut io: NegotiatedSubstream, protocol: Self::Info) -> Self::Future {
async move {
let read = self.codec.read_request(&protocol, &mut io);
let request = read.await?;
if let Ok(()) = self.request_sender.send(request) {
if let Ok(response) = self.response_receiver.await {
let write = self.codec.write_response(&protocol, &mut io, response);
write.await?;
}
}
io.close().await?;
Ok(())
}.boxed()
}
}
/// Request substream upgrade protocol.
///
/// Sends a request and receives a response.
#[derive(Debug, Clone)]
pub struct RequestProtocol<TCodec>
where
TCodec: RequestResponseCodec
{
pub(crate) codec: TCodec,
pub(crate) protocols: SmallVec<[TCodec::Protocol; 2]>,
pub(crate) request_id: RequestId,
pub(crate) request: TCodec::Request,
}
impl<TCodec> UpgradeInfo for RequestProtocol<TCodec>
where
TCodec: RequestResponseCodec
{
type Info = TCodec::Protocol;
type InfoIter = smallvec::IntoIter<[Self::Info; 2]>;
fn protocol_info(&self) -> Self::InfoIter {
self.protocols.clone().into_iter()
}
}
impl<TCodec> OutboundUpgrade<NegotiatedSubstream> for RequestProtocol<TCodec>
where
TCodec: RequestResponseCodec + Send + 'static,
{
type Output = TCodec::Response;
type Error = io::Error;
type Future = BoxFuture<'static, Result<Self::Output, Self::Error>>;
fn upgrade_outbound(mut self, mut io: NegotiatedSubstream, protocol: Self::Info) -> Self::Future {
async move {
let write = self.codec.write_request(&protocol, &mut io, self.request);
write.await?;
io.close().await?;
let read = self.codec.read_response(&protocol, &mut io);
let response = read.await?;
Ok(response)
}.boxed()
}
}