smoldot_light/
platform.rs

1// Smoldot
2// Copyright (C) 2019-2022  Parity Technologies (UK) Ltd.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18use alloc::borrow::Cow;
19use core::{fmt, net::IpAddr, ops, panic::UnwindSafe, pin::Pin, str, time::Duration};
20
21pub use smoldot::libp2p::read_write;
22
23#[cfg(feature = "std")]
24pub use smoldot::libp2p::with_buffers;
25
26// TODO: this module should probably not be public?
27pub mod address_parse;
28pub mod default;
29
30mod with_prefix;
31
32#[cfg(feature = "std")]
33pub use default::DefaultPlatform;
34
35pub use with_prefix::WithPrefix;
36
37/// Access to a platform's capabilities.
38///
39/// Implementations of this trait are expected to be cheaply-clonable "handles". All clones of the
40/// same platform share the same objects. For instance, it is legal to create clone a platform,
41/// then create a connection on one clone, then access this connection on the other clone.
42pub trait PlatformRef: UnwindSafe + Clone + Send + Sync + 'static {
43    /// `Future` that resolves once a certain amount of time has passed or a certain point in time
44    /// is reached. See [`PlatformRef::sleep`] and [`PlatformRef::sleep_until`].
45    type Delay: Future<Output = ()> + Send + 'static;
46
47    /// A certain point in time. Typically `std::time::Instant`, but one can also
48    /// use `core::time::Duration`.
49    ///
50    /// The implementations of the `Add` and `Sub` traits must panic in case of overflow or
51    /// underflow, similar to the ones of `std::time::Instant` and `core::time::Duration`.
52    type Instant: Clone
53        + ops::Add<Duration, Output = Self::Instant>
54        + ops::Sub<Self::Instant, Output = Duration>
55        + PartialOrd
56        + Ord
57        + PartialEq
58        + Eq
59        + Send
60        + Sync
61        + 'static;
62
63    /// A multi-stream connection.
64    ///
65    /// This object is merely a handle. The underlying connection should be dropped only after
66    /// the `MultiStream` and all its associated substream objects ([`PlatformRef::Stream`]) have
67    /// been dropped.
68    type MultiStream: Send + Sync + 'static;
69
70    /// Opaque object representing either a single-stream connection or a substream in a
71    /// multi-stream connection.
72    ///
73    /// If this object is abruptly dropped, the underlying single stream connection or substream
74    /// should be abruptly dropped (i.e. RST) as well, unless its reading and writing sides
75    /// have been gracefully closed in the past.
76    type Stream: Send + 'static;
77
78    /// Object that dereferences to [`read_write::ReadWrite`] and gives access to the stream's
79    /// buffers. See the [`read_write`] module for more information.
80    /// See also [`PlatformRef::read_write_access`].
81    type ReadWriteAccess<'a>: ops::DerefMut<Target = read_write::ReadWrite<Self::Instant>> + 'a;
82
83    /// Reference to an error that happened on a stream.
84    ///
85    /// Potentially returned by [`PlatformRef::read_write_access`].
86    ///
87    /// Typically `&'a std::io::Error`.
88    type StreamErrorRef<'a>: fmt::Display + fmt::Debug;
89
90    /// `Future` returned by [`PlatformRef::connect_stream`].
91    type StreamConnectFuture: Future<Output = Self::Stream> + Send + 'static;
92    /// `Future` returned by [`PlatformRef::connect_multistream`].
93    type MultiStreamConnectFuture: Future<Output = MultiStreamWebRtcConnection<Self::MultiStream>>
94        + Send
95        + 'static;
96    /// `Future` returned by [`PlatformRef::wait_read_write_again`].
97    type StreamUpdateFuture<'a>: Future<Output = ()> + Send + 'a;
98    /// `Future` returned by [`PlatformRef::next_substream`].
99    type NextSubstreamFuture<'a>: Future<Output = Option<(Self::Stream, SubstreamDirection)>>
100        + Send
101        + 'a;
102
103    /// Returns the time elapsed since [the Unix Epoch](https://en.wikipedia.org/wiki/Unix_time)
104    /// (i.e. 00:00:00 UTC on 1 January 1970), ignoring leap seconds.
105    ///
106    /// # Panic
107    ///
108    /// Panics if the system time is configured to be below the UNIX epoch. This situation is a
109    /// very very niche edge case that isn't worth handling.
110    ///
111    fn now_from_unix_epoch(&self) -> Duration;
112
113    /// Returns an object that represents "now".
114    fn now(&self) -> Self::Instant;
115
116    /// The given buffer must be completely filled with pseudo-random bytes.
117    ///
118    /// # Panic
119    ///
120    /// Must panic if for some reason it is not possible to fill the buffer.
121    ///
122    fn fill_random_bytes(&self, buffer: &mut [u8]);
123
124    /// Creates a future that becomes ready after at least the given duration has elapsed.
125    fn sleep(&self, duration: Duration) -> Self::Delay;
126
127    /// Creates a future that becomes ready after the given instant has been reached.
128    fn sleep_until(&self, when: Self::Instant) -> Self::Delay;
129
130    /// Spawns a task that runs indefinitely in the background.
131    ///
132    /// The first parameter is the name of the task, which can be useful for debugging purposes.
133    ///
134    /// The `Future` must be run until it yields a value.
135    ///
136    /// Implementers should be aware of the fact that polling the `Future` might panic (never
137    /// intentionally, but in case of a bug). Many tasks can be restarted if they panic, and
138    /// implementers are encouraged to absorb the panics that happen when polling. If a panic
139    /// happens, the `Future` that has panicked must never be polled again.
140    ///
141    /// > **Note**: Ideally, the `task` parameter would require the `UnwindSafe` trait.
142    /// >           Unfortunately, at the time of writing of this comment, it is extremely
143    /// >           difficult if not impossible to implement this trait on `Future`s. It is for
144    /// >           the same reason that the `std::thread::spawn` function of the standard library
145    /// >           doesn't require its parameter to implement `UnwindSafe`.
146    fn spawn_task(&self, task_name: Cow<str>, task: impl Future<Output = ()> + Send + 'static);
147
148    /// Emit a log line.
149    ///
150    /// The `log_level` and `log_target` can be used in order to filter desired log lines.
151    ///
152    /// The `message` is typically a short constant message indicating the nature of the log line.
153    /// The `key_values` are a list of keys and values that are the parameters of the log message.
154    ///
155    /// For example, `message` can be `"block-announce-received"` and `key_values` can contain
156    /// the entries `("block_hash", ...)` and `("sender", ...)`.
157    ///
158    /// At the time of writing of this comment, `message` can also be a message written in plain
159    /// english and destined to the user of the library. This might disappear in the future.
160    // TODO: act on this last sentence ^
161    fn log<'a>(
162        &self,
163        log_level: LogLevel,
164        log_target: &'a str,
165        message: &'a str,
166        key_values: impl Iterator<Item = (&'a str, &'a dyn fmt::Display)>,
167    );
168
169    /// Value returned when a JSON-RPC client requests the name of the client, or when a peer
170    /// performs an identification request. Reasonable value is `env!("CARGO_PKG_NAME")`.
171    fn client_name(&self) -> Cow<str>;
172
173    /// Value returned when a JSON-RPC client requests the version of the client, or when a peer
174    /// performs an identification request. Reasonable value is `env!("CARGO_PKG_VERSION")`.
175    fn client_version(&self) -> Cow<str>;
176
177    /// Returns `true` if [`PlatformRef::connect_stream`] or [`PlatformRef::connect_multistream`]
178    /// accepts a connection of the corresponding type.
179    ///
180    /// > **Note**: This function is meant to be pure. Implementations are expected to always
181    /// >           return the same value for the same [`ConnectionType`] input. Enabling or
182    /// >           disabling certain connection types after start-up is not supported.
183    fn supports_connection_type(&self, connection_type: ConnectionType) -> bool;
184
185    /// Starts a connection attempt to the given address.
186    ///
187    /// This function is only ever called with an `address` of a type for which
188    /// [`PlatformRef::supports_connection_type`] returned `true` in the past.
189    ///
190    /// This function returns a `Future`. This `Future` **must** return as soon as possible, and
191    /// must **not** wait for the connection to be established.
192    /// In most scenarios, the `Future` returned by this function should immediately produce
193    /// an output.
194    ///
195    /// # Panic
196    ///
197    /// The function implementation panics if [`Address`] is of a type for which
198    /// [`PlatformRef::supports_connection_type`] returns `false`.
199    ///
200    fn connect_stream(&self, address: Address) -> Self::StreamConnectFuture;
201
202    /// Starts a connection attempt to the given address.
203    ///
204    /// This function is only ever called with an `address` of a type for which
205    /// [`PlatformRef::supports_connection_type`] returned `true` in the past.
206    ///
207    /// > **Note**: A so-called "multistream connection" is a connection made of multiple
208    /// >           substreams, and for which the opening and closing or substreams is handled by
209    /// >           the environment rather than by smoldot itself. Most platforms do not need to
210    /// >           support multistream connections. This function is in practice used in order
211    /// >           to support WebRTC connections when embedding smoldot-light within a web
212    /// >           browser.
213    ///
214    /// This function returns a `Future`. This `Future` **must** return as soon as possible, and
215    /// must **not** wait for the connection to be established.
216    /// In most scenarios, the `Future` returned by this function should immediately produce
217    /// an output.
218    ///
219    /// # Panic
220    ///
221    /// The function implementation panics if [`MultiStreamAddress`] is of a type for which
222    /// [`PlatformRef::supports_connection_type`] returns `false`.
223    ///
224    fn connect_multistream(&self, address: MultiStreamAddress) -> Self::MultiStreamConnectFuture;
225
226    /// Queues the opening of an additional outbound substream.
227    ///
228    /// The substream, once opened, must be yielded by [`PlatformRef::next_substream`].
229    ///
230    /// Calls to this function should be ignored if the connection has already been killed by the
231    /// remote.
232    ///
233    /// > **Note**: No mechanism exists in this API to handle the situation where a substream fails
234    /// >           to open, as this is not supposed to happen. If you need to handle such a
235    /// >           situation, either try again opening a substream again or reset the entire
236    /// >           connection.
237    fn open_out_substream(&self, connection: &mut Self::MultiStream);
238
239    /// Waits until a new incoming substream arrives on the connection.
240    ///
241    /// This returns both inbound and outbound substreams. Outbound substreams should only be
242    /// yielded once for every call to [`PlatformRef::open_out_substream`].
243    ///
244    /// The future can also return `None` if the connection has been killed by the remote. If
245    /// the future returns `None`, the user of the `PlatformRef` should drop the `MultiStream` and
246    /// all its associated `Stream`s as soon as possible.
247    fn next_substream<'a>(
248        &self,
249        connection: &'a mut Self::MultiStream,
250    ) -> Self::NextSubstreamFuture<'a>;
251
252    /// Returns an object that implements `DerefMut<Target = ReadWrite>`. The
253    /// [`read_write::ReadWrite`] object allows the API user to read data from the stream and write
254    /// data to the stream.
255    ///
256    /// If the stream has been reset in the past, this function should return a reference to
257    /// the error that happened.
258    ///
259    /// See the documentation of [`read_write`] for more information
260    ///
261    /// > **Note**: The `with_buffers` module provides a helper to more easily implement this
262    /// >           function.
263    fn read_write_access<'a>(
264        &self,
265        stream: Pin<&'a mut Self::Stream>,
266    ) -> Result<Self::ReadWriteAccess<'a>, Self::StreamErrorRef<'a>>;
267
268    /// Returns a future that becomes ready when [`PlatformRef::read_write_access`] should be
269    /// called again on this stream.
270    ///
271    /// Must always returned immediately if [`PlatformRef::read_write_access`] has never been
272    /// called on this stream.
273    ///
274    /// See the documentation of [`read_write`] for more information.
275    ///
276    /// > **Note**: The `with_buffers` module provides a helper to more easily implement this
277    /// >           function.
278    fn wait_read_write_again<'a>(
279        &self,
280        stream: Pin<&'a mut Self::Stream>,
281    ) -> Self::StreamUpdateFuture<'a>;
282}
283
284/// Log level of a log entry.
285///
286/// See [`PlatformRef::log`].
287#[derive(Debug)]
288pub enum LogLevel {
289    Error = 1,
290    Warn = 2,
291    Info = 3,
292    Debug = 4,
293    Trace = 5,
294}
295
296/// Established multistream connection information. See [`PlatformRef::connect_multistream`].
297#[derive(Debug)]
298pub struct MultiStreamWebRtcConnection<TConnection> {
299    /// Object representing the WebRTC connection.
300    pub connection: TConnection,
301    /// SHA256 hash of the TLS certificate used by the local node at the DTLS layer.
302    pub local_tls_certificate_sha256: [u8; 32],
303}
304
305/// Direction in which a substream has been opened. See [`PlatformRef::next_substream`].
306#[derive(Debug)]
307pub enum SubstreamDirection {
308    /// Substream has been opened by the remote.
309    Inbound,
310    /// Substream has been opened locally in response to [`PlatformRef::open_out_substream`].
311    Outbound,
312}
313
314/// Connection type passed to [`PlatformRef::supports_connection_type`].
315#[derive(Debug, Copy, Clone, PartialEq, Eq)]
316pub enum ConnectionType {
317    /// TCP/IP connection.
318    TcpIpv4,
319    /// TCP/IP connection.
320    TcpIpv6,
321    /// TCP/IP connection.
322    TcpDns,
323    /// Non-secure WebSocket connection.
324    WebSocketIpv4 {
325        /// `true` if the target of the connection is `localhost`.
326        ///
327        /// > **Note**: Some platforms (namely browsers) sometimes only accept non-secure WebSocket
328        /// >           connections only towards `localhost`.
329        remote_is_localhost: bool,
330    },
331    /// Non-secure WebSocket connection.
332    WebSocketIpv6 {
333        /// `true` if the target of the connection is `localhost`.
334        ///
335        /// > **Note**: Some platforms (namely browsers) sometimes only accept non-secure WebSocket
336        /// >           connections only towards `localhost`.
337        remote_is_localhost: bool,
338    },
339    /// WebSocket connection.
340    WebSocketDns {
341        /// `true` for WebSocket secure connections.
342        secure: bool,
343        /// `true` if the target of the connection is `localhost`.
344        ///
345        /// > **Note**: Some platforms (namely browsers) sometimes only accept non-secure WebSocket
346        /// >           connections only towards `localhost`.
347        remote_is_localhost: bool,
348    },
349    /// Libp2p-specific WebRTC flavour.
350    WebRtcIpv4,
351    /// Libp2p-specific WebRTC flavour.
352    WebRtcIpv6,
353}
354
355impl<'a> From<&'a Address<'a>> for ConnectionType {
356    fn from(address: &'a Address<'a>) -> ConnectionType {
357        match address {
358            Address::TcpIp {
359                ip: IpAddr::V4(_), ..
360            } => ConnectionType::TcpIpv4,
361            Address::TcpIp {
362                ip: IpAddr::V6(_), ..
363            } => ConnectionType::TcpIpv6,
364            Address::TcpDns { .. } => ConnectionType::TcpDns,
365            Address::WebSocketIp {
366                ip: IpAddr::V4(ip), ..
367            } => ConnectionType::WebSocketIpv4 {
368                remote_is_localhost: ip.is_loopback(),
369            },
370            Address::WebSocketIp {
371                ip: IpAddr::V6(ip), ..
372            } => ConnectionType::WebSocketIpv6 {
373                remote_is_localhost: ip.is_loopback(),
374            },
375            Address::WebSocketDns {
376                hostname, secure, ..
377            } => ConnectionType::WebSocketDns {
378                secure: *secure,
379                remote_is_localhost: hostname.eq_ignore_ascii_case("localhost"),
380            },
381        }
382    }
383}
384
385impl<'a> From<&'a MultiStreamAddress<'a>> for ConnectionType {
386    fn from(address: &'a MultiStreamAddress) -> ConnectionType {
387        match address {
388            MultiStreamAddress::WebRtc {
389                ip: IpAddr::V4(_), ..
390            } => ConnectionType::WebRtcIpv4,
391            MultiStreamAddress::WebRtc {
392                ip: IpAddr::V6(_), ..
393            } => ConnectionType::WebRtcIpv6,
394        }
395    }
396}
397
398/// Address passed to [`PlatformRef::connect_stream`].
399// TODO: we don't differentiate between Dns4 and Dns6
400#[derive(Debug, Copy, Clone, PartialEq, Eq)]
401pub enum Address<'a> {
402    /// TCP/IP connection with a domain name.
403    TcpDns {
404        /// DNS hostname to connect to.
405        ///
406        /// > **Note**: According to RFC2181 section 11, a domain name is not necessarily an UTF-8
407        /// >           string. Any binary data can be used as a domain name, provided it follows
408        /// >           a few restrictions (notably its length). However, in the context of the
409        /// >           [`PlatformRef`] trait, we automatically consider as non-supported a
410        /// >           multiaddress that contains a non-UTF-8 domain name, for the sake of
411        /// >           simplicity.
412        hostname: &'a str,
413        /// TCP port to connect to.
414        port: u16,
415    },
416
417    /// TCP/IP connection with an IP address.
418    TcpIp {
419        /// IP address to connect to.
420        ip: IpAddr,
421        /// TCP port to connect to.
422        port: u16,
423    },
424
425    /// WebSocket connection with an IP address.
426    WebSocketIp {
427        /// IP address to connect to.
428        ip: IpAddr,
429        /// TCP port to connect to.
430        port: u16,
431    },
432
433    /// WebSocket connection with a domain name.
434    WebSocketDns {
435        /// DNS hostname to connect to.
436        ///
437        /// > **Note**: According to RFC2181 section 11, a domain name is not necessarily an UTF-8
438        /// >           string. Any binary data can be used as a domain name, provided it follows
439        /// >           a few restrictions (notably its length). However, in the context of the
440        /// >           [`PlatformRef`] trait, we automatically consider as non-supported a
441        /// >           multiaddress that contains a non-UTF-8 domain name, for the sake of
442        /// >           simplicity.
443        hostname: &'a str,
444        /// TCP port to connect to.
445        port: u16,
446        /// `true` for WebSocket secure connections.
447        secure: bool,
448    },
449}
450
451/// Address passed to [`PlatformRef::connect_multistream`].
452// TODO: we don't differentiate between Dns4 and Dns6
453#[derive(Debug, Copy, Clone, PartialEq, Eq)]
454pub enum MultiStreamAddress<'a> {
455    /// Libp2p-specific WebRTC flavour.
456    ///
457    /// The implementation the [`PlatformRef`] trait is responsible for opening the SCTP
458    /// connection. The API user of the [`PlatformRef`] trait is responsible for opening the first
459    /// data channel and performing the Noise handshake.
460    // TODO: maybe explain more what the implementation is supposed to do?
461    WebRtc {
462        /// IP address to connect to.
463        ip: IpAddr,
464        /// UDP port to connect to.
465        port: u16,
466        /// SHA-256 hash of the target's WebRTC certificate.
467        remote_certificate_sha256: &'a [u8; 32],
468    },
469}
470
471// TODO: find a way to keep this private somehow?
472#[macro_export]
473macro_rules! log_inner {
474    () => {
475        core::iter::empty()
476    };
477    (,) => {
478        core::iter::empty()
479    };
480    ($key:ident = $value:expr) => {
481        $crate::log_inner!($key = $value,)
482    };
483    ($key:ident = ?$value:expr) => {
484        $crate::log_inner!($key = ?$value,)
485    };
486    ($key:ident) => {
487        $crate::log_inner!($key = $key,)
488    };
489    (?$key:ident) => {
490        $crate::log_inner!($key = ?$key,)
491    };
492    ($key:ident = $value:expr, $($rest:tt)*) => {
493        core::iter::once((stringify!($key), &$value as &dyn core::fmt::Display)).chain($crate::log_inner!($($rest)*))
494    };
495    ($key:ident = ?$value:expr, $($rest:tt)*) => {
496        core::iter::once((stringify!($key), &format_args!("{:?}", $value) as &dyn core::fmt::Display)).chain($crate::log_inner!($($rest)*))
497    };
498    ($key:ident, $($rest:tt)*) => {
499        $crate::log_inner!($key = $key, $($rest)*)
500    };
501    (?$key:ident, $($rest:tt)*) => {
502        $crate::log_inner!($key = ?$key, $($rest)*)
503    };
504}
505
506/// Helper macro for using the [`crate::platform::PlatformRef::log`] function.
507///
508/// This macro takes at least 4 parameters:
509///
510/// - A reference to an implementation of the [`crate::platform::PlatformRef`] trait.
511/// - A log level: `Error`, `Warn`, `Info`, `Debug`, or `Trace`. See [`crate::platform::LogLevel`].
512/// - A `&str` representing the target of the logging. This can be used in order to filter log
513/// entries belonging to a specific target.
514/// - An object that implements of `AsRef<str>` and that contains the log message itself.
515///
516/// In addition to these parameters, the macro accepts an unlimited number of extra
517/// (comma-separated) parameters.
518///
519/// Each parameter has one of these four syntaxes:
520///
521/// - `key = value`, where `key` is an identifier and `value` an expression that implements the
522/// `Display` trait.
523/// - `key = ?value`, where `key` is an identifier and `value` an expression that implements
524/// the `Debug` trait.
525/// - `key`, which is syntax sugar for `key = key`.
526/// - `?key`, which is syntax sugar for `key = ?key`.
527///
528#[macro_export]
529macro_rules! log {
530    ($plat:expr, $level:ident, $target:expr, $message:expr) => {
531        log!($plat, $level, $target, $message,)
532    };
533    ($plat:expr, $level:ident, $target:expr, $message:expr, $($params:tt)*) => {
534        $crate::platform::PlatformRef::log(
535            $plat,
536            $crate::platform::LogLevel::$level,
537            $target,
538            AsRef::<str>::as_ref(&$message),
539            $crate::log_inner!($($params)*)
540        )
541    };
542}