libp2prs_core/upgrade.rs
1// Copyright 2018 Parity Technologies (UK) Ltd.
2// Copyright 2020 Netwarps Ltd.
3//
4// Permission is hereby granted, free of charge, to any person obtaining a
5// copy of this software and associated documentation files (the "Software"),
6// to deal in the Software without restriction, including without limitation
7// the rights to use, copy, modify, merge, publish, distribute, sublicense,
8// and/or sell copies of the Software, and to permit persons to whom the
9// Software is furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in
12// all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20// DEALINGS IN THE SOFTWARE.
21
22//! Contains everything related to upgrading a connection to use a protocol.
23//!
24//! After a connection with a remote has been successfully established. The next step
25//! is to *upgrade* this connection to use a protocol.
26//!
27//! This is where the `Upgrader` traits come into play.
28//! The trait is implemented on types that represent a collection of one or more possible
29//! protocols for respectively an ingoing or outgoing connection.
30//!
31//! > **Note**: Multiple versions of the same protocol are treated as different protocols.
32//! > For example, `/foo/1.0.0` and `/foo/1.1.0` are totally unrelated as far as
33//! > upgrading is concerned.
34//!
35//! # Upgrade process
36//!
37//! An upgrade is performed in two steps:
38//!
39//! - A protocol negotiation step. The `UpgradeInfo::protocol_info` method is called to determine
40//! which protocols are supported by the trait implementation. The `multistream-select` protocol
41//! is used in order to agree on which protocol to use amongst the ones supported.
42//!
43//! - A handshake. After a successful negotiation, the `Upgrader::upgrade_inbound` or
44//! `Upgrader::upgrade_outbound` method is called. This method will return a upgraded
45//! 'Connection'. This handshake is considered mandatory, however in practice it is
46//! possible for the trait implementation to return a dummy `Connection` that doesn't perform any
47//! action and immediately succeeds.
48//!
49//! After an upgrade is successful, an object of type `Upgrader::Output` is returned.
50//! The actual object depends on the implementation and there is no constraint on the traits that
51//! it should implement, however it is expected that it can be used by the user to control the
52//! behaviour of the protocol.
53//!
54
55use std::fmt;
56
57use async_trait::async_trait;
58
59use crate::transport::TransportError;
60
61pub use self::{dummy::DummyUpgrader, select::Selector};
62pub(crate) mod dummy;
63pub(crate) mod multistream;
64pub(crate) mod select;
65
66/// Types serving as protocol names.
67///
68/// # Context
69///
70/// In situations where we provide a list of protocols that we support,
71/// the elements of that list are required to implement the [`ProtocolName`] trait.
72///
73/// Libp2p will call [`ProtocolName::protocol_name`] on each element of that list, and transmit the
74/// returned value on the network. If the remote accepts a given protocol, the element
75/// serves as the return value of the function that performed the negotiation.
76///
77/// # Example
78///
79/// ```
80/// use libp2prs_core::upgrade::ProtocolName;
81///
82/// enum MyProtocolName {
83/// Version1,
84/// Version2,
85/// Version3,
86/// }
87///
88/// impl ProtocolName for MyProtocolName {
89/// fn protocol_name(&self) -> &[u8] {
90/// match *self {
91/// MyProtocolName::Version1 => b"/myproto/1.0",
92/// MyProtocolName::Version2 => b"/myproto/2.0",
93/// MyProtocolName::Version3 => b"/myproto/3.0",
94/// }
95/// }
96/// }
97/// ```
98///
99pub trait ProtocolName {
100 /// The protocol name as bytes. Transmitted on the network.
101 ///
102 /// **Note:** Valid protocol names must start with `/` and
103 /// not exceed 140 bytes in length.
104 fn protocol_name(&self) -> &[u8];
105}
106
107impl<T: AsRef<[u8]>> ProtocolName for T {
108 fn protocol_name(&self) -> &[u8] {
109 self.as_ref()
110 }
111}
112
113/// A general new type implementation of ProtocolName trait.
114///
115/// It is used to simplify the usage of ProtocolName. Furthermore, it
116/// provides more friendly Debug and Display, which can output a
117/// readable string instead of an array [...]
118#[derive(Clone, PartialOrd, PartialEq, Eq, Hash)]
119pub struct ProtocolId(&'static [u8]);
120
121impl fmt::Debug for ProtocolId {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "ProtocolId({})", String::from_utf8_lossy(&self.0))
124 }
125}
126
127impl fmt::Display for ProtocolId {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 write!(f, "{}", String::from_utf8_lossy(&self.0))
130 }
131}
132
133// impl ProtocolName for ProtocolId {
134// fn protocol_name(&self) -> &[u8] {
135// self.0.as_ref()
136// }
137// }
138
139impl AsRef<[u8]> for ProtocolId {
140 fn as_ref(&self) -> &[u8] {
141 self.0
142 }
143}
144
145// impl From<Bytes> for ProtocolId {
146// fn from(value: Bytes) -> Self {
147// ProtocolId(value)
148// }
149// }
150
151impl From<&'static [u8]> for ProtocolId {
152 fn from(value: &'static [u8]) -> Self {
153 ProtocolId(value)
154 }
155}
156
157pub trait UpgradeInfo: Send {
158 /// Opaque type representing a negotiable protocol.
159 type Info: ProtocolName + Clone + Send + Sync + fmt::Debug;
160
161 /// Returns the list of protocols that are supported. Used during the negotiation process.
162 fn protocol_info(&self) -> Vec<Self::Info>;
163}
164
165/// Common trait for upgrades that can be applied on a connection.
166#[async_trait]
167pub trait Upgrader<C>: UpgradeInfo + Clone {
168 /// Output after the upgrade has been successfully negotiated and the handshake performed.
169 type Output: Send;
170
171 /// After we have determined that the remote supports one of the protocols we support, this
172 /// method is called to start the handshake.
173 ///
174 /// The `info` is the identifier of the protocol, as produced by `protocol_info`.
175 async fn upgrade_inbound(self, socket: C, info: <Self as UpgradeInfo>::Info) -> Result<Self::Output, TransportError>;
176
177 /// After we have determined that the remote supports one of the protocols we support, this
178 /// method is called to start the handshake.
179 ///
180 /// The `info` is the identifier of the protocol, as produced by `protocol_info`.
181 async fn upgrade_outbound(self, socket: C, info: <Self as UpgradeInfo>::Info) -> Result<Self::Output, TransportError>;
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn upgrade_info_multi_versions() {
190 #[derive(PartialEq, Debug, Clone)]
191 enum MyProtocolName {
192 Version1,
193 Version2,
194 Version3,
195 }
196
197 impl ProtocolName for MyProtocolName {
198 fn protocol_name(&self) -> &[u8] {
199 match *self {
200 MyProtocolName::Version1 => b"/myproto/1.0",
201 MyProtocolName::Version2 => b"/myproto/2.0",
202 MyProtocolName::Version3 => b"/myproto/3.0",
203 }
204 }
205 }
206
207 struct P;
208
209 impl UpgradeInfo for P {
210 type Info = MyProtocolName;
211 fn protocol_info(&self) -> Vec<Self::Info> {
212 vec![MyProtocolName::Version1, MyProtocolName::Version2, MyProtocolName::Version3]
213 }
214 }
215
216 let p = P {};
217
218 assert_eq!(p.protocol_info().get(0).unwrap(), &MyProtocolName::Version1);
219 assert_eq!(p.protocol_info().get(1).unwrap(), &MyProtocolName::Version2);
220 assert_eq!(p.protocol_info().get(2).unwrap(), &MyProtocolName::Version3);
221 }
222
223 #[test]
224 fn protocol_id() {
225 let p = ProtocolId::from(b"protocol" as &[u8]);
226
227 assert_eq!(p.to_string(), "protocol");
228 }
229}