protocols/rendezvous: Implement protocol (#2107)

Implement the libp2p rendezvous protocol.

> A lightweight mechanism for generalized peer discovery. It can be used for
bootstrap purposes, real time peer discovery, application specific routing, and
so on.

Co-authored-by: rishflab <rishflab@hotmail.com>
Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
Thomas Eizinger
2021-09-08 00:36:52 +10:00
committed by GitHub
parent c1ae8a046c
commit adcfdc0750
32 changed files with 4463 additions and 25 deletions

View File

@ -0,0 +1,189 @@
// Copyright 2021 COMIT Network.
//
// 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::codec::{
Cookie, ErrorCode, Message, Namespace, NewRegistration, Registration, RendezvousCodec, Ttl,
};
use crate::handler::Error;
use crate::handler::PROTOCOL_IDENT;
use crate::substream_handler::{Next, PassthroughProtocol, SubstreamHandler};
use asynchronous_codec::Framed;
use futures::{SinkExt, StreamExt};
use libp2p_swarm::{NegotiatedSubstream, SubstreamProtocol};
use std::fmt;
use std::task::{Context, Poll};
/// The state of an inbound substream (i.e. the remote node opened it).
#[allow(clippy::large_enum_variant)]
pub enum Stream {
/// We are in the process of reading a message from the substream.
PendingRead(Framed<NegotiatedSubstream, RendezvousCodec>),
/// We read a message, dispatched it to the behaviour and are waiting for the response.
PendingBehaviour(Framed<NegotiatedSubstream, RendezvousCodec>),
/// We are in the process of sending a response.
PendingSend(Framed<NegotiatedSubstream, RendezvousCodec>, Message),
/// We've sent the message and are now closing down the substream.
PendingClose(Framed<NegotiatedSubstream, RendezvousCodec>),
}
impl fmt::Debug for Stream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Stream::PendingRead(_) => write!(f, "Inbound::PendingRead"),
Stream::PendingBehaviour(_) => write!(f, "Inbound::PendingBehaviour"),
Stream::PendingSend(_, _) => write!(f, "Inbound::PendingSend"),
Stream::PendingClose(_) => write!(f, "Inbound::PendingClose"),
}
}
}
#[derive(Debug, Clone)]
pub enum OutEvent {
RegistrationRequested(NewRegistration),
UnregisterRequested(Namespace),
DiscoverRequested {
namespace: Option<Namespace>,
cookie: Option<Cookie>,
limit: Option<u64>,
},
}
#[derive(Debug)]
pub enum InEvent {
RegisterResponse {
ttl: Ttl,
},
DeclineRegisterRequest(ErrorCode),
DiscoverResponse {
discovered: Vec<Registration>,
cookie: Cookie,
},
DeclineDiscoverRequest(ErrorCode),
}
impl SubstreamHandler for Stream {
type InEvent = InEvent;
type OutEvent = OutEvent;
type Error = Error;
type OpenInfo = ();
fn upgrade(
open_info: Self::OpenInfo,
) -> SubstreamProtocol<PassthroughProtocol, Self::OpenInfo> {
SubstreamProtocol::new(PassthroughProtocol::new(PROTOCOL_IDENT), open_info)
}
fn new(substream: NegotiatedSubstream, _: Self::OpenInfo) -> Self {
Stream::PendingRead(Framed::new(substream, RendezvousCodec::default()))
}
fn inject_event(self, event: Self::InEvent) -> Self {
match (event, self) {
(InEvent::RegisterResponse { ttl }, Stream::PendingBehaviour(substream)) => {
Stream::PendingSend(substream, Message::RegisterResponse(Ok(ttl)))
}
(InEvent::DeclineRegisterRequest(error), Stream::PendingBehaviour(substream)) => {
Stream::PendingSend(substream, Message::RegisterResponse(Err(error)))
}
(
InEvent::DiscoverResponse { discovered, cookie },
Stream::PendingBehaviour(substream),
) => Stream::PendingSend(
substream,
Message::DiscoverResponse(Ok((discovered, cookie))),
),
(InEvent::DeclineDiscoverRequest(error), Stream::PendingBehaviour(substream)) => {
Stream::PendingSend(substream, Message::DiscoverResponse(Err(error)))
}
(event, inbound) => {
debug_assert!(false, "{:?} cannot handle event {:?}", inbound, event);
inbound
}
}
}
fn advance(self, cx: &mut Context<'_>) -> Result<Next<Self, Self::OutEvent>, Self::Error> {
let next_state = match self {
Stream::PendingRead(mut substream) => {
match substream.poll_next_unpin(cx).map_err(Error::ReadMessage)? {
Poll::Ready(Some(msg)) => {
let event = match msg {
Message::Register(registration) => {
OutEvent::RegistrationRequested(registration)
}
Message::Discover {
cookie,
namespace,
limit,
} => OutEvent::DiscoverRequested {
cookie,
namespace,
limit,
},
Message::Unregister(namespace) => {
OutEvent::UnregisterRequested(namespace)
}
other => return Err(Error::BadMessage(other)),
};
Next::EmitEvent {
event,
next_state: Stream::PendingBehaviour(substream),
}
}
Poll::Ready(None) => return Err(Error::UnexpectedEndOfStream),
Poll::Pending => Next::Pending {
next_state: Stream::PendingRead(substream),
},
}
}
Stream::PendingBehaviour(substream) => Next::Pending {
next_state: Stream::PendingBehaviour(substream),
},
Stream::PendingSend(mut substream, message) => match substream
.poll_ready_unpin(cx)
.map_err(Error::WriteMessage)?
{
Poll::Ready(()) => {
substream
.start_send_unpin(message)
.map_err(Error::WriteMessage)?;
Next::Continue {
next_state: Stream::PendingClose(substream),
}
}
Poll::Pending => Next::Pending {
next_state: Stream::PendingSend(substream, message),
},
},
Stream::PendingClose(mut substream) => match substream.poll_close_unpin(cx) {
Poll::Ready(Ok(())) => Next::Done,
Poll::Ready(Err(_)) => Next::Done, // there is nothing we can do about an error during close
Poll::Pending => Next::Pending {
next_state: Stream::PendingClose(substream),
},
},
};
Ok(next_state)
}
}

View File

@ -0,0 +1,132 @@
// Copyright 2021 COMIT Network.
//
// 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::codec::{Cookie, Message, NewRegistration, RendezvousCodec};
use crate::handler::Error;
use crate::handler::PROTOCOL_IDENT;
use crate::substream_handler::{FutureSubstream, Next, PassthroughProtocol, SubstreamHandler};
use crate::{ErrorCode, Namespace, Registration, Ttl};
use asynchronous_codec::Framed;
use futures::{SinkExt, TryFutureExt, TryStreamExt};
use libp2p_swarm::{NegotiatedSubstream, SubstreamProtocol};
use std::task::Context;
use void::Void;
pub struct Stream(FutureSubstream<OutEvent, Error>);
impl SubstreamHandler for Stream {
type InEvent = Void;
type OutEvent = OutEvent;
type Error = Error;
type OpenInfo = OpenInfo;
fn upgrade(
open_info: Self::OpenInfo,
) -> SubstreamProtocol<PassthroughProtocol, Self::OpenInfo> {
SubstreamProtocol::new(PassthroughProtocol::new(PROTOCOL_IDENT), open_info)
}
fn new(substream: NegotiatedSubstream, info: Self::OpenInfo) -> Self {
let mut stream = Framed::new(substream, RendezvousCodec::default());
let sent_message = match info {
OpenInfo::RegisterRequest(new_registration) => Message::Register(new_registration),
OpenInfo::UnregisterRequest(namespace) => Message::Unregister(namespace),
OpenInfo::DiscoverRequest {
namespace,
cookie,
limit,
} => Message::Discover {
namespace,
cookie,
limit,
},
};
Self(FutureSubstream::new(async move {
use Message::*;
use OutEvent::*;
stream
.send(sent_message.clone())
.map_err(Error::WriteMessage)
.await?;
let received_message = stream.try_next().map_err(Error::ReadMessage).await?;
let received_message = received_message.ok_or(Error::UnexpectedEndOfStream)?;
let event = match (sent_message, received_message) {
(Register(registration), RegisterResponse(Ok(ttl))) => Registered {
namespace: registration.namespace,
ttl,
},
(Register(registration), RegisterResponse(Err(error))) => {
RegisterFailed(registration.namespace, error)
}
(Discover { .. }, DiscoverResponse(Ok((registrations, cookie)))) => Discovered {
registrations,
cookie,
},
(Discover { namespace, .. }, DiscoverResponse(Err(error))) => {
DiscoverFailed { namespace, error }
}
(.., other) => return Err(Error::BadMessage(other)),
};
stream.close().map_err(Error::WriteMessage).await?;
Ok(event)
}))
}
fn inject_event(self, event: Self::InEvent) -> Self {
void::unreachable(event)
}
fn advance(self, cx: &mut Context<'_>) -> Result<Next<Self, Self::OutEvent>, Self::Error> {
Ok(self.0.advance(cx)?.map_state(Stream))
}
}
#[derive(Debug, Clone)]
pub enum OutEvent {
Registered {
namespace: Namespace,
ttl: Ttl,
},
RegisterFailed(Namespace, ErrorCode),
Discovered {
registrations: Vec<Registration>,
cookie: Cookie,
},
DiscoverFailed {
namespace: Option<Namespace>,
error: ErrorCode,
},
}
#[derive(Debug)]
pub enum OpenInfo {
RegisterRequest(NewRegistration),
UnregisterRequest(Namespace),
DiscoverRequest {
namespace: Option<Namespace>,
cookie: Option<Cookie>,
limit: Option<Ttl>,
},
}