1
//! Module exposing the relay circuit reactor subsystem.
2
//!
3
//! See [`reactor`](crate::circuit::reactor) for a description of the overall architecture.
4
//!
5
//! All cells moving in the forward direction (i.e. away from the client)
6
//! are handled by the forward reactor, which deals with
7
//!
8
//!  * unrecognized RELAY* cells, by moving them in the forward direction (towards the exit)
9
//!  * recognized RELAY* cells, by splitting each cell into messages, and handling
10
//!    each message individually as described in the table below
11
//!    (Note: since prop340 is not yet implemented, in practice there is only 1 message per cell).
12
//!  * DESTROY cells, by tearing down the circuit, and causing a DESTROY to be sent forward,
13
//!    to the next hop, if there is one
14
//!  * PADDING_NEGOTIATE cells (**not yet implemented**)
15
//!
16
//! ```text
17
//!
18
//! Legend: `F` = "forward reactor", `B` = "backward reactor", `S` = "stream reactor"
19
//! `FH` = `ForwardHandler`
20
//!
21
//! | RELAY cmd  | Received in | Handled in            | Description                            |
22
//! |------------|-------------|-----------------------|----------------------------------------|
23
//! | DROP       | F           | FH::handle_meta_msg() | Passed to PaddingController for        |
24
//! |            |             |                       | validation                             |
25
//! |------------|-------------|-----------------------|----------------------------------------|
26
//! | EXTEND2    | F           | FH::handle_meta_msg() | Handled by the ExtendRequestHandler    |
27
//! |            |             |                       | See [forward::extend_handler].         |
28
//! |------------|-------------|-----------------------|----------------------------------------|
29
//! | TRUNCATE   | F           | FH::handle_meta_msg() | Not supported: TRUNCATE is considered  |
30
//! |            |             |                       | a protocol violation, because none of  |
31
//! |            |             |                       | of our implementations send it.        |
32
//! |------------|-------------|-----------------------|----------------------------------------|
33
//! | SENDME     | F           | B                     | Sent to BackwardReactor for handling.  |
34
//! | (sid = 0)  |             |                       | See the [crate::circuit::reactor] docs |
35
//! |------------|-------------|-----------------------|----------------------------------------|
36
//! | Other      | F           | FH::handle_meta_msg() | Rejected as unrecognized               |
37
//! | (sid = 0)  |             |                       |                                        |
38
//! |------------|-------------|-----------------------|----------------------------------------|
39
//! | Other      | F           | S                     | Handled in the `StreamReactor`         |
40
//! | (sid != 0) |             |                       |                                        |
41
//! ```
42

            
43
pub(crate) mod backward;
44
pub(crate) mod forward;
45

            
46
use std::sync::Arc;
47
use std::time::Duration;
48

            
49
use futures::StreamExt as _;
50
use futures::channel::mpsc;
51

            
52
use tor_cell::chancell::CircId;
53
use tor_cell::relaycell::RelayCmd;
54
use tor_linkspec::OwnedChanTarget;
55
use tor_memquota::mq_queue::{ChannelSpec, MpscSpec};
56
use tor_rtcompat::{DynTimeProvider, Runtime};
57

            
58
use crate::channel::Channel;
59
use crate::circuit::circhop::ReactorStreamComponents;
60
use crate::circuit::circhop::{CircHopOutbound, HopSettings};
61
use crate::circuit::reactor::Reactor as BaseReactor;
62
use crate::circuit::reactor::hop_mgr::HopMgr;
63
use crate::circuit::reactor::stream;
64
use crate::circuit::{CircuitRxReceiver, UniqId};
65
use crate::congestion::sendme::StreamRecvWindow;
66
use crate::crypto::cell::{InboundRelayLayer, OutboundRelayLayer};
67
use crate::memquota::{CircuitAccount, SpecificAccount};
68
use crate::relay::RelayCirc;
69
use crate::relay::channel_provider::ChannelProvider;
70
use crate::relay::reactor::backward::Backward;
71
use crate::relay::reactor::forward::Forward;
72
use crate::stream::flow_ctrl::state::WithSidechannelMitigations;
73
use crate::stream::flow_ctrl::xon_xoff::reader::XonXoffReaderCtrl;
74
use crate::stream::incoming::{
75
    IncomingCmdChecker, IncomingStream, IncomingStreamRequestFilter, IncomingStreamRequestHandler,
76
    StreamReqInfo,
77
};
78
use crate::stream::raw::StreamReceiver;
79
use crate::stream::{RECV_WINDOW_INIT, StreamComponents, StreamTarget, Tunnel};
80

            
81
// TODO(circpad): once padding is stabilized, the padding module will be moved out of client.
82
use crate::client::circuit::padding::{PaddingController, PaddingEventStream};
83

            
84
/// Type-alias for the relay base reactor type.
85
type RelayBaseReactor<R> = BaseReactor<R, Forward, Backward>;
86

            
87
/// The entry point of the circuit reactor subsystem.
88
#[allow(unused)] // TODO(relay)
89
#[must_use = "If you don't call run() on a reactor, the circuit won't work."]
90
pub(crate) struct Reactor<R: Runtime>(RelayBaseReactor<R>);
91

            
92
/// A handler customizing the relay stream reactor.
93
struct StreamHandler;
94

            
95
impl stream::StreamHandler for StreamHandler {
96
12
    fn halfstream_expiry(&self, hop: &CircHopOutbound) -> Duration {
97
12
        let ccontrol = hop.ccontrol();
98

            
99
        // Note: if we have no measurements for the RTT, this will be set to 0,
100
        // so the stream will be removed from the stream map immediately,
101
        // and any subsequent messages arriving on it will trigger
102
        // a proto violation causing the circuit to close.
103
        //
104
        // TODO(relay-tuning): we should make sure that this doesn't cause us to
105
        // wrongly close legitimate circuits that still have in-flight stream data
106
12
        ccontrol
107
12
            .lock()
108
12
            .expect("poisoned lock")
109
12
            .rtt()
110
12
            .max_rtt_usec()
111
12
            .map(|rtt| Duration::from_millis(u64::from(rtt)))
112
            // TODO(relay): we should fallback to a non-zero default here
113
            // if we don't have any RTT measurements yet
114
12
            .unwrap_or_default()
115
12
    }
116

            
117
16
    fn flowctrl_sidechannel_mitigations(&self) -> WithSidechannelMitigations {
118
        // We're a relay, so we don't want sidechannel mitigations for flow control.
119
16
        WithSidechannelMitigations::Disabled
120
16
    }
121
}
122

            
123
#[allow(unused)] // TODO(relay)
124
impl<R: Runtime> Reactor<R> {
125
    /// Create a new circuit reactor.
126
    ///
127
    /// Returns the [`Reactor`], a [`RelayCirc`] handle to it,
128
    /// and a [`Stream`](futures::Stream) of `IncomingStream`s.
129
    ///
130
    /// The reactor will send outbound messages on `channel`, receive incoming
131
    /// messages on `input`, and identify this circuit by the channel-local
132
    /// [`CircId`] provided.
133
    ///
134
    /// The internal unique identifier for this circuit will be `unique_id`.
135
    ///
136
    /// The returned `IncomingStream`s are exit, dns, or directory streams.
137
    /// An incoming stream is automatically rejected by the reactor
138
    /// if the provided `IncomingStreamRequestFilter` rejects it.
139
    /// You can also explicitly reject a stream by calling [`IncomingStream::reject`].
140
    /// If the `Stream` is dropped, the next incoming stream request
141
    /// (`BEGIN`, `BEGIN_DIR`, or RESOLVE`)
142
    /// on this circuit will cause the stream reactor to shut down,
143
    /// which will trigger a shutdown of all the circuit reactors (FWD, BWD),
144
    /// which causing the circuit to close.
145
    ///
146
    /// The streams not rejected by the `IncomingStreamRequestFilter` will
147
    /// get an entry in the circuit's stream map.
148
    /// Rejecting such a stream using [`IncomingStream::reject`] will remove the entry.
149
    ///
150
    /// The `IncomingStreamRequestFilter` should only perform inexpensive checks
151
    /// that won't block the reactor.
152
    /// More expensive, or blocking checks, should be handled outside of the circuit reactor,
153
    /// when processing new `IncomingStream`s from the returned Rust stream.
154
    ///
155
    /// Data and directory streams can be accepted by calling [`IncomingStream::accept_data`].
156
    /// The caller is responsible for proxying data between the resulting `DataStream`
157
    /// and the local application stream.
158
    ///
159
    // TODO(relay): say how RESOLVE streams should be handled
160
    //
161
    // TODO: declare a type-alias for the impl futures::Stream return type
162
    // when support for impl in type aliases gets stabilized.
163
    //
164
    // See issue #63063 <https://github.com/rust-lang/rust/issues/63063>
165
    //
166
    // TODO(DEDUP): the incoming stream handling is *very* similar
167
    // to the impll from ServiceOnionServiceDataTunnel::allow_stream_requests.
168
    // We should dedupe these someday, when we rewrite the client reactor
169
    // to use the new multi-reactor architecture
170
    #[allow(clippy::too_many_arguments)] // TODO
171
64
    pub(crate) fn new(
172
64
        runtime: R,
173
64
        channel: &Arc<Channel>,
174
64
        circ_id: CircId,
175
64
        unique_id: UniqId,
176
64
        input: CircuitRxReceiver,
177
64
        crypto_in: Box<dyn InboundRelayLayer + Send>,
178
64
        crypto_out: Box<dyn OutboundRelayLayer + Send>,
179
64
        settings: &HopSettings,
180
64
        chan_provider: Arc<dyn ChannelProvider<BuildSpec = OwnedChanTarget> + Send + Sync>,
181
64
        padding_ctrl: PaddingController,
182
64
        padding_event_stream: PaddingEventStream,
183
64
        incoming_filter: Box<dyn IncomingStreamRequestFilter>,
184
64
        allowed_stream_cmds: &[RelayCmd],
185
64
        memquota: &CircuitAccount,
186
64
    ) -> crate::Result<(
187
64
        Self,
188
64
        Arc<RelayCirc>,
189
64
        impl futures::Stream<Item = IncomingStream> + use<R>,
190
64
    )> {
191
        // NOTE: not registering this channel with the memquota subsystem is okay,
192
        // because it has no buffering (if ever decide to make the size of this buffer
193
        // non-zero for whatever reason, we must remember to register it with memquota
194
        // so that it counts towards the total memory usage for the circuit.
195
        #[allow(clippy::disallowed_methods)]
196
64
        let (stream_tx, stream_rx) = mpsc::channel(0);
197

            
198
        /// The size of the channel receiving IncomingStreamRequestContexts.
199
        ///
200
        // TODO(relay-tuning): buffer size
201
        //
202
        // This is currently set to 2x the initial receive window,
203
        // the same as the buffer size we use for onion services.
204
        // This value was picked arbitrarily,
205
        // and is not necessarily tuned for relay needs.
206
        const INCOMING_BUFFER: usize = crate::stream::STREAM_READER_BUFFER;
207

            
208
64
        let time_provider = DynTimeProvider::new(runtime.clone());
209
64
        let (incoming_sender, incoming_receiver) = MpscSpec::new(INCOMING_BUFFER)
210
64
            .new_mq(time_provider.clone(), memquota.as_raw_account())?;
211

            
212
        // Our IncomingCmdChecker does not reject BEGIN, BEGIN_DIR, RESOLVE cells,
213
        // but that doesn't necessarily mean the stream will be accepted.
214
        // An incoming stream can still be rejected at a later stage,
215
        // by the IncomingStreamRequestFilter, or directly by the consumer of the
216
        // futures::Stream<Item = IncomingStream> (by calling IncomingStream::reject()).
217
64
        let cmd_checker = IncomingCmdChecker::new_any(allowed_stream_cmds);
218
64
        let incoming_handler = IncomingStreamRequestHandler {
219
64
            incoming_sender,
220
64
            hop_num: None,
221
64
            cmd_checker,
222
64
            filter: incoming_filter,
223
64
        };
224
64
        let mut hop_mgr = HopMgr::new_with_incoming_handler(
225
64
            runtime.clone(),
226
64
            unique_id,
227
64
            circ_id,
228
64
            StreamHandler,
229
64
            stream_tx,
230
64
            incoming_handler,
231
64
            memquota.clone(),
232
        );
233

            
234
        // On the relay side, we always have one "hop" (ourselves).
235
        //
236
        // Clients will need to call this function in response to CtrlMsg::Create
237
        // (TODO: for clients, we probably will need to store a bunch more state here)
238
64
        hop_mgr.add_hop(settings.clone())?;
239

            
240
        // TODO(relay): currently we don't need buffering on this channel,
241
        // but we might need it if we start using it for more than just EXTENDED2 events
242
        #[allow(clippy::disallowed_methods)]
243
64
        let (fwd_ev_tx, fwd_ev_rx) = mpsc::channel(0);
244
64
        let forward = Forward::new(
245
64
            channel,
246
64
            circ_id,
247
64
            unique_id,
248
64
            crypto_out,
249
64
            chan_provider,
250
64
            fwd_ev_tx,
251
64
            memquota.clone(),
252
        );
253
64
        let backward = Backward::new(crypto_in);
254

            
255
64
        let (inner, handle) = BaseReactor::new(
256
64
            runtime,
257
64
            channel,
258
64
            circ_id,
259
64
            unique_id,
260
64
            input,
261
64
            forward,
262
64
            backward,
263
64
            hop_mgr,
264
64
            padding_ctrl,
265
64
            padding_event_stream,
266
64
            stream_rx,
267
64
            fwd_ev_rx,
268
64
            memquota,
269
64
        );
270

            
271
64
        let reactor = Self(inner);
272
64
        let handle = Arc::new(RelayCirc(handle));
273

            
274
        // Note: tunnel is a bit of a misnomer for relays
275
64
        let tunnel = Arc::clone(&handle);
276
        // TODO(relay): this is more or less copy-pasta from client code
277
64
        let stream = incoming_receiver.map(move |req_ctx| {
278
            let StreamReqInfo {
279
16
                req,
280
16
                stream_id,
281
16
                hop,
282
                stream_components:
283
                    ReactorStreamComponents {
284
16
                        stream_inbound_rx,
285
16
                        stream_outbound_tx,
286
16
                        rate_limit_rx,
287
16
                        drain_rate_request_rx,
288
                    },
289
16
                memquota,
290
16
                relay_cell_format,
291
16
            } = req_ctx;
292

            
293
            // There is no originating hop if we're a relay
294
16
            debug_assert!(hop.is_none());
295

            
296
16
            let target = StreamTarget {
297
16
                tunnel: Tunnel::Relay(Arc::clone(&tunnel)),
298
16
                tx: stream_outbound_tx,
299
16
                hop: None,
300
16
                stream_id,
301
16
                relay_cell_format,
302
16
                rate_limit_stream: rate_limit_rx,
303
16
            };
304

            
305
            // can be used to build a reader that supports XON/XOFF flow control
306
16
            let xon_xoff_reader_ctrl =
307
16
                XonXoffReaderCtrl::new(drain_rate_request_rx, target.clone());
308

            
309
16
            let reader = StreamReceiver {
310
16
                target: target.clone(),
311
16
                receiver: stream_inbound_rx,
312
16
                recv_window: StreamRecvWindow::new(RECV_WINDOW_INIT),
313
16
                ended: false,
314
16
            };
315

            
316
16
            let components = StreamComponents {
317
16
                stream_receiver: reader,
318
16
                target,
319
16
                memquota,
320
16
                xon_xoff_reader_ctrl,
321
16
            };
322

            
323
16
            IncomingStream::new(time_provider.clone(), req, components)
324
16
        });
325

            
326
64
        Ok((reactor, handle, stream))
327
64
    }
328

            
329
    /// Launch the reactor, and run until the circuit closes or we
330
    /// encounter an error.
331
    ///
332
    /// Once this method returns, the circuit is dead and cannot be
333
    /// used again.
334
64
    pub(crate) async fn run(mut self) -> crate::Result<()> {
335
64
        self.0.run().await
336
64
    }
337
}
338

            
339
#[cfg(test)]
340
pub(crate) mod test {
341
    // @@ begin test lint list maintained by maint/add_warning @@
342
    #![allow(clippy::bool_assert_comparison)]
343
    #![allow(clippy::clone_on_copy)]
344
    #![allow(clippy::dbg_macro)]
345
    #![allow(clippy::mixed_attributes_style)]
346
    #![allow(clippy::print_stderr)]
347
    #![allow(clippy::print_stdout)]
348
    #![allow(clippy::single_char_pattern)]
349
    #![allow(clippy::unwrap_used)]
350
    #![allow(clippy::unchecked_time_subtraction)]
351
    #![allow(clippy::useless_vec)]
352
    #![allow(clippy::needless_pass_by_value)]
353
    #![allow(clippy::string_slice)] // See arti#2571
354
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
355

            
356
    use super::*;
357
    use crate::channel::test_utils::DummyChan;
358
    use crate::circuit::reactor::test::{AllowAllStreamsFilter, rmsg_to_ccmsg};
359
    use crate::circuit::test::fake_mpsc;
360
    use crate::circuit::{CircParameters, CircuitRxSender};
361
    use crate::client::circuit::padding::new_padding;
362
    use crate::congestion::test_utils::params::build_cc_vegas_params;
363
    use crate::crypto::cell::RelayCellBody;
364
    use crate::crypto::cell::{InboundRelayLayer, OutboundRelayLayer};
365
    use crate::relay::channel::test::DummyChanProvider;
366
    use crate::stream::flow_ctrl::params::FlowCtrlParameters;
367
    use crate::stream::incoming::{IncomingStream, IncomingStreamRequest};
368

            
369
    use futures::AsyncReadExt as _;
370
    use tracing_test::traced_test;
371

            
372
    use tor_cell::chancell::{ChanCell, ChanCmd, msg as chanmsg};
373
    use tor_cell::relaycell::{AnyRelayMsgOuter, RelayCellFormat, StreamId, msg as relaymsg};
374
    use tor_linkspec::{EncodedLinkSpec, HasRelayIds, LinkSpec};
375
    use tor_protover::{Protocols, named};
376
    use tor_rtcompat::SpawnExt;
377
    use tor_rtcompat::{DynTimeProvider, Runtime};
378
    use tor_rtmock::MockRuntime;
379

            
380
    use chanmsg::{AnyChanMsg, Destroy, DestroyReason, HandshakeType};
381
    use relaymsg::SendmeTag;
382

            
383
    use std::net::IpAddr;
384
    use std::sync::{Arc, Mutex, mpsc};
385
    use std::task::{Context, Poll, Waker};
386

            
387
    // An inbound encryption layer that doesn't do any crypto.
388
    struct DummyInboundCrypto {}
389

            
390
    // An outbound encryption layer that doesn't do any crypto.
391
    struct DummyOutboundCrypto {
392
        /// Channel for controlling whether the current cell is meant for us or not.
393
        ///
394
        /// Useful for tests that check if recognized/unrecognized
395
        /// cells are handled/forwarded correctly.
396
        recognized_rx: mpsc::Receiver<Recognized>,
397
    }
398

            
399
    const DUMMY_TAG: [u8; 20] = [1; 20];
400

            
401
    impl InboundRelayLayer for DummyInboundCrypto {
402
        fn originate(&mut self, _cmd: ChanCmd, _cell: &mut RelayCellBody) -> SendmeTag {
403
            DUMMY_TAG.into()
404
        }
405

            
406
        fn encrypt_inbound(&mut self, _cmd: ChanCmd, _cell: &mut RelayCellBody) {}
407
    }
408

            
409
    impl OutboundRelayLayer for DummyOutboundCrypto {
410
        fn decrypt_outbound(
411
            &mut self,
412
            _cmd: ChanCmd,
413
            _cell: &mut RelayCellBody,
414
        ) -> Option<SendmeTag> {
415
            // Note: this should never block.
416
            let recognized = self.recognized_rx.recv().unwrap();
417

            
418
            match recognized {
419
                Recognized::Yes => Some(DUMMY_TAG.into()),
420
                Recognized::No => None,
421
            }
422
        }
423
    }
424

            
425
    struct ReactorTestCtrl {
426
        /// The relay circuit handle.
427
        relay_circ: Arc<RelayCirc>,
428
        /// Mock channel -> circuit reactor MPSC channel.
429
        circmsg_send: CircuitRxSender,
430
        /// The inbound channel ("towards the client").
431
        inbound_chan: DummyChan,
432
        /// The outbound channel ("away from the client"), if any.
433
        ///
434
        /// Shared with the DummyChanProvider, which initializes this
435
        /// when the relay reactor launches a channel to the next hop
436
        /// via `get_or_launch()`.
437
        outbound_chan: Arc<Mutex<Option<DummyChan>>>,
438
        /// MPSC channel for telling the DummyOutboundCrypto that the next
439
        /// cell we're about to send to the reactor should be "recognized".
440
        recognized_tx: mpsc::Sender<Recognized>,
441
    }
442

            
443
    /// Whether a forward cell to send should be "recognized"
444
    /// or "unrecognized" by the relay under test.
445
    enum Recognized {
446
        /// Recognized
447
        Yes,
448
        /// Unrecognized
449
        No,
450
    }
451

            
452
    /// The direction we expect the reactor to have sent a DESTROY in
453
    #[allow(dead_code)] // we don't use all of these yet
454
    enum DestroyDirection {
455
        /// Forward ("towards the exit")
456
        Forward,
457
        /// Backward ("towards the client")
458
        Backward,
459
        /// Both forward and backward
460
        Both,
461
    }
462

            
463
    /// Decode a cell, extracting the underlying message of type `expect_msg`
464
    macro_rules! decode_relay_cell {
465
        ($cell:expr, $expect_msg:tt) => {{
466
            let rmsg = match $cell.msg() {
467
                chanmsg::AnyChanMsg::Relay(r) => AnyRelayMsgOuter::decode_singleton(
468
                    RelayCellFormat::V0,
469
                    r.clone().into_relay_body(),
470
                )
471
                .unwrap(),
472
                msg => panic!("unexpected forwarded {msg:?}"),
473
            };
474

            
475
            let msg = match rmsg.msg() {
476
                relaymsg::AnyRelayMsg::$expect_msg(inner) => inner.clone(),
477
                _ => panic!("unexpected relay message {rmsg:?}"),
478
            };
479

            
480
            (rmsg.stream_id(), msg)
481
        }};
482
    }
483

            
484
    impl ReactorTestCtrl {
485
        /// Spawn a relay circuit reactor, returning a `ReactorTestCtrl` for
486
        /// controlling it.
487
        async fn spawn_reactor<R: Runtime>(
488
            rt: &R,
489
            allowed_stream_cmds: &[RelayCmd],
490
        ) -> (Self, impl futures::Stream<Item = IncomingStream>) {
491
            use crate::channel::CtrlMsg;
492
            use crate::circuit::circ_sender;
493
            use oneshot_fused_workaround as oneshot;
494

            
495
            let inbound_chan = DummyChan::run(rt);
496

            
497
            let memquota = CircuitAccount::new_noop();
498
            let time_provider = DynTimeProvider::new(rt.clone());
499

            
500
            let (sender, receiver) = MpscSpec::new(128)
501
                .new_mq(time_provider, memquota.as_raw_account())
502
                .unwrap();
503
            let (sender, _receiver) = circ_sender::channel(sender, receiver);
504
            let (created_sender, _created_receiver) = oneshot::channel();
505

            
506
            let (tx, rx) = oneshot::channel();
507

            
508
            // Note: we need to make sure the circuit is in the channel reactor's
509
            // circuit map, because otherwise we can't test the DESTROY behavior,
510
            // (the channel reactor conditionally sends DESTROY based on whether
511
            // the circuit entry is still in the circmap or not;
512
            // the presence of a circuit in the circmap is a proxy for
513
            // whether we have sent a DESTROY ourselves or not).
514
            inbound_chan
515
                .channel
516
                .send_control(CtrlMsg::AllocateCircuit {
517
                    created_sender,
518
                    sender,
519
                    tx,
520
                })
521
                .unwrap();
522

            
523
            let (circid, _circ_unique_id, _padding_ctrl, _padding_stream) =
524
                rx.await.unwrap().unwrap();
525

            
526
            let unique_id = UniqId::new(8, 17);
527
            let (padding_ctrl, padding_stream) = new_padding(DynTimeProvider::new(rt.clone()));
528
            let (circmsg_send, circmsg_recv) = fake_mpsc(64);
529
            let params = CircParameters::new(
530
                true,
531
                build_cc_vegas_params(),
532
                FlowCtrlParameters::defaults_for_tests(),
533
            );
534
            let settings = HopSettings::from_params_and_caps(
535
                crate::circuit::circhop::HopNegotiationType::Full,
536
                &params,
537
                &[named::FLOWCTRL_CC].into_iter().collect::<Protocols>(),
538
            )
539
            .unwrap();
540

            
541
            let outbound_chan = Arc::new(Mutex::new(None));
542
            let (recognized_tx, recognized_rx) = mpsc::channel();
543
            let chan_provider = Arc::new(DummyChanProvider::new(
544
                rt.clone(),
545
                Arc::clone(&outbound_chan),
546
            ));
547

            
548
            let (reactor, relay_circ, incoming_streams) = Reactor::new(
549
                rt.clone(),
550
                &Arc::clone(&inbound_chan.channel),
551
                circid,
552
                unique_id,
553
                circmsg_recv,
554
                Box::new(DummyInboundCrypto {}),
555
                Box::new(DummyOutboundCrypto { recognized_rx }),
556
                &settings,
557
                chan_provider,
558
                padding_ctrl,
559
                padding_stream,
560
                Box::new(AllowAllStreamsFilter),
561
                allowed_stream_cmds,
562
                &CircuitAccount::new_noop(),
563
            )
564
            .unwrap();
565

            
566
            rt.spawn(async {
567
                let _ = reactor.run().await;
568
            })
569
            .unwrap();
570

            
571
            let ctrl = Self {
572
                relay_circ,
573
                circmsg_send,
574
                recognized_tx,
575
                inbound_chan,
576
                outbound_chan,
577
            };
578

            
579
            (ctrl, incoming_streams)
580
        }
581

            
582
        /// Simulate the sending of a forward relay message through our relay.
583
        async fn send_fwd(
584
            &mut self,
585
            id: Option<StreamId>,
586
            msg: relaymsg::AnyRelayMsg,
587
            recognized: Recognized,
588
            early: bool,
589
        ) {
590
            // This a bit janky, but for each forward cell we send to the reactor
591
            // we need to send a bit of metadata to the DummyOutboundLayer
592
            // specifying whether the cell should be treated as recognized
593
            // or unrecognized
594
            self.recognized_tx.send(recognized).unwrap();
595
            self.circmsg_send
596
                .send(rmsg_to_ccmsg(id, msg, early))
597
                .await
598
                .unwrap();
599
        }
600

            
601
        /// Simulate the sending of a forward channel message through our relay.
602
        async fn send_fwd_cmsg(&mut self, msg: chanmsg::AnyChanMsg) {
603
            self.circmsg_send.send(msg).await.unwrap();
604
        }
605

            
606
        /// Whether the reactor opened an outbound channel
607
        /// (i.e. a channel to the next relay in the circuit).
608
        fn outbound_chan_launched(&self) -> bool {
609
            self.outbound_chan.lock().unwrap().is_some()
610
        }
611

            
612
        /// Perform the CREATE2 handshake.
613
        async fn do_create2_handshake(
614
            &mut self,
615
            rt: &MockRuntime,
616
            expected_hs_type: HandshakeType,
617
        ) -> Option<CircId> {
618
            // First, check that the reactor actually sent a CREATE2 to the next hop...
619
            let (circid, msg) = self.read_outbound().into_circid_and_msg();
620
            let _create2 = match msg {
621
                chanmsg::AnyChanMsg::Create2(c) => {
622
                    assert_eq!(c.handshake_type(), expected_hs_type);
623
                    c
624
                }
625
                _ => panic!("unexpected forwarded {msg:?}"),
626
            };
627

            
628
            let handshake = vec![];
629
            let created2 = chanmsg::Created2::new(handshake.clone());
630
            // ...and then finalize the handshake by pretending to be
631
            // the responding relay
632
            self.write_outbound(circid, chanmsg::AnyChanMsg::Created2(created2));
633
            rt.advance_until_stalled().await;
634

            
635
            // Make sure we actually did send an EXTENDED2 towards the client
636
            let msg = self.read_inbound();
637

            
638
            let (_sid, e) = decode_relay_cell!(msg, Extended2);
639
            assert_eq!(e.clone().into_body(), handshake);
640

            
641
            circid
642
        }
643

            
644
        /// Whether the circuit is closing (e.g. due to a proto violation).
645
        fn is_closing(&self) -> bool {
646
            self.relay_circ.is_closing()
647
        }
648

            
649
        /// Read a cell from the inbound channel
650
        /// (moving towards the client).
651
        ///
652
        /// See [`try_read_inbound`](Self::try_read_inbound).
653
        ///
654
        /// Panics if there are no ready cells on the inbound MPSC channel.
655
        fn read_inbound(&mut self) -> ChanCell<AnyChanMsg> {
656
            self.try_read_inbound().unwrap()
657
        }
658

            
659
        /// Try to read a cell from the inbound channel
660
        /// (moving towards the client).
661
        ///
662
        /// For example, for a circuit of the form A -> B -> C,
663
        /// where B is the relay whose circuit reactor we're testing,
664
        /// this function reads a channel message on the A <-> B channel,
665
        /// from the perspective of A (i.e. it reads a channel message sent by B).
666
        ///
667
        /// Returns None if there are no ready cells on the inbound MPSC channel.
668
        fn try_read_inbound(&mut self) -> Option<ChanCell<AnyChanMsg>> {
669
            #[allow(deprecated)] // TODO(#2386)
670
            self.inbound_chan.rx.try_next().ok().flatten()
671
        }
672

            
673
        /// Read a cell from the outbound channel
674
        /// (moving towards the next hop).
675
        ///
676
        /// See [`try_read_outbound`](Self::try_read_outbound).
677
        ///
678
        /// Panics if there are no ready cells on the outbound MPSC channel,
679
        /// or if there is no outbound channel.
680
        fn read_outbound(&mut self) -> ChanCell<AnyChanMsg> {
681
            self.try_read_outbound().unwrap()
682
        }
683

            
684
        /// Read a cell from the outbound channel
685
        /// (moving towards the next hop).
686
        ///
687
        /// For example, for a circuit of the form A -> B -> C,
688
        /// where B is the relay whose circuit reactor we're testing,
689
        /// this function reads a channel message on the B <-> C channel,
690
        /// from the perspective of C (i.e. it reads a channel message sent by B).
691
        ///
692
        /// Returns None if there are no ready cells on the outbound MPSC channel,
693
        /// or if there is no outbound channel.
694
        fn try_read_outbound(&mut self) -> Option<ChanCell<AnyChanMsg>> {
695
            let mut lock = self.outbound_chan.lock().unwrap();
696
            let chan = lock.as_mut()?;
697
            #[allow(deprecated)] // TODO(#2386)
698
            chan.rx.try_next().ok().flatten()
699
        }
700

            
701
        /// Write to the sending end of the outbound Tor channel.
702
        ///
703
        /// Simulates the receipt of a cell from the next hop.
704
        ///
705
        /// Panics if the outbound chan sender is full.
706
        fn write_outbound(&mut self, circid: Option<CircId>, msg: chanmsg::AnyChanMsg) {
707
            let mut lock = self.outbound_chan.lock().unwrap();
708
            let chan = lock.as_mut().unwrap();
709
            let cell = ChanCell::new(circid, msg);
710

            
711
            chan.tx.try_send(Ok(cell)).unwrap();
712
        }
713
    }
714

            
715
    fn dummy_linkspecs() -> Vec<EncodedLinkSpec> {
716
        vec![
717
            LinkSpec::Ed25519Id([43; 32].into()).encode().unwrap(),
718
            LinkSpec::RsaId([45; 20].into()).encode().unwrap(),
719
            LinkSpec::OrPort("127.0.0.1".parse::<IpAddr>().unwrap(), 999)
720
                .encode()
721
                .unwrap(),
722
        ]
723
    }
724

            
725
    macro_rules! assert_cell_is_destroy {
726
        ($cell:expr, $reason:expr) => {{
727
            match $cell.msg() {
728
                chanmsg::AnyChanMsg::Destroy(d) => {
729
                    assert_eq!(d.reason(), $reason);
730
                }
731
                _ => panic!("unexpected ending {:?}", $cell),
732
            }
733
        }};
734
    }
735

            
736
    /// Assert that we have sent a DESTROY cell with the specified `reason`
737
    /// towards the "client" and/or the "next hop".
738
    ///
739
    /// The test is expected to drain the inbound Tor "channel"
740
    /// of any non-ending cells it might be expecting before calling this function.
741
    fn assert_destroy_sent(
742
        ctrl: &mut ReactorTestCtrl,
743
        reason: DestroyReason,
744
        direction: DestroyDirection,
745
    ) {
746
        assert!(ctrl.is_closing());
747

            
748
        match direction {
749
            DestroyDirection::Backward => {
750
                assert_cell_is_destroy!(ctrl.read_inbound(), reason);
751
                assert!(ctrl.try_read_outbound().is_none());
752
            }
753
            DestroyDirection::Forward => {
754
                assert_cell_is_destroy!(ctrl.read_outbound(), reason);
755
                assert!(ctrl.try_read_inbound().is_none());
756
            }
757
            DestroyDirection::Both => {
758
                assert_cell_is_destroy!(ctrl.read_inbound(), reason);
759
                assert_cell_is_destroy!(ctrl.read_outbound(), reason);
760
            }
761
        }
762
    }
763

            
764
    macro_rules! expect_cell {
765
        ($cell:expr, $chanmsg:tt, $relaymsg:tt) => {{
766
            let msg = match $cell.msg() {
767
                chanmsg::AnyChanMsg::$chanmsg(m) => {
768
                    let body = m.clone().into_relay_body();
769
                    AnyRelayMsgOuter::decode_singleton(RelayCellFormat::V0, body).unwrap()
770
                }
771
                _ => panic!("unexpected forwarded {:?}", $cell),
772
            };
773

            
774
            match msg.msg() {
775
                relaymsg::AnyRelayMsg::$relaymsg(m) => m.clone(),
776
                _ => panic!("unexpected cell {msg:?}"),
777
            }
778
        }};
779
    }
780

            
781
    #[traced_test]
782
    #[test]
783
    fn reject_extend2_relay() {
784
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
785
            let (mut ctrl, _incoming_streams) =
786
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
787
            rt.advance_until_stalled().await;
788

            
789
            let linkspecs = dummy_linkspecs();
790
            let extend2 = relaymsg::Extend2::new(linkspecs, HandshakeType::NTOR_V3, vec![]).into();
791
            ctrl.send_fwd(None, extend2, Recognized::Yes, false).await;
792
            rt.advance_until_stalled().await;
793

            
794
            assert!(logs_contain("got EXTEND2 in a RELAY cell?!"));
795
            assert!(!ctrl.outbound_chan_launched());
796

            
797
            // There is no next hop because we haven't extended the circuit,
798
            // so only expect the DESTROY to be sent toward the client (Backward).
799
            assert_destroy_sent(&mut ctrl, DestroyReason::NONE, DestroyDirection::Backward);
800
        });
801
    }
802

            
803
    #[traced_test]
804
    #[test]
805
    fn reject_extend2_previous_hop() {
806
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
807
            let (mut ctrl, _incoming_streams) =
808
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
809
            rt.advance_until_stalled().await;
810

            
811
            // No outbound circuits yet
812
            assert!(!ctrl.outbound_chan_launched());
813

            
814
            // Build a linkspec with the identities of the dummy channel
815
            let mut linkspecs = ctrl
816
                .inbound_chan
817
                .channel
818
                .target()
819
                .identities()
820
                .map(|id| LinkSpec::from(id.to_owned()).encode())
821
                .collect::<Result<Vec<_>, _>>()
822
                .unwrap();
823

            
824
            // Make sure this channel actually has some identities
825
            // (i.e. that it's not a client channel or something)
826
            assert_eq!(linkspecs.len(), 2);
827

            
828
            // There must be at least one IPv4 OR port address
829
            linkspecs.push(
830
                LinkSpec::OrPort("127.0.0.1".parse::<IpAddr>().unwrap(), 999)
831
                    .encode()
832
                    .unwrap(),
833
            );
834
            let handshake_type = HandshakeType::NTOR_V3;
835
            let extend2 = relaymsg::Extend2::new(linkspecs, handshake_type, vec![]).into();
836
            ctrl.send_fwd(None, extend2, Recognized::Yes, true).await;
837
            rt.advance_until_stalled().await;
838

            
839
            // The reactor handled the EXTEND2 and launched an outbound channel
840
            assert!(logs_contain("Cannot extend circuit to previous hop"));
841
            assert!(!ctrl.outbound_chan_launched());
842
            assert!(ctrl.is_closing());
843
        });
844
    }
845

            
846
    #[traced_test]
847
    #[test]
848
    fn extend_and_forward() {
849
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
850
            let (mut ctrl, _incoming_streams) =
851
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
852
            rt.advance_until_stalled().await;
853

            
854
            // No outbound circuits yet
855
            assert!(!ctrl.outbound_chan_launched());
856

            
857
            let linkspecs = dummy_linkspecs();
858
            let handshake_type = HandshakeType::NTOR_V3;
859
            let extend2 = relaymsg::Extend2::new(linkspecs, handshake_type, vec![]).into();
860
            ctrl.send_fwd(None, extend2, Recognized::Yes, true).await;
861
            rt.advance_until_stalled().await;
862

            
863
            // The reactor handled the EXTEND2 and launched an outbound channel
864
            assert!(logs_contain(
865
                "Launched channel to the next hop circ_uniq_id=Circ 8.17"
866
            ));
867
            assert!(ctrl.outbound_chan_launched());
868
            assert!(!ctrl.is_closing());
869

            
870
            let _circid = ctrl.do_create2_handshake(&rt, handshake_type).await;
871
            assert!(logs_contain("Got CREATED2 response from next hop"));
872
            assert!(logs_contain("Extended circuit to the next hop"));
873

            
874
            // Time to forward a message to the next hop!
875
            let early = false;
876
            let begin = relaymsg::Begin::new("127.0.0.1", 1111, 0).unwrap();
877
            ctrl.send_fwd(None, begin.clone().into(), Recognized::No, early)
878
                .await;
879
            rt.advance_until_stalled().await;
880

            
881
            // Ensure the other end received the BEGIN cell
882
            let cell = ctrl.read_outbound();
883
            let recvd_begin = expect_cell!(cell, Relay, Begin);
884
            assert_eq!(begin, recvd_begin);
885

            
886
            // Now send the same message again, but this time in a RELAY_EARLY
887
            let early = true;
888
            let begin = relaymsg::Begin::new("127.0.0.1", 1111, 0).unwrap();
889
            ctrl.send_fwd(None, begin.clone().into(), Recognized::No, early)
890
                .await;
891
            rt.advance_until_stalled().await;
892
            let cell = ctrl.read_outbound();
893
            let recvd_begin = expect_cell!(cell, RelayEarly, Begin);
894
            assert_eq!(begin, recvd_begin);
895
        });
896
    }
897

            
898
    #[traced_test]
899
    #[test]
900
    fn forward_before_extend() {
901
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
902
            let (mut ctrl, _incoming_streams) =
903
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
904
            rt.advance_until_stalled().await;
905

            
906
            // Send an arbitrary unrecognized cell. The reactor should flag this as
907
            // a protocol violation, because we don't have an outbound channel to forward it on.
908
            let end = relaymsg::End::new_misc().into();
909
            ctrl.send_fwd(None, end, Recognized::No, true).await;
910
            rt.advance_until_stalled().await;
911

            
912
            assert!(logs_contain(
913
                "Asked to forward cell before the circuit was extended?!"
914
            ));
915

            
916
            // There is no next hop because we haven't extended the circuit,
917
            // so only expect the DESTROY to be sent toward the client (Backward).
918
            assert_destroy_sent(&mut ctrl, DestroyReason::NONE, DestroyDirection::Backward);
919
        });
920
    }
921

            
922
    #[traced_test]
923
    #[test]
924
    fn reject_invalid_begin() {
925
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
926
            let (mut ctrl, _incoming_streams) =
927
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
928
            rt.advance_until_stalled().await;
929

            
930
            let begin = relaymsg::Begin::new("127.0.0.1", 1111, 0).unwrap().into();
931

            
932
            // BEGIN cells *must* have a stream ID, so expect the reactor to reject this
933
            // and close the circuit
934
            ctrl.send_fwd(None, begin, Recognized::Yes, false).await;
935
            rt.advance_until_stalled().await;
936

            
937
            assert!(logs_contain(
938
                "Invalid stream ID [scrubbed] for relay command BEGIN"
939
            ));
940

            
941
            // There is no next hop because we haven't extended the circuit,
942
            // so only expect the DESTROY to be sent toward the client (Backward).
943
            assert_destroy_sent(&mut ctrl, DestroyReason::NONE, DestroyDirection::Backward);
944
        });
945
    }
946

            
947
    #[traced_test]
948
    #[test]
949
    fn destroy_from_client() {
950
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
951
            let (mut ctrl, _incoming_streams) =
952
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
953
            rt.advance_until_stalled().await;
954

            
955
            // Extend the circuit by another hop
956
            let linkspecs = dummy_linkspecs();
957
            let handshake_type = HandshakeType::NTOR_V3;
958
            let extend2 = relaymsg::Extend2::new(linkspecs, handshake_type, vec![]).into();
959
            ctrl.send_fwd(None, extend2, Recognized::Yes, true).await;
960
            rt.advance_until_stalled().await;
961
            let _circid = ctrl.do_create2_handshake(&rt, handshake_type).await;
962
            assert!(logs_contain("Extended circuit to the next hop"));
963
            assert!(ctrl.outbound_chan_launched());
964

            
965
            // Simulate the client sending us a DESTROY cell
966
            let destroy = Destroy::new(DestroyReason::PROTOCOL);
967
            ctrl.send_fwd_cmsg(destroy.into()).await;
968
            rt.advance_until_stalled().await;
969

            
970
            assert!(logs_contain(
971
                "Received outbound DESTROY, circuit shutting down"
972
            ));
973

            
974
            // If we received a DESTROY, we shouldn't send one back.
975
            // However, in this test, the reactor does in fact send a DESTROY
976
            // back to the mock "client", because the DESTROY we "received" from
977
            // the it was sent via a mock channel -> circuit reactor MPSC,
978
            // instead of going through the channel reactor like it would normally.
979
            // Because of this, the channel reactor doesn't get a chance to actually
980
            // remove the circuit from the circmap, which would normally suppress
981
            // the *sending* of a DESTROY on drop.
982
            //
983
            // TODO(relay): we need to update the test harness here to replace
984
            // the circmsg_send/circmsg_recv MPSC with an MPSC that is actually
985
            // connected to the channel reactor
986
            //assert!(!logs_contain("sending DESTROY"));
987
            //assert_destroy_sent(&mut ctrl, DestroyReason::NONE, DestroyDirection::Backward);
988

            
989
            // Since this is a circuit of the form A -> B -> C,
990
            // and A sent us a DESTROY, we expect our relay (B) to forward
991
            // the DESTROY to C.
992
            assert_cell_is_destroy!(ctrl.read_outbound(), DestroyReason::NONE);
993
        });
994
    }
995

            
996
    #[traced_test]
997
    #[test]
998
    fn destroy_from_next_hop() {
999
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
            let (mut ctrl, _incoming_streams) =
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
            rt.advance_until_stalled().await;
            // Extend the circuit by another hop
            let linkspecs = dummy_linkspecs();
            let handshake_type = HandshakeType::NTOR_V3;
            let extend2 = relaymsg::Extend2::new(linkspecs, handshake_type, vec![]).into();
            ctrl.send_fwd(None, extend2, Recognized::Yes, true).await;
            rt.advance_until_stalled().await;
            let circid = ctrl.do_create2_handshake(&rt, handshake_type).await;
            assert!(logs_contain("Extended circuit to the next hop"));
            assert!(ctrl.outbound_chan_launched());
            // Simulate the next hop sending us a DESTROY cell
            let destroy = Destroy::new(DestroyReason::PROTOCOL);
            ctrl.write_outbound(circid, destroy.into());
            rt.advance_until_stalled().await;
            // We have *not* received an outbound destroy
            assert!(!logs_contain(
                "Received outbound DESTROY, circuit shutting down"
            ));
            // We received an inbound one (from the next hop)
            assert!(logs_contain(
                "Received inbound DESTROY, circuit shutting down"
            ));
            // There is no next hop because we haven't extended the circuit,
            // so only expect the DESTROY to be sent toward the client (Backward).
            // This also ensures the destroy reason (PROTOCOL) is not propagated.
            assert_destroy_sent(&mut ctrl, DestroyReason::NONE, DestroyDirection::Backward);
        });
    }
    #[traced_test]
    #[test]
    fn truncate() {
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
            let (mut ctrl, _incoming_streams) =
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
            rt.advance_until_stalled().await;
            // Simulate the client sending us a TRUNCATE cell
            let truncate = relaymsg::Truncate::default().into();
            ctrl.send_fwd(None, truncate, Recognized::Yes, false).await;
            rt.advance_until_stalled().await;
            assert!(logs_contain(
                "Circuit protocol violation: TRUNCATE not allowed"
            ));
            // There is no next hop because we haven't extended the circuit,
            // so only expect the DESTROY to be sent toward the client (Backward).
            assert_destroy_sent(&mut ctrl, DestroyReason::NONE, DestroyDirection::Backward);
        });
    }
    #[traced_test]
    #[test]
    fn data_stream() {
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
            const TO_SEND: &[u8] = b"The bells were musical in the silvery sun";
            let (mut ctrl, mut incoming_streams) =
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
            rt.advance_until_stalled().await;
            let begin = relaymsg::Begin::new("127.0.0.1", 1111, 0).unwrap().into();
            ctrl.send_fwd(StreamId::new(1), begin, Recognized::Yes, false)
                .await;
            rt.advance_until_stalled().await;
            let data = relaymsg::Data::new(TO_SEND).unwrap().into();
            ctrl.send_fwd(StreamId::new(1), data, Recognized::Yes, false)
                .await;
            // We should have a pending incoming stream
            let pending = incoming_streams.next().await.unwrap();
            // Accept it, and let's see what we have!
            let mut stream = pending
                .accept_data(relaymsg::Connected::new_empty())
                .await
                .unwrap();
            let mut recv_buf = [0_u8; TO_SEND.len()];
            stream.read_exact(&mut recv_buf).await.unwrap();
            assert_eq!(recv_buf, TO_SEND);
        });
    }
    #[traced_test]
    #[test]
    fn reject_stream() {
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
            let (mut ctrl, mut incoming_streams) =
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::BEGIN]).await;
            rt.advance_until_stalled().await;
            let begin = relaymsg::Begin::new("127.0.0.1", 1111, 0).unwrap().into();
            ctrl.send_fwd(StreamId::new(1), begin, Recognized::Yes, false)
                .await;
            rt.advance_until_stalled().await;
            // We should have a pending incoming stream
            let pending = incoming_streams.next().await.unwrap();
            // Reject the stream, and wait for the reactor to finish sending the END
            let end = relaymsg::End::new_misc();
            pending.reject(end.clone()).await.unwrap();
            rt.advance_until_stalled().await;
            // The END cell written to the Tor channel should be the same as
            // the one we sent above, in reject().
            let cell = ctrl.read_inbound();
            let actual_end = expect_cell!(cell, Relay, End);
            assert_eq!(end.reason(), actual_end.reason());
            // Sending another message on this stream results is flagged
            // as a proto violation
            let data = relaymsg::Data::new(b"no dice").unwrap().into();
            ctrl.send_fwd(StreamId::new(1), data, Recognized::Yes, false)
                .await;
            rt.advance_until_stalled().await;
            assert!(logs_contain("Stream protocol violation"));
            assert!(logs_contain(
                "Unexpected RelayCmd(DATA) message on unknown stream 1"
            ));
        });
    }
    #[traced_test]
    #[test]
    fn only_allow_begin_dir() {
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
            let (mut ctrl, mut incoming_streams) = ReactorTestCtrl::spawn_reactor(
                &rt,
                // The stream reactor will only accept BEGIN_DIR streams
                &[RelayCmd::BEGIN_DIR],
            )
            .await;
            rt.advance_until_stalled().await;
            // Directory streams should be allowed (because BEGIN_DIR is allowed)...
            let begin_dir = relaymsg::BeginDir::default().into();
            ctrl.send_fwd(StreamId::new(1), begin_dir, Recognized::Yes, false)
                .await;
            rt.advance_until_stalled().await;
            let pending_dir_stream = incoming_streams.next().await.unwrap();
            assert!(matches!(
                pending_dir_stream.request(),
                IncomingStreamRequest::BeginDir(_)
            ));
            let begin = relaymsg::Begin::new("127.0.0.1", 1111, 0).unwrap().into();
            ctrl.send_fwd(StreamId::new(2), begin, Recognized::Yes, false)
                .await;
            rt.advance_until_stalled().await;
            // ... but the exit stream is not
            assert!(logs_contain("stream reactor shut down"));
            assert!(logs_contain(
                "Stream protocol violation: Unexpected BEGIN on incoming stream circ_uniq_id=Circ 8.17"
            ));
            // The reactor won't create an IncomingStream,
            // because the stream request is rejected right away
            let mut noop_cx = Context::from_waker(Waker::noop());
            assert_eq!(
                incoming_streams.poll_next_unpin(&mut noop_cx).map(|_| ()),
                Poll::Pending
            );
        });
    }
    #[traced_test]
    #[test]
    fn resolve_stream() {
        tor_rtmock::MockRuntime::test_with_various(|rt| async move {
            let (mut ctrl, mut incoming_streams) =
                ReactorTestCtrl::spawn_reactor(&rt, &[RelayCmd::RESOLVE]).await;
            rt.advance_until_stalled().await;
            let resolve = relaymsg::Resolve::new("example.com");
            let resolve_sid = StreamId::new(1337);
            ctrl.send_fwd(resolve_sid, resolve.into(), Recognized::Yes, false)
                .await;
            rt.advance_until_stalled().await;
            // We should have a pending incoming stream
            let pending = incoming_streams.next().await.unwrap();
            let mut resolved = relaymsg::Resolved::new_empty();
            let resolved_val = relaymsg::ResolvedVal::Ip(IpAddr::from([1, 2, 3, 4]));
            resolved.add_answer(resolved_val.clone(), 1337);
            // We expect the client to receive this cell
            let expected_resolved = resolved.clone();
            // Respond with RESOLVED
            pending.resolve(resolved).await.unwrap();
            rt.advance_until_stalled().await;
            let (sid, resolved) = decode_relay_cell!(ctrl.read_inbound(), Resolved);
            // Make sure the RESOLVED cell sent towards the client
            // matches what we sent via the IncomingStream::resolve() call above
            assert_eq!(resolved.into_answers(), expected_resolved.into_answers());
            assert_eq!(sid, resolve_sid);
            assert!(logs_contain("Ending stream"));
        });
    }
}