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 /// Libp2p-specific WebRTC flavour, with a domain name.
354 WebRtcDns,
355}
356
357impl<'a> From<&'a Address<'a>> for ConnectionType {
358 fn from(address: &'a Address<'a>) -> ConnectionType {
359 match address {
360 Address::TcpIp {
361 ip: IpAddr::V4(_), ..
362 } => ConnectionType::TcpIpv4,
363 Address::TcpIp {
364 ip: IpAddr::V6(_), ..
365 } => ConnectionType::TcpIpv6,
366 Address::TcpDns { .. } => ConnectionType::TcpDns,
367 Address::WebSocketIp {
368 ip: IpAddr::V4(ip), ..
369 } => ConnectionType::WebSocketIpv4 {
370 remote_is_localhost: ip.is_loopback(),
371 },
372 Address::WebSocketIp {
373 ip: IpAddr::V6(ip), ..
374 } => ConnectionType::WebSocketIpv6 {
375 remote_is_localhost: ip.is_loopback(),
376 },
377 Address::WebSocketDns {
378 hostname, secure, ..
379 } => ConnectionType::WebSocketDns {
380 secure: *secure,
381 remote_is_localhost: hostname.eq_ignore_ascii_case("localhost"),
382 },
383 }
384 }
385}
386
387impl<'a> From<&'a MultiStreamAddress<'a>> for ConnectionType {
388 fn from(address: &'a MultiStreamAddress) -> ConnectionType {
389 match address {
390 MultiStreamAddress::WebRtc {
391 ip: IpAddr::V4(_), ..
392 } => ConnectionType::WebRtcIpv4,
393 MultiStreamAddress::WebRtc {
394 ip: IpAddr::V6(_), ..
395 } => ConnectionType::WebRtcIpv6,
396 MultiStreamAddress::WebRtcDns { .. } => ConnectionType::WebRtcDns,
397 }
398 }
399}
400
401/// Address passed to [`PlatformRef::connect_stream`].
402// TODO: we don't differentiate between Dns4 and Dns6
403#[derive(Debug, Copy, Clone, PartialEq, Eq)]
404pub enum Address<'a> {
405 /// TCP/IP connection with a domain name.
406 TcpDns {
407 /// DNS hostname to connect to.
408 ///
409 /// > **Note**: According to RFC2181 section 11, a domain name is not necessarily an UTF-8
410 /// > string. Any binary data can be used as a domain name, provided it follows
411 /// > a few restrictions (notably its length). However, in the context of the
412 /// > [`PlatformRef`] trait, we automatically consider as non-supported a
413 /// > multiaddress that contains a non-UTF-8 domain name, for the sake of
414 /// > simplicity.
415 hostname: &'a str,
416 /// TCP port to connect to.
417 port: u16,
418 },
419
420 /// TCP/IP connection with an IP address.
421 TcpIp {
422 /// IP address to connect to.
423 ip: IpAddr,
424 /// TCP port to connect to.
425 port: u16,
426 },
427
428 /// WebSocket connection with an IP address.
429 WebSocketIp {
430 /// IP address to connect to.
431 ip: IpAddr,
432 /// TCP port to connect to.
433 port: u16,
434 },
435
436 /// WebSocket connection with a domain name.
437 WebSocketDns {
438 /// DNS hostname to connect to.
439 ///
440 /// > **Note**: According to RFC2181 section 11, a domain name is not necessarily an UTF-8
441 /// > string. Any binary data can be used as a domain name, provided it follows
442 /// > a few restrictions (notably its length). However, in the context of the
443 /// > [`PlatformRef`] trait, we automatically consider as non-supported a
444 /// > multiaddress that contains a non-UTF-8 domain name, for the sake of
445 /// > simplicity.
446 hostname: &'a str,
447 /// TCP port to connect to.
448 port: u16,
449 /// `true` for WebSocket secure connections.
450 secure: bool,
451 },
452}
453
454/// Address family that a domain name found in a multiaddress is expected to resolve to.
455#[derive(Debug, Copy, Clone, PartialEq, Eq)]
456pub enum DnsFamily {
457 /// Name resolve to either IPv4 or IPv6.
458 Any,
459 /// Name resolve to IPv4.
460 Ipv4,
461 /// Name resolve to IPv6.
462 Ipv6,
463}
464
465/// Address passed to [`PlatformRef::connect_multistream`].
466#[derive(Debug, Copy, Clone, PartialEq, Eq)]
467pub enum MultiStreamAddress<'a> {
468 /// Libp2p-specific WebRTC flavour.
469 ///
470 /// The implementation the [`PlatformRef`] trait is responsible for opening the SCTP
471 /// connection. The API user of the [`PlatformRef`] trait is responsible for opening the first
472 /// data channel and performing the Noise handshake.
473 // TODO: maybe explain more what the implementation is supposed to do?
474 WebRtc {
475 /// IP address to connect to.
476 ip: IpAddr,
477 /// UDP port to connect to.
478 port: u16,
479 /// SHA-256 hash of the target's WebRTC certificate.
480 remote_certificate_sha256: &'a [u8; 32],
481 },
482
483 /// Same as [`MultiStreamAddress::WebRtc`], but with a domain name instead of an IP address.
484 WebRtcDns {
485 /// DNS hostname to connect to.
486 hostname: &'a str,
487 /// Address family the hostname must resolve to.
488 family: DnsFamily,
489 /// UDP port to connect to.
490 port: u16,
491 /// SHA-256 hash of the target's WebRTC certificate.
492 remote_certificate_sha256: &'a [u8; 32],
493 },
494}
495
496// TODO: find a way to keep this private somehow?
497#[macro_export]
498macro_rules! log_inner {
499 () => {
500 core::iter::empty()
501 };
502 (,) => {
503 core::iter::empty()
504 };
505 ($key:ident = $value:expr) => {
506 $crate::log_inner!($key = $value,)
507 };
508 ($key:ident = ?$value:expr) => {
509 $crate::log_inner!($key = ?$value,)
510 };
511 ($key:ident) => {
512 $crate::log_inner!($key = $key,)
513 };
514 (?$key:ident) => {
515 $crate::log_inner!($key = ?$key,)
516 };
517 ($key:ident = $value:expr, $($rest:tt)*) => {
518 core::iter::once((stringify!($key), &$value as &dyn core::fmt::Display)).chain($crate::log_inner!($($rest)*))
519 };
520 ($key:ident = ?$value:expr, $($rest:tt)*) => {
521 core::iter::once((stringify!($key), &format_args!("{:?}", $value) as &dyn core::fmt::Display)).chain($crate::log_inner!($($rest)*))
522 };
523 ($key:ident, $($rest:tt)*) => {
524 $crate::log_inner!($key = $key, $($rest)*)
525 };
526 (?$key:ident, $($rest:tt)*) => {
527 $crate::log_inner!($key = ?$key, $($rest)*)
528 };
529}
530
531/// Helper macro for using the [`crate::platform::PlatformRef::log`] function.
532///
533/// This macro takes at least 4 parameters:
534///
535/// - A reference to an implementation of the [`crate::platform::PlatformRef`] trait.
536/// - A log level: `Error`, `Warn`, `Info`, `Debug`, or `Trace`. See [`crate::platform::LogLevel`].
537/// - A `&str` representing the target of the logging. This can be used in order to filter log
538/// entries belonging to a specific target.
539/// - An object that implements of `AsRef<str>` and that contains the log message itself.
540///
541/// In addition to these parameters, the macro accepts an unlimited number of extra
542/// (comma-separated) parameters.
543///
544/// Each parameter has one of these four syntaxes:
545///
546/// - `key = value`, where `key` is an identifier and `value` an expression that implements the
547/// `Display` trait.
548/// - `key = ?value`, where `key` is an identifier and `value` an expression that implements
549/// the `Debug` trait.
550/// - `key`, which is syntax sugar for `key = key`.
551/// - `?key`, which is syntax sugar for `key = ?key`.
552///
553#[macro_export]
554macro_rules! log {
555 ($plat:expr, $level:ident, $target:expr, $message:expr) => {
556 log!($plat, $level, $target, $message,)
557 };
558 ($plat:expr, $level:ident, $target:expr, $message:expr, $($params:tt)*) => {
559 $crate::platform::PlatformRef::log(
560 $plat,
561 $crate::platform::LogLevel::$level,
562 $target,
563 AsRef::<str>::as_ref(&$message),
564 $crate::log_inner!($($params)*)
565 )
566 };
567}