Coverage Report

Created: 2024-05-16 12:16

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