1
1
//! Compatibility between different async runtimes for Arti.
2
//!
3
//! # Overview
4
//!
5
//! Rust's support for asynchronous programming is powerful, but still
6
//! a bit immature: there are multiple powerful runtimes you can use,
7
//! but they do not expose a consistent set of interfaces.
8
//!
9
//! The [`futures`] API abstracts much of the differences among these
10
//! runtime libraries, but there are still areas where no standard API
11
//! yet exists, including:
12
//!  - Network programming.
13
//!  - Time and delays.
14
//!  - Launching new tasks
15
//!  - Blocking until a task is finished.
16
//!
17
//! Additionally, the `AsyncRead` and `AsyncWrite` traits provide by
18
//! [`futures`] are not the same as those provided by `tokio`, and
19
//! require compatibility wrappers to use.
20
//!
21
//! To solve these problems, the `tor-rtcompat` crate provides a set
22
//! of traits that represent a runtime's ability to perform these
23
//! tasks, along with implementations for these traits for the `tokio`
24
//! and `async-std` runtimes.  In the future we hope to add support
25
//! for other runtimes as needed.
26
//!
27
//! This crate is part of
28
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
29
//! implement [Tor](https://www.torproject.org/) in Rust.
30
//! As such, it does not currently include (or
31
//! plan to include) any functionality beyond what Arti needs to
32
//! implement Tor.
33
//!
34
//! We hope that in the future this crate can be replaced (or mostly
35
//! replaced) with standardized and general-purpose versions of the
36
//! traits it provides.
37
//!
38
//! # Using `tor-rtcompat`
39
//!
40
//! The `tor-rtcompat` crate provides several traits that
41
//! encapsulate different runtime capabilities.
42
//!
43
//!  * A runtime is a [`BlockOn`] if it can block on a future.
44
//!  * A runtime is a [`SleepProvider`] if it can make timer futures that
45
//!    become Ready after a given interval of time.
46
//!  * A runtime is a [`TcpProvider`] if it can make and receive TCP
47
//!    connections
48
//!  * A runtime is a [`TlsProvider`] if it can make TLS connections.
49
//!
50
//! For convenience, the [`Runtime`] trait derives from all the traits
51
//! above, plus [`futures::task::Spawn`] and [`Send`].
52
//!
53
//! You can get a [`Runtime`] in several ways:
54
//!
55
//!   * If you already have an asynchronous backend (for example, one
56
//!     that you built with tokio by running with
57
//!     `#[tokio::main]`), you can wrap it as a [`Runtime`] with
58
//!     [`PreferredRuntime::current()`].
59
//!
60
//!   * If you want to construct a default runtime that you won't be
61
//!     using for anything besides Arti, you can use [`PreferredRuntime::create()`].
62
//!
63
//! Both of the above methods use the "preferred runtime", which is usually Tokio.
64
//! However, changing the set of Cargo features available can affect this; see
65
//! [`PreferredRuntime`] for more.
66
//!
67
//!   * If you want to use a runtime with an explicitly chosen backend,
68
//!     name its type directly as [`async_std::AsyncStdNativeTlsRuntime`],
69
//!     [`async_std::AsyncStdRustlsRuntime`], [`tokio::TokioNativeTlsRuntime`],
70
//!     or [`tokio::TokioRustlsRuntime`]. To construct one of these runtimes,
71
//!     call its `create()` method.  Or if you have already constructed a
72
//!     Tokio runtime that you want to use, you can wrap it as a
73
//!     [`Runtime`] explicitly with `current()`.
74
//!
75
//! # Advanced usage: implementing runtimes yourself
76
//!
77
//! You might want to implement some of the traits above (especially [`TcpProvider`] and
78
//! [`TlsProvider`]) if you're embedding Arti, and want more control over the resources it uses.
79
//! For example, you might want to perform actions when TCP connections open and close, replace the
80
//! TLS stack with your own, or proxy TCP connections over your own custom transport.
81
//!
82
//! This can be more easily accomplished using the [`CompoundRuntime`] type, which lets you
83
//! create a [`Runtime`] from various implementors of the various traits (which don't all need to
84
//! be the same).
85
//!
86
//! See [`arti-client/examples/hook-tcp.rs`](https://gitlab.torproject.org/tpo/core/arti/-/blob/main/crates/arti-client/examples/hook-tcp.rs)
87
//! for a full example of this.
88
//!
89
//! # Cargo features
90
//!
91
//! Features supported by this crate:
92
//!
93
//! * `tokio` -- build with [Tokio](https://tokio.rs/) support
94
//! * `async-std` -- build with [async-std](https://async.rs/) support
95
//! * `native-tls` --  build with the [native-tls](https://github.com/sfackler/rust-native-tls)
96
//!   crate for TLS support
97
//! * `static` -- link the native TLS library statically (enables the `vendored` feature of the
98
//!   `native-tls` crate).
99
//! * `rustls` -- build with the [rustls](https://github.com/rustls/rustls) crate for TLS support
100
//!
101
//! By default, *this* crate doesn't enable any features. However, you're almost certainly
102
//! using this as part of the `arti-client` crate, which will enable `tokio` and `native-tls` in
103
//! its default configuration.
104
//!
105
//! # Design FAQ
106
//!
107
//! ## Why support `async_std`?
108
//!
109
//! Although Tokio currently a more popular and widely supported
110
//! asynchronous runtime than `async_std` is, we believe that it's
111
//! critical to build Arti against multiple runtimes.
112
//!
113
//! By supporting multiple runtimes, we avoid making tokio-specific
114
//! assumptions in our code, which we hope will make it easier to port
115
//! to other environments (like WASM) in the future.
116
//!
117
//! ## Why a `Runtime` trait, and not a set of functions?
118
//!
119
//! We could simplify this code significantly by removing most of the
120
//! traits it exposes, and instead just exposing a single
121
//! implementation.  For example, instead of exposing a
122
//! [`BlockOn`] trait to represent blocking until a task is
123
//! done, we could just provide a single global `block_on` function.
124
//!
125
//! That simplification would come at a cost, however.  First of all,
126
//! it would make it harder for us to use Rust's "feature" system
127
//! correctly.  Current features are supposed to be _additive only_,
128
//! but if had a single global runtime, then support for different
129
//! backends would be _mutually exclusive_.  (That is, you couldn't
130
//! have both the tokio and async-std features building at the same
131
//! time.)
132
//!
133
//! Secondly, much of our testing in the rest of Arti relies on the
134
//! ability to replace [`Runtime`]s.  By treating a runtime as an
135
//! object, we can override a runtime's view of time, or of the
136
//! network, in order to test asynchronous code effectively.
137
//! (See the [`tor-rtmock`] crate for examples.)
138

            
139
#![deny(missing_docs)]
140
#![warn(noop_method_call)]
141
#![deny(unreachable_pub)]
142
#![warn(clippy::all)]
143
#![deny(clippy::await_holding_lock)]
144
#![deny(clippy::cargo_common_metadata)]
145
#![deny(clippy::cast_lossless)]
146
#![deny(clippy::checked_conversions)]
147
#![warn(clippy::cognitive_complexity)]
148
#![deny(clippy::debug_assert_with_mut_call)]
149
#![deny(clippy::exhaustive_enums)]
150
#![deny(clippy::exhaustive_structs)]
151
#![deny(clippy::expl_impl_clone_on_copy)]
152
#![deny(clippy::fallible_impl_from)]
153
#![deny(clippy::implicit_clone)]
154
#![deny(clippy::large_stack_arrays)]
155
#![warn(clippy::manual_ok_or)]
156
#![deny(clippy::missing_docs_in_private_items)]
157
#![deny(clippy::missing_panics_doc)]
158
#![warn(clippy::needless_borrow)]
159
#![warn(clippy::needless_pass_by_value)]
160
#![warn(clippy::option_option)]
161
#![warn(clippy::rc_buffer)]
162
#![deny(clippy::ref_option_ref)]
163
#![warn(clippy::semicolon_if_nothing_returned)]
164
#![warn(clippy::trait_duplication_in_bounds)]
165
#![deny(clippy::unnecessary_wraps)]
166
#![warn(clippy::unseparated_literal_suffix)]
167
#![deny(clippy::unwrap_used)]
168

            
169
#[cfg(all(
170
    any(feature = "native-tls", feature = "rustls"),
171
    any(feature = "async-std", feature = "tokio")
172
))]
173
pub(crate) mod impls;
174
pub mod task;
175

            
176
mod compound;
177
mod opaque;
178
mod timer;
179
mod traits;
180

            
181
use std::io;
182
pub use traits::{
183
    BlockOn, CertifiedConn, Runtime, SleepProvider, TcpListener, TcpProvider, TlsProvider,
184
};
185

            
186
pub use timer::{SleepProviderExt, Timeout, TimeoutError};
187

            
188
/// Traits used to describe TLS connections and objects that can
189
/// create them.
190
pub mod tls {
191
    pub use crate::traits::{CertifiedConn, TlsConnector};
192

            
193
    #[cfg(feature = "native-tls")]
194
    pub use crate::impls::native_tls::NativeTlsProvider;
195
    #[cfg(feature = "rustls")]
196
    pub use crate::impls::rustls::RustlsProvider;
197
}
198

            
199
#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "tokio"))]
200
pub mod tokio;
201

            
202
#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "async-std"))]
203
pub mod async_std;
204

            
205
pub use compound::CompoundRuntime;
206

            
207
#[cfg(all(
208
    any(feature = "native-tls", feature = "rustls"),
209
    feature = "async-std",
210
    not(feature = "tokio")
211
))]
212
use async_std as preferred_backend_mod;
213
#[cfg(all(any(feature = "native-tls", feature = "rustls"), feature = "tokio"))]
214
use tokio as preferred_backend_mod;
215

            
216
/// The runtime that we prefer to use, out of all the runtimes compiled into the
217
/// tor-rtcompat crate.
218
///
219
/// If `tokio` and `async-std` are both available, we prefer `tokio` for its
220
/// performance.
221
/// If `native_tls` and `rustls` are both available, we prefer `native_tls` since
222
/// it has been used in Arti for longer.
223
#[cfg(all(
224
    any(feature = "native-tls", feature = "rustls"),
225
    any(feature = "async-std", feature = "tokio")
226
))]
227
228
#[derive(Clone)]
228
pub struct PreferredRuntime {
229
    /// The underlying runtime object.
230
    inner: preferred_backend_mod::PreferredRuntime,
231
}
232

            
233
#[cfg(all(
234
    any(feature = "native-tls", feature = "rustls"),
235
    any(feature = "async-std", feature = "tokio")
236
))]
237
10
crate::opaque::implement_opaque_runtime! {
238
10
    PreferredRuntime { inner : preferred_backend_mod::PreferredRuntime }
239
10
}
240

            
241
#[cfg(all(
242
    any(feature = "native-tls", feature = "rustls"),
243
    any(feature = "async-std", feature = "tokio")
244
))]
245
impl PreferredRuntime {
246
    /// Obtain a [`PreferredRuntime`] from the currently running asynchronous runtime.
247
    /// Generally, this is what you want.
248
    ///
249
    /// This tries to get a handle to a currently running asynchronous runtime, and
250
    /// wraps it; the returned [`PreferredRuntime`] isn't the same thing as the
251
    /// asynchronous runtime object itself (e.g. `tokio::runtime::Runtime`).
252
    ///
253
    /// # Panics
254
    ///
255
    /// When `tor-rtcompat` is compiled with the `tokio` feature enabled
256
    /// (regardless of whether the `async-std` feature is also enabled),
257
    /// panics if called outside of Tokio runtime context.
258
    /// See `tokio::runtime::Handle::current`.
259
    ///
260
    /// # Usage notes
261
    ///
262
    /// Once you have a runtime returned by this function, you should
263
    /// just create more handles to it via [`Clone`].
264
    ///
265
    /// # Limitations
266
    ///
267
    /// If the `tor-rtcompat` crate was compiled with `tokio` support,
268
    /// this function will never return a runtime based on `async_std`.
269
    ///
270
    //
271
    // ## Note to Arti developers
272
    //
273
    // We should never call this from inside other Arti crates, or from
274
    // library crates that want to support multiple runtimes!  This
275
    // function is for Arti _users_ who want to wrap some existing Tokio
276
    // or Async_std runtime as a [`Runtime`].  It is not for library
277
    // crates that want to work with multiple runtimes.
278
    pub fn current() -> io::Result<Self> {
279
        let rt = preferred_backend_mod::PreferredRuntime::current()?;
280

            
281
        Ok(Self { inner: rt })
282
    }
283

            
284
    /// Create and return a new instance of the default [`Runtime`].
285
    ///
286
    /// Generally you should call this function at most once, and then use
287
    /// [`Clone::clone()`] to create additional references to that runtime.
288
    ///
289
    /// Tokio users may want to avoid this function and instead obtain a runtime using
290
    /// [`PreferredRuntime::current`]: this function always _builds_ a runtime,
291
    /// and if you already have a runtime, that isn't what you want with Tokio.
292
    ///
293
    /// If you need more fine-grained control over a runtime, you can create it
294
    /// using an appropriate builder type or function.
295
    //
296
    // ## Note to Arti developers
297
    //
298
    // We should never call this from inside other Arti crates, or from
299
    // library crates that want to support multiple runtimes!  This
300
    // function is for Arti _users_ who want to wrap some existing Tokio
301
    // or Async_std runtime as a [`Runtime`].  It is not for library
302
    // crates that want to work with multiple runtimes.
303
577
    pub fn create() -> io::Result<Self> {
304
577
        let rt = preferred_backend_mod::PreferredRuntime::create()?;
305

            
306
577
        Ok(Self { inner: rt })
307
577
    }
308

            
309
    /// Helper to run a single test function in a freshly created runtime.
310
    ///
311
    /// # Panics
312
    ///
313
    /// Panics if we can't create this runtime.
314
    ///
315
    /// # Warning
316
    ///
317
    /// This API is **NOT** for consumption outside Arti. Semver guarantees are not provided.
318
    #[doc(hidden)]
319
39
    pub fn run_test<P, F, O>(func: P) -> O
320
39
    where
321
39
        P: FnOnce(Self) -> F,
322
39
        F: futures::Future<Output = O>,
323
39
    {
324
39
        let runtime = Self::create().expect("Failed to create runtime");
325
39
        runtime.clone().block_on(func(runtime))
326
39
    }
327
}
328

            
329
/// Helpers for test_with_all_runtimes
330
///
331
/// # Warning
332
///
333
/// This API is **NOT** for consumption outside Arti. Semver guarantees are not provided.
334
#[doc(hidden)]
335
pub mod testing__ {
336
    /// A trait for an object that might represent a test failure, or which
337
    /// might just be `()`.
338
    pub trait TestOutcome {
339
        /// Abort if the test has failed.
340
        fn check_ok(&self);
341
    }
342
    impl TestOutcome for () {
343
855
        fn check_ok(&self) {}
344
    }
345
    impl<E: std::fmt::Debug> TestOutcome for Result<(), E> {
346
20
        fn check_ok(&self) {
347
20
            self.as_ref().expect("Test failure");
348
20
        }
349
    }
350
}
351

            
352
/// Helper: define a macro that expands a token tree iff a pair of features are
353
/// both present.
354
macro_rules! declare_conditional_macro {
355
    ( $(#[$meta:meta])* macro $name:ident = ($f1:expr, $f2:expr) ) => {
356
        $( #[$meta] )*
357
        #[cfg(all(feature=$f1, feature=$f2))]
358
        #[macro_export]
359
        macro_rules! $name {
360
            ($tt:tt) => {
361
                $tt
362
            };
363
        }
364

            
365
        $( #[$meta] )*
366
        #[cfg(not(all(feature=$f1, feature=$f2)))]
367
        #[macro_export]
368
        macro_rules! $name {
369
            ($tt:tt) => {};
370
        }
371

            
372
        // Needed so that we can access this macro at this path, both within the
373
        // crate and without.
374
        pub use $name;
375
    };
376
}
377

            
378
/// Defines macros that will expand when certain runtimes are available.
379
#[doc(hidden)]
380
pub mod cond {
381
    declare_conditional_macro! {
382
        /// Expand a token tree if the TokioNativeTlsRuntime is available.
383
        #[doc(hidden)]
384
        macro if_tokio_native_tls_present = ("tokio", "native-tls")
385
    }
386
    declare_conditional_macro! {
387
        /// Expand a token tree if the TokioRustlsRuntime is available.
388
        #[doc(hidden)]
389
        macro if_tokio_rustls_present = ("tokio", "rustls")
390
    }
391
    declare_conditional_macro! {
392
        /// Expand a token tree if the TokioNativeTlsRuntime is available.
393
        #[doc(hidden)]
394
        macro if_async_std_native_tls_present = ("async-std", "native-tls")
395
    }
396
    declare_conditional_macro! {
397
        /// Expand a token tree if the TokioNativeTlsRuntime is available.
398
        #[doc(hidden)]
399
        macro if_async_std_rustls_present = ("async-std", "rustls")
400
    }
401
}
402

            
403
/// Run a test closure, passing as argument every supported runtime.
404
///
405
/// (This is a macro so that it can repeat the closure as multiple separate
406
/// expressions, so it can take on two different types, if needed.)
407
#[macro_export]
408
#[cfg(all(
409
    any(feature = "native-tls", feature = "rustls"),
410
    any(feature = "tokio", feature = "async-std"),
411
))]
412
macro_rules! test_with_all_runtimes {
413
    ( $fn:expr ) => {{
414
        use $crate::cond::*;
415
        use $crate::testing__::TestOutcome;
416
        // We have to do this outcome-checking business rather than just using
417
        // the ? operator or calling expect() because some of the closures that
418
        // we use this macro with return (), and some return Result.
419

            
420
        if_tokio_native_tls_present! {{
421
           $crate::tokio::TokioNativeTlsRuntime::run_test($fn).check_ok();
422
        }}
423
        if_tokio_rustls_present! {{
424
            $crate::tokio::TokioRustlsRuntime::run_test($fn).check_ok();
425
        }}
426
        if_async_std_native_tls_present! {{
427
            $crate::async_std::AsyncStdNativeTlsRuntime::run_test($fn).check_ok();
428
        }}
429
        if_async_std_rustls_present! {{
430
            $crate::async_std::AsyncStdRustlsRuntime::run_test($fn).check_ok();
431
        }}
432
    }};
433
}
434

            
435
/// Run a test closure, passing as argument one supported runtime.
436
///
437
/// (Always prefers tokio if present.)
438
#[macro_export]
439
#[cfg(all(
440
    any(feature = "native-tls", feature = "rustls"),
441
    any(feature = "tokio", feature = "async-std"),
442
))]
443
macro_rules! test_with_one_runtime {
444
    ( $fn:expr ) => {{
445
        $crate::PreferredRuntime::run_test($fn)
446
    }};
447
}
448

            
449
#[cfg(all(
450
    test,
451
    any(feature = "native-tls", feature = "rustls"),
452
    any(feature = "async-std", feature = "tokio")
453
))]
454
mod test {
455
    #![allow(clippy::unwrap_used, clippy::unnecessary_wraps)]
456
    use crate::Runtime;
457
    use crate::SleepProviderExt;
458

            
459
    use crate::traits::*;
460

            
461
    use futures::io::{AsyncReadExt, AsyncWriteExt};
462
    use futures::stream::StreamExt;
463
    use native_tls_crate as native_tls;
464
    use std::io::Result as IoResult;
465
    use std::net::{Ipv4Addr, SocketAddrV4};
466
    use std::time::{Duration, Instant, SystemTime};
467

            
468
    // Test "sleep" with a tiny delay, and make sure that at least that
469
    // much delay happens.
470
    fn small_delay<R: Runtime>(runtime: &R) -> IoResult<()> {
471
        let rt = runtime.clone();
472
        runtime.block_on(async {
473
            let i1 = Instant::now();
474
            let one_msec = Duration::from_millis(1);
475
            rt.sleep(one_msec).await;
476
            let i2 = Instant::now();
477
            assert!(i2 >= i1 + one_msec);
478
        });
479
        Ok(())
480
    }
481

            
482
    // Try a timeout operation that will succeed.
483
    fn small_timeout_ok<R: Runtime>(runtime: &R) -> IoResult<()> {
484
        let rt = runtime.clone();
485
        runtime.block_on(async {
486
            let one_day = Duration::from_secs(86400);
487
            let outcome = rt.timeout(one_day, async { 413_u32 }).await;
488
            assert_eq!(outcome, Ok(413));
489
        });
490
        Ok(())
491
    }
492

            
493
    // Try a timeout operation that will time out.
494
    fn small_timeout_expire<R: Runtime>(runtime: &R) -> IoResult<()> {
495
        use futures::future::pending;
496

            
497
        let rt = runtime.clone();
498
        runtime.block_on(async {
499
            let one_micros = Duration::from_micros(1);
500
            let outcome = rt.timeout(one_micros, pending::<()>()).await;
501
            assert_eq!(outcome, Err(crate::TimeoutError));
502
            assert_eq!(
503
                outcome.err().unwrap().to_string(),
504
                "Timeout expired".to_string()
505
            );
506
        });
507
        Ok(())
508
    }
509
    // Try a little wallclock delay.
510
    //
511
    // NOTE: This test will fail if the clock jumps a lot while it's
512
    // running.  We should use simulated time instead.
513
    fn tiny_wallclock<R: Runtime>(runtime: &R) -> IoResult<()> {
514
        let rt = runtime.clone();
515
        runtime.block_on(async {
516
            let i1 = Instant::now();
517
            let now = SystemTime::now();
518
            let one_millis = Duration::from_millis(1);
519
            let one_millis_later = now + one_millis;
520

            
521
            rt.sleep_until_wallclock(one_millis_later).await;
522

            
523
            let i2 = Instant::now();
524
            let newtime = SystemTime::now();
525
            assert!(newtime >= one_millis_later);
526
            assert!(i2 - i1 >= one_millis);
527
        });
528
        Ok(())
529
    }
530

            
531
    // Try connecting to ourself and sending a little data.
532
    //
533
    // NOTE: requires Ipv4 localhost.
534
    fn self_connect<R: Runtime>(runtime: &R) -> IoResult<()> {
535
        let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
536
        let rt1 = runtime.clone();
537

            
538
        let listener = runtime.block_on(rt1.listen(&(localhost.into())))?;
539
        let addr = listener.local_addr()?;
540

            
541
        runtime.block_on(async {
542
            let task1 = async {
543
                let mut buf = vec![0_u8; 11];
544
                let (mut con, _addr) = listener.accept().await?;
545
                con.read_exact(&mut buf[..]).await?;
546
                IoResult::Ok(buf)
547
            };
548
            let task2 = async {
549
                let mut con = rt1.connect(&addr).await?;
550
                con.write_all(b"Hello world").await?;
551
                con.flush().await?;
552
                IoResult::Ok(())
553
            };
554

            
555
            let (data, send_r) = futures::join!(task1, task2);
556
            send_r?;
557

            
558
            assert_eq!(&data?[..], b"Hello world");
559

            
560
            Ok(())
561
        })
562
    }
563

            
564
    // Try out our incoming connection stream code.
565
    //
566
    // We launch a few connections and make sure that we can read data on
567
    // them.
568
    fn listener_stream<R: Runtime>(runtime: &R) -> IoResult<()> {
569
        let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
570
        let rt1 = runtime.clone();
571

            
572
        let listener = runtime.block_on(rt1.listen(&(localhost.into()))).unwrap();
573
        let addr = listener.local_addr().unwrap();
574
        let mut stream = listener.incoming();
575

            
576
        runtime.block_on(async {
577
            let task1 = async {
578
                let mut n = 0_u32;
579
                loop {
580
                    let (mut con, _addr) = stream.next().await.unwrap()?;
581
                    let mut buf = vec![0_u8; 11];
582
                    con.read_exact(&mut buf[..]).await?;
583
                    n += 1;
584
                    if &buf[..] == b"world done!" {
585
                        break IoResult::Ok(n);
586
                    }
587
                }
588
            };
589
            let task2 = async {
590
                for _ in 0_u8..5 {
591
                    let mut con = rt1.connect(&addr).await?;
592
                    con.write_all(b"Hello world").await?;
593
                    con.flush().await?;
594
                }
595
                let mut con = rt1.connect(&addr).await?;
596
                con.write_all(b"world done!").await?;
597
                con.flush().await?;
598
                con.close().await?;
599
                IoResult::Ok(())
600
            };
601

            
602
            let (n, send_r) = futures::join!(task1, task2);
603
            send_r?;
604

            
605
            assert_eq!(n?, 6);
606

            
607
            Ok(())
608
        })
609
    }
610

            
611
    // Try listening on an address and connecting there, except using TLS.
612
    //
613
    // Note that since we don't have async tls server support yet, I'm just
614
    // going to use a thread.
615
    fn simple_tls<R: Runtime>(runtime: &R) -> IoResult<()> {
616
        /*
617
         A simple expired self-signed rsa-2048 certificate.
618

            
619
         Generated by running the make-cert.c program in tor-rtcompat/test-data-helper,
620
         and then making a PFX file using
621

            
622
         openssl pkcs12 -export -certpbe PBE-SHA1-3DES -out test.pfx -inkey test.key -in test.crt
623

            
624
         The password is "abc".
625
        */
626
        static PFX_ID: &[u8] = include_bytes!("test.pfx");
627
        // Note that we need to set a password on the pkcs12 file, since apparently
628
        // OSX doesn't support pkcs12 with empty passwords. (That was arti#111).
629
        static PFX_PASSWORD: &str = "abc";
630

            
631
        let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
632
        let listener = std::net::TcpListener::bind(localhost)?;
633
        let addr = listener.local_addr()?;
634

            
635
        let identity = native_tls::Identity::from_pkcs12(PFX_ID, PFX_PASSWORD).unwrap();
636

            
637
        // See note on function for why we're using a thread here.
638
        let th = std::thread::spawn(move || {
639
            // Accept a single TLS connection and run an echo server
640
            use std::io::{Read, Write};
641
            let acceptor = native_tls::TlsAcceptor::new(identity).unwrap();
642
            let (con, _addr) = listener.accept()?;
643
            let mut con = acceptor.accept(con).unwrap();
644
            let mut buf = [0_u8; 16];
645
            loop {
646
                let n = con.read(&mut buf)?;
647
                if n == 0 {
648
                    break;
649
                }
650
                con.write_all(&buf[..n])?;
651
            }
652
            IoResult::Ok(())
653
        });
654

            
655
        let connector = runtime.tls_connector();
656

            
657
        runtime.block_on(async {
658
            let text = b"I Suddenly Dont Understand Anything";
659
            let mut buf = vec![0_u8; text.len()];
660
            let conn = runtime.connect(&addr).await?;
661
            let mut conn = connector.negotiate_unvalidated(conn, "Kan.Aya").await?;
662
            assert!(conn.peer_certificate()?.is_some());
663
            conn.write_all(text).await?;
664
            conn.flush().await?;
665
            conn.read_exact(&mut buf[..]).await?;
666
            assert_eq!(&buf[..], text);
667
            conn.close().await?;
668
            IoResult::Ok(())
669
        })?;
670

            
671
        th.join().unwrap()?;
672
        IoResult::Ok(())
673
    }
674

            
675
    macro_rules! tests_with_runtime {
676
        { $runtime:expr  => $($id:ident),* $(,)? } => {
677
            $(
678
                #[test]
679
                fn $id() -> std::io::Result<()> {
680
                    super::$id($runtime)
681
                }
682
            )*
683
        }
684
    }
685

            
686
    macro_rules! runtime_tests {
687
        { $($id:ident),* $(,)? } =>
688
        {
689
           #[cfg(feature="tokio")]
690
            mod tokio_runtime_tests {
691
                tests_with_runtime! { &crate::tokio::PreferredRuntime::create()? => $($id),* }
692
            }
693
            #[cfg(feature="async-std")]
694
            mod async_std_runtime_tests {
695
                tests_with_runtime! { &crate::async_std::PreferredRuntime::create()? => $($id),* }
696
            }
697
            mod default_runtime_tests {
698
                tests_with_runtime! { &crate::PreferredRuntime::create()? => $($id),* }
699
            }
700
        }
701
    }
702

            
703
    macro_rules! tls_runtime_tests {
704
        { $($id:ident),* $(,)? } =>
705
        {
706
            #[cfg(all(feature="tokio", feature = "native-tls"))]
707
            mod tokio_native_tls_tests {
708
                tests_with_runtime! { &crate::tokio::TokioNativeTlsRuntime::create()? => $($id),* }
709
            }
710
            #[cfg(all(feature="async-std", feature = "native-tls"))]
711
            mod async_std_native_tls_tests {
712
                tests_with_runtime! { &crate::async_std::AsyncStdNativeTlsRuntime::create()? => $($id),* }
713
            }
714
            #[cfg(all(feature="tokio", feature="rustls"))]
715
            mod tokio_rustls_tests {
716
                tests_with_runtime! {  &crate::tokio::TokioRustlsRuntime::create()? => $($id),* }
717
            }
718
            #[cfg(all(feature="async-std", feature="rustls"))]
719
            mod async_std_rustls_tests {
720
                tests_with_runtime! {  &crate::async_std::AsyncStdRustlsRuntime::create()? => $($id),* }
721
            }
722
            mod default_runtime_tls_tests {
723
                tests_with_runtime! { &crate::PreferredRuntime::create()? => $($id),* }
724
            }
725
        }
726
    }
727

            
728
    runtime_tests! {
729
        small_delay,
730
        small_timeout_ok,
731
        small_timeout_expire,
732
        tiny_wallclock,
733
        self_connect,
734
        listener_stream,
735
    }
736

            
737
    tls_runtime_tests! {
738
        simple_tls,
739
    }
740
}