tor_rtcompat/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46
47// TODO #1645 (either remove this, or decide to have it everywhere)
48#![cfg_attr(not(all(feature = "full")), allow(unused))]
49
50#[cfg(all(
51    any(feature = "native-tls", feature = "rustls"),
52    any(feature = "async-std", feature = "tokio", feature = "smol")
53))]
54pub(crate) mod impls;
55pub mod task;
56
57mod coarse_time;
58mod compound;
59mod dyn_time;
60pub mod general;
61mod opaque;
62pub mod scheduler;
63mod timer;
64mod traits;
65pub mod unimpl;
66pub mod unix;
67
68#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))]
69use std::io;
70pub use traits::{
71    Blocking, CertifiedConn, CoarseTimeProvider, NetStreamListener, NetStreamProvider,
72    NoOpStreamOpsHandle, Runtime, SleepProvider, StreamOps, TlsProvider, ToplevelBlockOn,
73    ToplevelRuntime, UdpProvider, UdpSocket, UnsupportedStreamOp,
74};
75
76pub use coarse_time::{CoarseDuration, CoarseInstant, RealCoarseTimeProvider};
77pub use dyn_time::DynTimeProvider;
78pub use timer::{SleepProviderExt, Timeout, TimeoutError};
79
80/// Traits used to describe TLS connections and objects that can
81/// create them.
82pub mod tls {
83    pub use crate::traits::{CertifiedConn, TlsConnector};
84
85    #[cfg(all(
86        feature = "native-tls",
87        any(feature = "tokio", feature = "async-std", feature = "smol")
88    ))]
89    pub use crate::impls::native_tls::NativeTlsProvider;
90    #[cfg(all(
91        feature = "rustls",
92        any(feature = "tokio", feature = "async-std", feature = "smol")
93    ))]
94    pub use crate::impls::rustls::RustlsProvider;
95}
96
97#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "tokio"))]
98pub mod tokio;
99
100#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "async-std"))]
101pub mod async_std;
102
103#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "smol"))]
104pub mod smol;
105
106pub use compound::{CompoundRuntime, RuntimeSubstExt};
107
108#[cfg(all(
109    any(feature = "native-tls", feature = "rustls"),
110    feature = "async-std",
111    not(feature = "tokio")
112))]
113use async_std as preferred_backend_mod;
114#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "tokio"))]
115use tokio as preferred_backend_mod;
116
117/// The runtime that we prefer to use, out of all the runtimes compiled into the
118/// tor-rtcompat crate.
119///
120/// If `tokio` and `async-std` are both available, we prefer `tokio` for its
121/// performance.
122/// If `native_tls` and `rustls` are both available, we prefer `native_tls` since
123/// it has been used in Arti for longer.
124///
125/// The process [**may not fork**](crate#do-not-fork)
126/// (except, very carefully, before exec)
127/// after creating this or any other `Runtime`.
128#[cfg(all(
129    any(feature = "native-tls", feature = "rustls"),
130    any(feature = "async-std", feature = "tokio")
131))]
132#[derive(Clone)]
133pub struct PreferredRuntime {
134    /// The underlying runtime object.
135    inner: preferred_backend_mod::PreferredRuntime,
136}
137
138#[cfg(all(
139    any(feature = "native-tls", feature = "rustls"),
140    any(feature = "async-std", feature = "tokio")
141))]
142crate::opaque::implement_opaque_runtime! {
143    PreferredRuntime { inner : preferred_backend_mod::PreferredRuntime }
144}
145
146#[cfg(all(
147    any(feature = "native-tls", feature = "rustls"),
148    any(feature = "async-std", feature = "tokio")
149))]
150impl PreferredRuntime {
151    /// Obtain a [`PreferredRuntime`] from the currently running asynchronous runtime.
152    /// Generally, this is what you want.
153    ///
154    /// This tries to get a handle to a currently running asynchronous runtime, and
155    /// wraps it; the returned [`PreferredRuntime`] isn't the same thing as the
156    /// asynchronous runtime object itself (e.g. `tokio::runtime::Runtime`).
157    ///
158    /// # Panics
159    ///
160    /// When `tor-rtcompat` is compiled with the `tokio` feature enabled
161    /// (regardless of whether the `async-std` feature is also enabled),
162    /// panics if called outside of Tokio runtime context.
163    /// See `tokio::runtime::Handle::current`.
164    ///
165    /// # Usage notes
166    ///
167    /// Once you have a runtime returned by this function, you should
168    /// just create more handles to it via [`Clone`].
169    ///
170    /// # Limitations
171    ///
172    /// If the `tor-rtcompat` crate was compiled with `tokio` support,
173    /// this function will never return a runtime based on `async_std`.
174    ///
175    /// The process [**may not fork**](crate#do-not-fork)
176    /// (except, very carefully, before exec)
177    /// after creating this or any other `Runtime`.
178    //
179    // ## Note to Arti developers
180    //
181    // We should never call this from inside other Arti crates, or from
182    // library crates that want to support multiple runtimes!  This
183    // function is for Arti _users_ who want to wrap some existing Tokio
184    // or Async_std runtime as a [`Runtime`].  It is not for library
185    // crates that want to work with multiple runtimes.
186    pub fn current() -> io::Result<Self> {
187        let rt = preferred_backend_mod::PreferredRuntime::current()?;
188
189        Ok(Self { inner: rt })
190    }
191
192    /// Create and return a new instance of the default [`Runtime`].
193    ///
194    /// Generally you should call this function at most once, and then use
195    /// [`Clone::clone()`] to create additional references to that runtime.
196    ///
197    /// Tokio users may want to avoid this function and instead obtain a runtime using
198    /// [`PreferredRuntime::current`]: this function always _builds_ a runtime,
199    /// and if you already have a runtime, that isn't what you want with Tokio.
200    ///
201    /// If you need more fine-grained control over a runtime, you can create it
202    /// using an appropriate builder type or function.
203    ///
204    /// The process [**may not fork**](crate#do-not-fork)
205    /// (except, very carefully, before exec)
206    /// after creating this or any other `Runtime`.
207    //
208    // ## Note to Arti developers
209    //
210    // We should never call this from inside other Arti crates, or from
211    // library crates that want to support multiple runtimes!  This
212    // function is for Arti _users_ who want to wrap some existing Tokio
213    // or Async_std runtime as a [`Runtime`].  It is not for library
214    // crates that want to work with multiple runtimes.
215    pub fn create() -> io::Result<Self> {
216        let rt = preferred_backend_mod::PreferredRuntime::create()?;
217
218        Ok(Self { inner: rt })
219    }
220
221    /// Helper to run a single test function in a freshly created runtime.
222    ///
223    /// # Panics
224    ///
225    /// Panics if we can't create this runtime.
226    ///
227    /// # Warning
228    ///
229    /// This API is **NOT** for consumption outside Arti. Semver guarantees are not provided.
230    #[doc(hidden)]
231    pub fn run_test<P, F, O>(func: P) -> O
232    where
233        P: FnOnce(Self) -> F,
234        F: futures::Future<Output = O>,
235    {
236        let runtime = Self::create().expect("Failed to create runtime");
237        runtime.clone().block_on(func(runtime))
238    }
239}
240
241/// Helpers for test_with_all_runtimes
242///
243/// # Warning
244///
245/// This API is **NOT** for consumption outside Arti. Semver guarantees are not provided.
246#[doc(hidden)]
247pub mod testing__ {
248    /// A trait for an object that might represent a test failure, or which
249    /// might just be `()`.
250    pub trait TestOutcome {
251        /// Abort if the test has failed.
252        fn check_ok(&self);
253    }
254    impl TestOutcome for () {
255        fn check_ok(&self) {}
256    }
257    impl<E: std::fmt::Debug> TestOutcome for Result<(), E> {
258        fn check_ok(&self) {
259            self.as_ref().expect("Test failure");
260        }
261    }
262}
263
264/// Helper: define a macro that expands a token tree iff a pair of features are
265/// both present.
266macro_rules! declare_conditional_macro {
267    ( $(#[$meta:meta])* macro $name:ident = ($f1:expr, $f2:expr) ) => {
268        $( #[$meta] )*
269        #[cfg(all(feature=$f1, feature=$f2))]
270        #[macro_export]
271        macro_rules! $name {
272            ($tt:tt) => {
273                $tt
274            };
275        }
276
277        $( #[$meta] )*
278        #[cfg(not(all(feature=$f1, feature=$f2)))]
279        #[macro_export]
280        macro_rules! $name {
281            ($tt:tt) => {};
282        }
283
284        // Needed so that we can access this macro at this path, both within the
285        // crate and without.
286        pub use $name;
287    };
288}
289
290/// Defines macros that will expand when certain runtimes are available.
291#[doc(hidden)]
292pub mod cond {
293    declare_conditional_macro! {
294        /// Expand a token tree if the TokioNativeTlsRuntime is available.
295        #[doc(hidden)]
296        macro if_tokio_native_tls_present = ("tokio", "native-tls")
297    }
298    declare_conditional_macro! {
299        /// Expand a token tree if the TokioRustlsRuntime is available.
300        #[doc(hidden)]
301        macro if_tokio_rustls_present = ("tokio", "rustls")
302    }
303    declare_conditional_macro! {
304        /// Expand a token tree if the TokioNativeTlsRuntime is available.
305        #[doc(hidden)]
306        macro if_async_std_native_tls_present = ("async-std", "native-tls")
307    }
308    declare_conditional_macro! {
309        /// Expand a token tree if the TokioNativeTlsRuntime is available.
310        #[doc(hidden)]
311        macro if_async_std_rustls_present = ("async-std", "rustls")
312    }
313    declare_conditional_macro! {
314        /// Expand a token tree if the SmolNativeTlsRuntime is available.
315        #[doc(hidden)]
316        macro if_smol_native_tls_present = ("smol", "native-tls")
317    }
318    declare_conditional_macro! {
319        /// Expand a token tree if the SmolRustlsRuntime is available.
320        #[doc(hidden)]
321        macro if_smol_rustls_present = ("smol", "rustls")
322    }
323}
324
325/// Run a test closure, passing as argument every supported runtime.
326///
327/// Usually, prefer `tor_rtmock::MockRuntime::test_with_various` to this.
328/// Use this macro only when you need to interact with things
329/// that `MockRuntime` can't handle,
330///
331/// If everything in your test case is supported by `MockRuntime`,
332/// you should use that instead:
333/// that will give superior test coverage *and* a (more) deterministic test.
334///
335/// (This is a macro so that it can repeat the closure as multiple separate
336/// expressions, so it can take on two different types, if needed.)
337//
338// NOTE(eta): changing this #[cfg] can affect tests inside this crate that use
339//            this macro, like in scheduler.rs
340#[macro_export]
341#[cfg(all(
342    any(feature = "native-tls", feature = "rustls"),
343    any(feature = "tokio", feature = "async-std", feature = "smol"),
344))]
345macro_rules! test_with_all_runtimes {
346    ( $fn:expr ) => {{
347        use $crate::cond::*;
348        use $crate::testing__::TestOutcome;
349        // We have to do this outcome-checking business rather than just using
350        // the ? operator or calling expect() because some of the closures that
351        // we use this macro with return (), and some return Result.
352
353        if_tokio_native_tls_present! {{
354           $crate::tokio::TokioNativeTlsRuntime::run_test($fn).check_ok();
355        }}
356        if_tokio_rustls_present! {{
357            $crate::tokio::TokioRustlsRuntime::run_test($fn).check_ok();
358        }}
359        if_async_std_native_tls_present! {{
360            $crate::async_std::AsyncStdNativeTlsRuntime::run_test($fn).check_ok();
361        }}
362        if_async_std_rustls_present! {{
363            $crate::async_std::AsyncStdRustlsRuntime::run_test($fn).check_ok();
364        }}
365        if_smol_native_tls_present! {{
366            $crate::smol::SmolNativeTlsRuntime::run_test($fn).check_ok();
367        }}
368        if_smol_rustls_present! {{
369            $crate::smol::SmolRustlsRuntime::run_test($fn).check_ok();
370        }}
371    }};
372}
373
374/// Run a test closure, passing as argument one supported runtime.
375///
376/// Usually, prefer `tor_rtmock::MockRuntime::test_with_various` to this.
377/// Use this macro only when you need to interact with things
378/// that `MockRuntime` can't handle.
379///
380/// If everything in your test case is supported by `MockRuntime`,
381/// you should use that instead:
382/// that will give superior test coverage *and* a (more) deterministic test.
383///
384/// (Always prefers tokio if present.)
385#[macro_export]
386#[cfg(all(
387    any(feature = "native-tls", feature = "rustls"),
388    any(feature = "tokio", feature = "async-std"),
389))]
390macro_rules! test_with_one_runtime {
391    ( $fn:expr ) => {{ $crate::PreferredRuntime::run_test($fn) }};
392}
393
394#[cfg(all(
395    test,
396    any(feature = "native-tls", feature = "rustls"),
397    any(feature = "async-std", feature = "tokio", feature = "smol"),
398    not(miri), // Many of these tests use real sockets or SystemTime.
399))]
400mod test {
401    #![allow(clippy::unwrap_used, clippy::unnecessary_wraps)]
402    use crate::SleepProviderExt;
403    use crate::ToplevelRuntime;
404
405    use crate::traits::*;
406
407    use futures::io::{AsyncReadExt, AsyncWriteExt};
408    use futures::stream::StreamExt;
409    use native_tls_crate as native_tls;
410    use std::io::Result as IoResult;
411    use std::net::SocketAddr;
412    use std::net::{Ipv4Addr, SocketAddrV4};
413    use std::time::{Duration, Instant};
414
415    // Test "sleep" with a tiny delay, and make sure that at least that
416    // much delay happens.
417    fn small_delay<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
418        let rt = runtime.clone();
419        runtime.block_on(async {
420            let i1 = Instant::now();
421            let one_msec = Duration::from_millis(1);
422            rt.sleep(one_msec).await;
423            let i2 = Instant::now();
424            assert!(i2 >= i1 + one_msec);
425        });
426        Ok(())
427    }
428
429    // Try a timeout operation that will succeed.
430    fn small_timeout_ok<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
431        let rt = runtime.clone();
432        runtime.block_on(async {
433            let one_day = Duration::from_secs(86400);
434            let outcome = rt.timeout(one_day, async { 413_u32 }).await;
435            assert_eq!(outcome, Ok(413));
436        });
437        Ok(())
438    }
439
440    // Try a timeout operation that will time out.
441    fn small_timeout_expire<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
442        use futures::future::pending;
443
444        let rt = runtime.clone();
445        runtime.block_on(async {
446            let one_micros = Duration::from_micros(1);
447            let outcome = rt.timeout(one_micros, pending::<()>()).await;
448            assert_eq!(outcome, Err(crate::TimeoutError));
449            assert_eq!(
450                outcome.err().unwrap().to_string(),
451                "Timeout expired".to_string()
452            );
453        });
454        Ok(())
455    }
456    // Try a little wallclock delay.
457    //
458    // NOTE: This test will fail if the clock jumps a lot while it's
459    // running.  We should use simulated time instead.
460    fn tiny_wallclock<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
461        let rt = runtime.clone();
462        runtime.block_on(async {
463            let i1 = Instant::now();
464            let now = runtime.wallclock();
465            let one_millis = Duration::from_millis(1);
466            let one_millis_later = now + one_millis;
467
468            rt.sleep_until_wallclock(one_millis_later).await;
469
470            let i2 = Instant::now();
471            let newtime = runtime.wallclock();
472            assert!(newtime >= one_millis_later);
473            assert!(i2 - i1 >= one_millis);
474        });
475        Ok(())
476    }
477
478    // Try connecting to ourself and sending a little data.
479    //
480    // NOTE: requires Ipv4 localhost.
481    fn self_connect_tcp<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
482        let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
483        let rt1 = runtime.clone();
484
485        let listener = runtime.block_on(rt1.listen(&(SocketAddr::from(localhost))))?;
486        let addr = listener.local_addr()?;
487
488        runtime.block_on(async {
489            let task1 = async {
490                let mut buf = vec![0_u8; 11];
491                let (mut con, _addr) = listener.incoming().next().await.expect("closed?")?;
492                con.read_exact(&mut buf[..]).await?;
493                IoResult::Ok(buf)
494            };
495            let task2 = async {
496                let mut con = rt1.connect(&addr).await?;
497                con.write_all(b"Hello world").await?;
498                con.flush().await?;
499                IoResult::Ok(())
500            };
501
502            let (data, send_r) = futures::join!(task1, task2);
503            send_r?;
504
505            assert_eq!(&data?[..], b"Hello world");
506
507            Ok(())
508        })
509    }
510
511    // Try connecting to ourself and sending a little data.
512    //
513    // NOTE: requires Ipv4 localhost.
514    fn self_connect_udp<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
515        let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
516        let rt1 = runtime.clone();
517
518        let socket1 = runtime.block_on(rt1.bind(&(localhost.into())))?;
519        let addr1 = socket1.local_addr()?;
520
521        let socket2 = runtime.block_on(rt1.bind(&(localhost.into())))?;
522        let addr2 = socket2.local_addr()?;
523
524        runtime.block_on(async {
525            let task1 = async {
526                let mut buf = [0_u8; 16];
527                let (len, addr) = socket1.recv(&mut buf[..]).await?;
528                IoResult::Ok((buf[..len].to_vec(), addr))
529            };
530            let task2 = async {
531                socket2.send(b"Hello world", &addr1).await?;
532                IoResult::Ok(())
533            };
534
535            let (recv_r, send_r) = futures::join!(task1, task2);
536            send_r?;
537            let (buff, addr) = recv_r?;
538            assert_eq!(addr2, addr);
539            assert_eq!(&buff, b"Hello world");
540
541            Ok(())
542        })
543    }
544
545    // Try out our incoming connection stream code.
546    //
547    // We launch a few connections and make sure that we can read data on
548    // them.
549    fn listener_stream<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
550        let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
551        let rt1 = runtime.clone();
552
553        let listener = runtime
554            .block_on(rt1.listen(&SocketAddr::from(localhost)))
555            .unwrap();
556        let addr = listener.local_addr().unwrap();
557        let mut stream = listener.incoming();
558
559        runtime.block_on(async {
560            let task1 = async {
561                let mut n = 0_u32;
562                loop {
563                    let (mut con, _addr) = stream.next().await.unwrap()?;
564                    let mut buf = [0_u8; 11];
565                    con.read_exact(&mut buf[..]).await?;
566                    n += 1;
567                    if &buf[..] == b"world done!" {
568                        break IoResult::Ok(n);
569                    }
570                }
571            };
572            let task2 = async {
573                for _ in 0_u8..5 {
574                    let mut con = rt1.connect(&addr).await?;
575                    con.write_all(b"Hello world").await?;
576                    con.flush().await?;
577                }
578                let mut con = rt1.connect(&addr).await?;
579                con.write_all(b"world done!").await?;
580                con.flush().await?;
581                con.close().await?;
582                IoResult::Ok(())
583            };
584
585            let (n, send_r) = futures::join!(task1, task2);
586            send_r?;
587
588            assert_eq!(n?, 6);
589
590            Ok(())
591        })
592    }
593
594    // Try listening on an address and connecting there, except using TLS.
595    //
596    // Note that since we don't have async tls server support yet, I'm just
597    // going to use a thread.
598    fn simple_tls<R: ToplevelRuntime>(runtime: &R) -> IoResult<()> {
599        /*
600         A simple expired self-signed rsa-2048 certificate.
601
602         Generated by running the make-cert.c program in tor-rtcompat/test-data-helper,
603         and then making a PFX file using
604
605         openssl pkcs12 -export -certpbe PBE-SHA1-3DES -out test.pfx -inkey test.key -in test.crt
606
607         The password is "abc".
608        */
609        static PFX_ID: &[u8] = include_bytes!("test.pfx");
610        // Note that we need to set a password on the pkcs12 file, since apparently
611        // OSX doesn't support pkcs12 with empty passwords. (That was arti#111).
612        static PFX_PASSWORD: &str = "abc";
613
614        let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
615        let listener = std::net::TcpListener::bind(localhost)?;
616        let addr = listener.local_addr()?;
617
618        let identity = native_tls::Identity::from_pkcs12(PFX_ID, PFX_PASSWORD).unwrap();
619
620        // See note on function for why we're using a thread here.
621        let th = std::thread::spawn(move || {
622            // Accept a single TLS connection and run an echo server
623            use std::io::{Read, Write};
624            let acceptor = native_tls::TlsAcceptor::new(identity).unwrap();
625            let (con, _addr) = listener.accept()?;
626            let mut con = acceptor.accept(con).unwrap();
627            let mut buf = [0_u8; 16];
628            loop {
629                let n = con.read(&mut buf)?;
630                if n == 0 {
631                    break;
632                }
633                con.write_all(&buf[..n])?;
634            }
635            IoResult::Ok(())
636        });
637
638        let connector = runtime.tls_connector();
639
640        runtime.block_on(async {
641            let text = b"I Suddenly Dont Understand Anything";
642            let mut buf = vec![0_u8; text.len()];
643            let conn = runtime.connect(&addr).await?;
644            let mut conn = connector.negotiate_unvalidated(conn, "Kan.Aya").await?;
645            assert!(conn.peer_certificate()?.is_some());
646            conn.write_all(text).await?;
647            conn.flush().await?;
648            conn.read_exact(&mut buf[..]).await?;
649            assert_eq!(&buf[..], text);
650            conn.close().await?;
651            IoResult::Ok(())
652        })?;
653
654        th.join().unwrap()?;
655        IoResult::Ok(())
656    }
657
658    macro_rules! tests_with_runtime {
659        { $runtime:expr  => $($id:ident),* $(,)? } => {
660            $(
661                #[test]
662                fn $id() -> std::io::Result<()> {
663                    super::$id($runtime)
664                }
665            )*
666        }
667    }
668
669    macro_rules! runtime_tests {
670        { $($id:ident),* $(,)? } =>
671        {
672           #[cfg(feature="tokio")]
673            mod tokio_runtime_tests {
674                tests_with_runtime! { &crate::tokio::PreferredRuntime::create()? => $($id),* }
675            }
676            #[cfg(feature="async-std")]
677            mod async_std_runtime_tests {
678                tests_with_runtime! { &crate::async_std::PreferredRuntime::create()? => $($id),* }
679            }
680            #[cfg(feature="smol")]
681            mod smol_runtime_tests {
682                tests_with_runtime! { &crate::smol::PreferredRuntime::create()? => $($id),* }
683            }
684            mod default_runtime_tests {
685                tests_with_runtime! { &crate::PreferredRuntime::create()? => $($id),* }
686            }
687        }
688    }
689
690    macro_rules! tls_runtime_tests {
691        { $($id:ident),* $(,)? } =>
692        {
693            #[cfg(all(feature="tokio", feature = "native-tls"))]
694            mod tokio_native_tls_tests {
695                tests_with_runtime! { &crate::tokio::TokioNativeTlsRuntime::create()? => $($id),* }
696            }
697            #[cfg(all(feature="async-std", feature = "native-tls"))]
698            mod async_std_native_tls_tests {
699                tests_with_runtime! { &crate::async_std::AsyncStdNativeTlsRuntime::create()? => $($id),* }
700            }
701            #[cfg(all(feature="smol", feature = "native-tls"))]
702            mod smol_native_tls_tests {
703                tests_with_runtime! { &crate::smol::SmolNativeTlsRuntime::create()? => $($id),* }
704            }
705            #[cfg(all(feature="tokio", feature="rustls"))]
706            mod tokio_rustls_tests {
707                tests_with_runtime! {  &crate::tokio::TokioRustlsRuntime::create()? => $($id),* }
708            }
709            #[cfg(all(feature="async-std", feature="rustls"))]
710            mod async_std_rustls_tests {
711                tests_with_runtime! {  &crate::async_std::AsyncStdRustlsRuntime::create()? => $($id),* }
712            }
713            #[cfg(all(feature="smol", feature="rustls"))]
714            mod smol_rustls_tests {
715                tests_with_runtime! {  &crate::smol::SmolRustlsRuntime::create()? => $($id),* }
716            }
717            mod default_runtime_tls_tests {
718                tests_with_runtime! { &crate::PreferredRuntime::create()? => $($id),* }
719            }
720        }
721    }
722
723    runtime_tests! {
724        small_delay,
725        small_timeout_ok,
726        small_timeout_expire,
727        tiny_wallclock,
728        self_connect_tcp,
729        self_connect_udp,
730        listener_stream,
731    }
732
733    tls_runtime_tests! {
734        simple_tls,
735    }
736}