1
//! Conflux-related functionality
2

            
3
// TODO: replace Itertools::exactly_one() with a stdlib equivalent when there is one.
4
//
5
// See issue #48919 <https://github.com/rust-lang/rust/issues/48919>
6
#![allow(unstable_name_collisions)]
7

            
8
#[cfg(feature = "conflux")]
9
pub(crate) mod msghandler;
10

            
11
use std::pin::Pin;
12
use std::sync::atomic::{self, AtomicU64};
13
use std::sync::{Arc, Mutex};
14

            
15
use futures::{FutureExt as _, StreamExt, select_biased};
16
use itertools::Itertools;
17
use itertools::structs::ExactlyOneError;
18
use smallvec::{SmallVec, smallvec};
19
use tor_rtcompat::{SleepProvider as _, SleepProviderExt as _};
20
use tracing::{info, instrument, trace, warn};
21

            
22
use tor_cell::relaycell::AnyRelayMsgOuter;
23
use tor_error::{Bug, bad_api_usage, internal};
24
use tor_linkspec::HasRelayIds as _;
25

            
26
use crate::circuit::UniqId;
27
use crate::circuit::circhop::SendRelayCell;
28
use crate::client::circuit::TunnelMutableState;
29
#[cfg(feature = "circ-padding")]
30
use crate::client::circuit::padding::PaddingEvent;
31
use crate::client::circuit::path::HopDetail;
32
use crate::conflux::cmd_counts_towards_seqno;
33
use crate::conflux::msghandler::{ConfluxStatus, RemoveLegReason};
34
use crate::congestion::params::CongestionWindowParams;
35
use crate::crypto::cell::HopNum;
36
use crate::streammap;
37
use crate::tunnel::TunnelId;
38
use crate::util::err::ReactorError;
39
use crate::util::poll_all::PollAll;
40
use crate::util::tunnel_activity::TunnelActivity;
41

            
42
use super::circuit::CircHop;
43
use super::{Circuit, CircuitEvent};
44

            
45
#[cfg(feature = "conflux")]
46
use {
47
    crate::conflux::msghandler::ConfluxMsgHandler,
48
    msghandler::ClientConfluxMsgHandler,
49
    tor_cell::relaycell::conflux::{V1DesiredUx, V1LinkPayload, V1Nonce},
50
    tor_cell::relaycell::msg::{ConfluxLink, ConfluxSwitch},
51
};
52

            
53
/// The maximum number of conflux legs to store in the conflux set SmallVec.
54
///
55
/// Attempting to store more legs will cause the SmallVec to spill to the heap.
56
///
57
/// Note: this value was picked arbitrarily and may not be suitable.
58
const MAX_CONFLUX_LEGS: usize = 16;
59

            
60
/// The number of futures we add to the per-circuit [`PollAll`] future in
61
/// [`ConfluxSet::next_circ_event`].
62
///
63
/// Used for the SmallVec size estimate;
64
const NUM_CIRC_FUTURES: usize = 2;
65

            
66
/// The expected number of circuit events to be returned from
67
/// [`ConfluxSet::next_circ_event`]
68
const CIRC_EVENT_COUNT: usize = MAX_CONFLUX_LEGS * NUM_CIRC_FUTURES;
69

            
70
/// A set with one or more circuits.
71
///
72
/// ### Conflux set life cycle
73
///
74
/// Conflux sets are created by the reactor using [`ConfluxSet::new`].
75
///
76
/// Every `ConfluxSet` starts out as a single-path set consisting of a single 0-length circuit.
77
///
78
/// After constructing a `ConfluxSet`, the reactor will proceed to extend its (only) circuit.
79
/// At this point, the `ConfluxSet` will be a single-path set with a single n-length circuit.
80
///
81
/// The reactor can then turn the `ConfluxSet` into a multi-path set
82
/// (a multi-path set is a conflux set that contains more than 1 circuit).
83
/// This is done using [`ConfluxSet::add_legs`], in response to a `CtrlMsg` sent
84
/// by the reactor user (also referred to as the "conflux handshake initiator").
85
/// After that, the conflux set is said to be a multi-path set with multiple N-length circuits.
86
///
87
/// Circuits can be removed from the set using [`ConfluxSet::remove`].
88
///
89
/// The lifetime of a `ConfluxSet` is tied to the lifetime of the reactor.
90
/// When the reactor is dropped, its underlying `ConfluxSet` is dropped too.
91
/// This can happen on an explicit shutdown request, or if a fatal error occurs.
92
///
93
/// Conversely, the `ConfluxSet` can also trigger a reactor shutdown.
94
/// For example, if after being instructed to remove a circuit from the set
95
/// using [`ConfluxSet::remove`], the set is completely depleted,
96
/// the `ConfluxSet` will return a [`ReactorError::Shutdown`] error,
97
/// which will cause the reactor to shut down.
98
pub(super) struct ConfluxSet {
99
    /// The unique identifier of the tunnel this conflux set belongs to.
100
    ///
101
    /// Used for setting the internal [`TunnelId`] of [`Circuit`]s
102
    /// that gets used for logging purposes.
103
    tunnel_id: TunnelId,
104
    /// The circuits in this conflux set.
105
    legs: SmallVec<[Circuit; MAX_CONFLUX_LEGS]>,
106
    /// Tunnel state, shared with `ClientCirc`.
107
    ///
108
    /// Contains the [`MutableState`](super::MutableState) of each circuit in the set.
109
    mutable: Arc<TunnelMutableState>,
110
    /// The unique identifier of the primary leg
111
    primary_id: UniqId,
112
    /// The join point of the set, if this is a multi-path set.
113
    ///
114
    /// Initially the conflux set starts out as a single-path set with no join point.
115
    /// When it is converted to a multipath set using [`add_legs`](Self::add_legs),
116
    /// the join point is initialized to the last hop in the tunnel.
117
    //
118
    // TODO(#2017): for simplicity, we currently we force all legs to have the same length,
119
    // to ensure the HopNum of the join point is the same for all of them.
120
    //
121
    // In the future we might want to relax this restriction.
122
    join_point: Option<JoinPoint>,
123
    /// The nonce associated with the circuits from this set.
124
    #[cfg(feature = "conflux")]
125
    nonce: V1Nonce,
126
    /// The desired UX
127
    #[cfg(feature = "conflux")]
128
    desired_ux: V1DesiredUx,
129
    /// The absolute sequence number of the last cell delivered to a stream.
130
    ///
131
    /// A clone of this is shared with each [`ConfluxMsgHandler`] created.
132
    ///
133
    /// When a message is received on a circuit leg, the `ConfluxMsgHandler`
134
    /// of the leg compares the (leg-local) sequence number of the message
135
    /// with this sequence number to determine whether the message is in-order.
136
    ///
137
    /// If the message is in-order, the `ConfluxMsgHandler` instructs the circuit
138
    /// to deliver it to its corresponding stream.
139
    ///
140
    /// If the message is out-of-order, the `ConfluxMsgHandler` instructs the circuit
141
    /// to instruct the reactor to buffer the message.
142
    last_seq_delivered: Arc<AtomicU64>,
143
    /// Whether we have selected our initial primary leg,
144
    /// if this is a multipath conflux set.
145
    selected_init_primary: bool,
146
}
147

            
148
/// The conflux join point.
149
#[derive(Clone, derive_more::Debug)]
150
struct JoinPoint {
151
    /// The hop number.
152
    hop: HopNum,
153
    /// The [`HopDetail`] of the hop.
154
    detail: HopDetail,
155
    /// The stream map of the joint point, shared with each circuit leg.
156
    #[debug(skip)]
157
    streams: Arc<Mutex<streammap::StreamMap>>,
158
}
159

            
160
impl ConfluxSet {
161
    /// Create a new conflux set, consisting of a single leg.
162
    ///
163
    /// Returns the newly created set and a reference to its [`TunnelMutableState`].
164
406
    pub(super) fn new(
165
406
        tunnel_id: TunnelId,
166
406
        circuit_leg: Circuit,
167
406
    ) -> (Self, Arc<TunnelMutableState>) {
168
406
        let primary_id = circuit_leg.unique_id();
169
406
        let circ_mutable = Arc::clone(circuit_leg.mutable());
170
406
        let legs = smallvec![circuit_leg];
171
        // Note: the join point is only set for multi-path tunnels
172
406
        let join_point = None;
173

            
174
        // TODO(#2035): read this from the consensus/config.
175
        #[cfg(feature = "conflux")]
176
406
        let desired_ux = V1DesiredUx::NO_OPINION;
177

            
178
406
        let mutable = Arc::new(TunnelMutableState::default());
179
406
        mutable.insert(primary_id, circ_mutable);
180

            
181
406
        let set = Self {
182
406
            tunnel_id,
183
406
            legs,
184
406
            primary_id,
185
406
            join_point,
186
406
            mutable: mutable.clone(),
187
406
            #[cfg(feature = "conflux")]
188
406
            nonce: V1Nonce::new(&mut rand::rng()),
189
406
            #[cfg(feature = "conflux")]
190
406
            desired_ux,
191
406
            last_seq_delivered: Arc::new(AtomicU64::new(0)),
192
406
            selected_init_primary: false,
193
406
        };
194

            
195
406
        (set, mutable)
196
406
    }
197

            
198
    /// Remove and return the only leg of this conflux set.
199
    ///
200
    /// Returns an error if there is more than one leg in the set,
201
    /// or if called before any circuit legs are available.
202
    ///
203
    /// Calling this function will empty the [`ConfluxSet`].
204
64
    pub(super) fn take_single_leg(&mut self) -> Result<Circuit, Bug> {
205
64
        let circ = self
206
64
            .legs
207
64
            .iter()
208
64
            .exactly_one()
209
64
            .map_err(NotSingleLegError::from)?;
210
60
        let circ_id = circ.unique_id();
211

            
212
60
        debug_assert!(circ_id == self.primary_id);
213

            
214
60
        self.remove_unchecked(circ_id)
215
64
    }
216

            
217
    /// Return a reference to the only leg of this conflux set,
218
    /// along with the leg's ID.
219
    ///
220
    /// Returns an error if there is more than one leg in the set,
221
    /// or if called before any circuit legs are available.
222
144
    pub(super) fn single_leg(&self) -> Result<&Circuit, NotSingleLegError> {
223
144
        Ok(self.legs.iter().exactly_one()?)
224
144
    }
225

            
226
    /// Return a mutable reference to the only leg of this conflux set,
227
    /// along with the leg's ID.
228
    ///
229
    /// Returns an error if there is more than one leg in the set,
230
    /// or if called before any circuit legs are available.
231
7494
    pub(super) fn single_leg_mut(&mut self) -> Result<&mut Circuit, NotSingleLegError> {
232
7494
        Ok(self.legs.iter_mut().exactly_one()?)
233
7494
    }
234

            
235
    /// Return the primary leg of this conflux set.
236
    ///
237
    /// Returns an error if called before any circuit legs are available.
238
32
    pub(super) fn primary_leg_mut(&mut self) -> Result<&mut Circuit, Bug> {
239
        #[cfg(not(feature = "conflux"))]
240
        if self.legs.len() > 1 {
241
            return Err(internal!(
242
                "got multipath tunnel, but conflux feature is disabled?!"
243
            ));
244
        }
245

            
246
32
        if self.legs.is_empty() {
247
            Err(bad_api_usage!(
248
                "tried to get circuit leg before creating it?!"
249
            ))
250
        } else {
251
32
            let circ = self
252
32
                .leg_mut(self.primary_id)
253
32
                .ok_or_else(|| internal!("conflux set is empty?!"))?;
254

            
255
32
            Ok(circ)
256
        }
257
32
    }
258

            
259
    /// Return a reference to the leg of this conflux set with the given id.
260
1506
    pub(super) fn leg(&self, leg_id: UniqId) -> Option<&Circuit> {
261
2775
        self.legs.iter().find(|circ| circ.unique_id() == leg_id)
262
1506
    }
263

            
264
    /// Return a mutable reference to the leg of this conflux set with the given id.
265
5292
    pub(super) fn leg_mut(&mut self, leg_id: UniqId) -> Option<&mut Circuit> {
266
8614
        self.legs.iter_mut().find(|circ| circ.unique_id() == leg_id)
267
5292
    }
268

            
269
    /// Return the number of legs in this conflux set.
270
    pub(super) fn len(&self) -> usize {
271
        self.legs.len()
272
    }
273

            
274
    /// Return whether this conflux set is empty.
275
6408
    pub(super) fn is_empty(&self) -> bool {
276
6408
        self.legs.len() == 0
277
6408
    }
278

            
279
    /// Remove the specified leg from this conflux set.
280
    ///
281
    /// Returns an error if the given leg doesn't exist in the set.
282
    ///
283
    /// Returns an error instructing the reactor to perform a clean shutdown
284
    /// ([`ReactorError::Shutdown`]), tearing down the entire [`ConfluxSet`], if
285
    ///
286
    ///   * the set is depleted (empty) after removing the specified leg
287
    ///   * `leg` is currently the sending (primary) leg of this set
288
    ///   * the closed leg had the highest non-zero last_seq_recv/sent
289
    ///   * the closed leg had some in-progress data (inflight > cc_sendme_inc)
290
    ///
291
    /// We do not yet support resumption. See [2.4.3. Closing circuits] in prop329.
292
    ///
293
    /// [2.4.3. Closing circuits]: https://spec.torproject.org/proposals/329-traffic-splitting.html#243-closing-circuits
294
    #[instrument(level = "trace", skip_all)]
295
52
    pub(super) fn remove(&mut self, leg: UniqId) -> Result<Circuit, ReactorError> {
296
52
        let circ = self.remove_unchecked(leg)?;
297

            
298
52
        tracing::trace!(
299
            circ_uniq_id = %circ.unique_id(),
300
            forward_circ_id = %circ.circ_id(),
301
            "Circuit removed from conflux set"
302
        );
303

            
304
52
        self.mutable.remove(circ.unique_id());
305

            
306
52
        if self.legs.is_empty() {
307
            // TODO: log the tunnel ID
308
24
            tracing::debug!("Conflux set is now empty, tunnel reactor shutting down");
309

            
310
            // The last circuit in the set has just died, so the reactor should exit.
311
24
            return Err(ReactorError::Shutdown);
312
28
        }
313

            
314
28
        if leg == self.primary_id {
315
            // We have just removed our sending leg,
316
            // so it's time to close the entire conflux set.
317
12
            return Err(ReactorError::Shutdown);
318
16
        }
319

            
320
        cfg_if::cfg_if! {
321
            if #[cfg(feature = "conflux")] {
322
16
                self.remove_conflux(circ)
323
            } else {
324
                // Conflux is disabled, so we can't possibly continue running if the only
325
                // leg in the tunnel is gone.
326
                //
327
                // Technically this should be unreachable (because of the is_empty()
328
                // check above)
329
                Err(internal!("Multiple legs in single-path tunnel?!").into())
330
            }
331
        }
332
52
    }
333

            
334
    /// Handle the removal of a circuit,
335
    /// returning an error if the reactor needs to shut down.
336
    #[cfg(feature = "conflux")]
337
16
    fn remove_conflux(&self, circ: Circuit) -> Result<Circuit, ReactorError> {
338
16
        let Some(status) = circ.conflux_status() else {
339
            return Err(internal!("Found non-conflux circuit in conflux set?!").into());
340
        };
341

            
342
        // TODO(conflux): should the circmgr be notified about the leg removal?
343
        //
344
        // "For circuits that are unlinked, the origin SHOULD immediately relaunch a new leg when it
345
        // is closed, subject to the limits in [SIDE_CHANNELS]."
346

            
347
        // If we've reached this point and the conflux set is non-empty,
348
        // it means it's a multi-path set.
349
        //
350
        // Time to check if we need to tear down the entire set.
351
16
        match status {
352
            ConfluxStatus::Unlinked => {
353
                // This circuit hasn't yet begun the conflux handshake,
354
                // so we can safely remove it from the set
355
                Ok(circ)
356
            }
357
            ConfluxStatus::Pending | ConfluxStatus::Linked => {
358
16
                let (circ_last_seq_recv, circ_last_seq_sent) =
359
16
                    (|| Ok::<_, ReactorError>((circ.last_seq_recv()?, circ.last_seq_sent()?)))()?;
360

            
361
                // If the closed leg had the highest non-zero last_seq_recv/sent, close the set
362
16
                if let Some(max_last_seq_recv) = self.max_last_seq_recv() {
363
16
                    if circ_last_seq_recv > max_last_seq_recv {
364
                        return Err(ReactorError::Shutdown);
365
16
                    }
366
                }
367

            
368
16
                if let Some(max_last_seq_sent) = self.max_last_seq_sent() {
369
16
                    if circ_last_seq_sent > max_last_seq_sent {
370
                        return Err(ReactorError::Shutdown);
371
16
                    }
372
                }
373

            
374
16
                let hop = self.join_point_hop(&circ)?;
375

            
376
24
                let (inflight, cwnd) = (|| {
377
16
                    let ccontrol = hop.ccontrol();
378
16
                    let inflight = ccontrol.inflight()?;
379
16
                    let cwnd = ccontrol.cwnd()?;
380

            
381
16
                    Some((inflight, cwnd))
382
                })()
383
16
                .ok_or_else(|| {
384
                    internal!("Congestion control algorithm doesn't track inflight cells or cwnd?!")
385
                })?;
386

            
387
                // If data is in progress on the leg (inflight > cc_sendme_inc),
388
                // then all legs must be closed
389
16
                if inflight >= u32::from(cwnd.params().sendme_inc()) {
390
                    return Err(ReactorError::Shutdown);
391
16
                }
392

            
393
16
                Ok(circ)
394
            }
395
        }
396
16
    }
397

            
398
    /// Return the maximum relative last_seq_recv across all circuits.
399
    #[cfg(feature = "conflux")]
400
16
    fn max_last_seq_recv(&self) -> Option<u64> {
401
16
        self.legs
402
16
            .iter()
403
24
            .filter_map(|leg| leg.last_seq_recv().ok())
404
16
            .max()
405
16
    }
406

            
407
    /// Return the maximum relative last_seq_sent across all circuits.
408
    #[cfg(feature = "conflux")]
409
16
    fn max_last_seq_sent(&self) -> Option<u64> {
410
16
        self.legs
411
16
            .iter()
412
24
            .filter_map(|leg| leg.last_seq_sent().ok())
413
16
            .max()
414
16
    }
415

            
416
    /// Get the [`CircHop`] of the join point on the specified `circ`,
417
    /// returning an error if this is a single path conflux set.
418
2468
    fn join_point_hop<'c>(&self, circ: &'c Circuit) -> Result<&'c CircHop, Bug> {
419
2468
        let Some(join_point) = self.join_point.as_ref().map(|p| p.hop) else {
420
            return Err(internal!("No join point on conflux tunnel?!"));
421
        };
422

            
423
2468
        circ.hop(join_point)
424
2468
            .ok_or_else(|| internal!("Conflux join point disappeared?!"))
425
2468
    }
426

            
427
    /// Return an iterator of all circuits in the conflux set.
428
120
    fn circuits(&self) -> impl Iterator<Item = &Circuit> {
429
120
        self.legs.iter()
430
120
    }
431

            
432
    /// Return the most active [`TunnelActivity`] for any leg of this `ConfluxSet`.
433
    pub(super) fn tunnel_activity(&self) -> TunnelActivity {
434
        self.circuits()
435
            .map(|c| c.hops.tunnel_activity())
436
            .max()
437
            .unwrap_or_else(TunnelActivity::never_used)
438
    }
439

            
440
    /// Add legs to the this conflux set.
441
    ///
442
    /// Returns an error if any of the legs is invalid.
443
    ///
444
    /// A leg is considered valid if
445
    ///
446
    ///   * the circuit has the same length as all the other circuits in the set
447
    ///   * its last hop is equal to the designated join point
448
    ///   * the circuit has no streams attached to any of its hops
449
    ///   * the circuit is not already part of a conflux set
450
    ///
451
    /// Note: the circuits will not begin linking until
452
    /// [`link_circuits`](Self::link_circuits) is called.
453
    ///
454
    /// IMPORTANT: this function does not prevent the construction of conflux sets
455
    /// where the circuit legs share guard or middle relays. It is the responsibility
456
    /// of the caller to enforce the following invariant from prop354:
457
    ///
458
    /// "If building a conflux leg: Reject any circuits that have the same Guard as the other conflux
459
    /// "leg(s) in the current conflux set, EXCEPT when one of the primary Guards is also the chosen
460
    /// "Exit of this conflux set (in which case, re-use the non-Exit Guard)."
461
    ///
462
    /// This is because at this level we don't actually know which relays are the guards,
463
    /// so we can't know if the join point happens to be one of the Guard + Exit relays.
464
    #[cfg(feature = "conflux")]
465
60
    pub(super) fn add_legs(
466
60
        &mut self,
467
60
        legs: Vec<Circuit>,
468
60
        runtime: &tor_rtcompat::DynTimeProvider,
469
60
    ) -> Result<(), Bug> {
470
60
        if legs.is_empty() {
471
            return Err(bad_api_usage!("asked to add empty leg list to conflux set"));
472
60
        }
473

            
474
60
        let join_point = match self.join_point.take() {
475
            Some(p) => {
476
                // Preserve the existing join point, if there is one.
477
                p
478
            }
479
            None => {
480
90
                let (hop, detail, streams) = (|| {
481
60
                    let first_leg = self.circuits().next()?;
482
60
                    let first_leg_path = first_leg.path();
483
60
                    let all_hops = first_leg_path.all_hops();
484
60
                    let hop_num = first_leg.last_hop_num()?;
485
60
                    let detail = all_hops.last()?;
486
60
                    let hop = first_leg.hop(hop_num)?;
487
60
                    let streams = Arc::clone(hop.stream_map());
488
60
                    Some((hop_num, detail.clone(), streams))
489
                })()
490
60
                .ok_or_else(|| bad_api_usage!("asked to join circuit with no hops"))?;
491

            
492
60
                JoinPoint {
493
60
                    hop,
494
60
                    detail,
495
60
                    streams,
496
60
                }
497
            }
498
        };
499

            
500
        // Check two HopDetails for equality.
501
        //
502
        // Returns an error if one of the hops is virtual.
503
88
        let hops_eq = |h1: &HopDetail, h2: &HopDetail| {
504
56
            match (h1, h2) {
505
56
                (HopDetail::Relay(t1), HopDetail::Relay(t2)) => Ok(t1.same_relay_ids(t2)),
506
                #[cfg(feature = "hs-common")]
507
                (HopDetail::Virtual, HopDetail::Virtual) => {
508
                    // TODO(#2016): support onion service conflux
509
                    Err(internal!("onion service conflux not supported"))
510
                }
511
                _ => Ok(false),
512
            }
513
56
        };
514

            
515
        // A leg is considered valid if
516
        //
517
        //   * the circuit has the expected length
518
        //     (the length of the first circuit we added to the set)
519
        //   * its last hop is equal to the designated join point
520
        //     (the last hop of the first circuit we added)
521
        //   * the circuit has no streams attached to any of its hops
522
        //   * the circuit is not already part of a conflux tunnel
523
        //
524
        // Returns an error if any hops are virtual.
525
90
        let leg_is_valid = |leg: &Circuit| -> Result<bool, Bug> {
526
            use crate::ccparams::Algorithm;
527

            
528
60
            let path = leg.path();
529
60
            let Some(last_hop) = path.all_hops().last() else {
530
                // A circuit with no hops is invalid
531
                return Ok(false);
532
            };
533

            
534
            // TODO: this sort of duplicates the check above.
535
            // The difference is that above we read the hop detail
536
            // information from the circuit Path, whereas here we get
537
            // the actual last CircHop of the circuit.
538
60
            let Some(last_hop_num) = leg.last_hop_num() else {
539
                // A circuit with no hops is invalid
540
                return Ok(false);
541
            };
542

            
543
60
            let circhop = leg
544
60
                .hop(last_hop_num)
545
60
                .ok_or_else(|| internal!("hop disappeared?!"))?;
546

            
547
            // Ensure we negotiated a suitable cc algorithm
548
60
            let is_cc_suitable = match circhop.ccontrol().algorithm() {
549
4
                Algorithm::FixedWindow(_) => false,
550
56
                Algorithm::Vegas(_) => true,
551
            };
552

            
553
60
            if !is_cc_suitable {
554
4
                return Ok(false);
555
56
            }
556

            
557
56
            Ok(last_hop_num == join_point.hop
558
56
                && hops_eq(last_hop, &join_point.detail)?
559
52
                && !leg.has_streams()
560
52
                && leg.conflux_status().is_none())
561
60
        };
562

            
563
60
        for leg in &legs {
564
60
            if !leg_is_valid(leg)? {
565
8
                return Err(bad_api_usage!("one more conflux circuits are invalid"));
566
52
            }
567
        }
568

            
569
        // Select a join point, or put the existing one back into self.
570
52
        self.join_point = Some(join_point.clone());
571

            
572
        // The legs are valid, so add them to the set.
573
52
        for circ in legs {
574
52
            let mutable = Arc::clone(circ.mutable());
575
52
            let unique_id = circ.unique_id();
576
52
            self.legs.push(circ);
577
52
            // Merge the mutable state of the circuit into our tunnel state.
578
52
            self.mutable.insert(unique_id, mutable);
579
52
        }
580

            
581
52
        let cwnd_params = self.cwnd_params()?;
582
104
        for circ in self.legs.iter_mut() {
583
            // The circuits that have a None status don't know they're part of
584
            // a multi-path tunnel yet. They need to be initialized with a
585
            // conflux message handler, and have their join point fixed up
586
            // to share a stream map with the join point on all the other circuits.
587
104
            if circ.conflux_status().is_none() {
588
104
                let handler = Box::new(ClientConfluxMsgHandler::new(
589
104
                    join_point.hop,
590
104
                    self.nonce,
591
104
                    Arc::clone(&self.last_seq_delivered),
592
104
                    cwnd_params,
593
104
                    runtime.clone(),
594
                ));
595
104
                let conflux_handler =
596
104
                    ConfluxMsgHandler::new(handler, Arc::clone(&self.last_seq_delivered));
597

            
598
104
                circ.add_to_conflux_tunnel(self.tunnel_id, conflux_handler);
599

            
600
                // Ensure the stream map of the last hop is shared by all the legs
601
104
                let last_hop = circ
602
104
                    .hop_mut(join_point.hop)
603
104
                    .ok_or_else(|| bad_api_usage!("asked to join circuit with no hops"))?;
604
104
                last_hop.set_stream_map(Arc::clone(&join_point.streams))?;
605
            }
606
        }
607

            
608
52
        Ok(())
609
60
    }
610

            
611
    /// Get the [`CongestionWindowParams`] of the join point
612
    /// on the first leg.
613
    ///
614
    /// Returns an error if the congestion control algorithm
615
    /// doesn't have a congestion control window object,
616
    /// or if the conflux set is empty, or the joint point hop
617
    /// does not exist.
618
    ///
619
    // TODO: this function is a bit of a hack. In reality, we only
620
    // need the cc_cwnd_init parameter (for SWITCH seqno validation).
621
    // The fact that we obtain it from the cc params of the join point
622
    // is an implementation detail (it's a workaround for the fact that
623
    // at this point, these params can only obtained from a CircHop)
624
    #[cfg(feature = "conflux")]
625
52
    fn cwnd_params(&self) -> Result<CongestionWindowParams, Bug> {
626
52
        let primary_leg = self
627
52
            .leg(self.primary_id)
628
52
            .ok_or_else(|| internal!("no primary leg?!"))?;
629
52
        let join_point = self.join_point_hop(primary_leg)?;
630
52
        let ccontrol = join_point.ccontrol();
631
52
        let cwnd = ccontrol
632
52
            .cwnd()
633
52
            .ok_or_else(|| internal!("congestion control algorithm does not track the cwnd?!"))?;
634

            
635
52
        Ok(*cwnd.params())
636
52
    }
637

            
638
    /// Try to update the primary leg based on the configured desired UX,
639
    /// if needed.
640
    ///
641
    /// Returns the SWITCH cell to send on the primary leg,
642
    /// if we switched primary leg.
643
    #[cfg(feature = "conflux")]
644
1212
    pub(super) fn maybe_update_primary_leg(&mut self) -> crate::Result<Option<SendRelayCell>> {
645
        use tor_error::into_internal;
646

            
647
1212
        let Some(join_point) = self.join_point.as_ref() else {
648
            // Return early if this is not a multi-path tunnel
649
            return Ok(None);
650
        };
651

            
652
1212
        let join_point = join_point.hop;
653

            
654
1212
        if !self.should_update_primary_leg() {
655
            // Nothing to do
656
12
            return Ok(None);
657
1200
        }
658

            
659
1200
        let Some(new_primary_id) = self.select_primary_leg()? else {
660
            // None of the legs satisfy our UX requirements, continue using the existing one.
661
            return Ok(None);
662
        };
663

            
664
        // Check that the newly selected leg is actually different from the previous
665
1200
        if self.primary_id == new_primary_id {
666
            // The primary leg stays the same, nothing to do.
667
1192
            return Ok(None);
668
8
        }
669

            
670
8
        let prev_last_seq_sent = self.primary_leg_mut()?.last_seq_sent()?;
671
8
        self.primary_id = new_primary_id;
672
8
        let new_last_seq_sent = self.primary_leg_mut()?.last_seq_sent()?;
673

            
674
        // If this fails, it means we haven't updated our primary leg in a very long time.
675
        //
676
        // TODO(#2036): there are currently no safeguards to prevent us from staying
677
        // on the same leg for "too long". Perhaps we should design should_update_primary_leg()
678
        // such that it forces us to switch legs periodically, to prevent the seqno delta from
679
        // getting too big?
680
8
        let seqno_delta = u32::try_from(prev_last_seq_sent - new_last_seq_sent).map_err(
681
8
            into_internal!("Seqno delta for switch does not fit in u32?!"),
682
        )?;
683

            
684
        // We need to carry the last_seq_sent over to the next leg
685
        // (the next cell sent will have seqno = prev_last_seq_sent + 1)
686
8
        self.primary_leg_mut()?
687
8
            .set_last_seq_sent(prev_last_seq_sent)?;
688

            
689
8
        let switch = ConfluxSwitch::new(seqno_delta);
690
8
        let cell = AnyRelayMsgOuter::new(None, switch.into());
691
8
        Ok(Some(SendRelayCell {
692
8
            hop: Some(join_point),
693
8
            early: false,
694
8
            cell,
695
8
        }))
696
1212
    }
697

            
698
    /// Whether it's time to select a new primary leg.
699
    #[cfg(feature = "conflux")]
700
1212
    fn should_update_primary_leg(&mut self) -> bool {
701
1212
        if !self.selected_init_primary {
702
12
            self.maybe_select_init_primary();
703
12
            return false;
704
1200
        }
705

            
706
        // If we don't have at least 2 legs,
707
        // we can't switch our primary leg.
708
1200
        if self.legs.len() < 2 {
709
            return false;
710
1200
        }
711

            
712
        // TODO(conflux-tuning): if it turns out we switch legs too frequently,
713
        // we might want to implement some sort of rate-limiting here
714
        // (see c-tor's conflux_can_switch).
715

            
716
1200
        true
717
1212
    }
718

            
719
    /// Return the best leg according to the configured desired UX.
720
    ///
721
    /// Returns `None` if no suitable leg was found.
722
    #[cfg(feature = "conflux")]
723
1200
    fn select_primary_leg(&self) -> Result<Option<UniqId>, Bug> {
724
1200
        match self.desired_ux {
725
            V1DesiredUx::NO_OPINION | V1DesiredUx::MIN_LATENCY => {
726
1200
                self.select_primary_leg_min_rtt(false)
727
            }
728
            V1DesiredUx::HIGH_THROUGHPUT => self.select_primary_leg_min_rtt(true),
729
            V1DesiredUx::LOW_MEM_LATENCY | V1DesiredUx::LOW_MEM_THROUGHPUT => {
730
                // TODO(conflux-tuning): add support for low-memory algorithms
731
                self.select_primary_leg_min_rtt(false)
732
            }
733
            _ => {
734
                // Default to MIN_RTT if we don't recognize the desired UX value
735
                warn!(
736
                    tunnel_id = %self.tunnel_id,
737
                    "Ignoring unrecognized conflux desired UX {}, using MIN_LATENCY",
738
                    self.desired_ux
739
                );
740
                self.select_primary_leg_min_rtt(false)
741
            }
742
        }
743
1200
    }
744

            
745
    /// Try to choose an initial primary leg, if we have an initial RTT measurement
746
    /// for at least one of the legs.
747
    #[cfg(feature = "conflux")]
748
12
    fn maybe_select_init_primary(&mut self) {
749
12
        let best = self
750
12
            .legs
751
12
            .iter()
752
30
            .filter_map(|leg| leg.init_rtt().map(|rtt| (leg, rtt)))
753
12
            .min_by_key(|(_leg, rtt)| *rtt)
754
18
            .map(|(leg, _rtt)| leg.unique_id());
755

            
756
12
        if let Some(best) = best {
757
12
            self.primary_id = best;
758
12
            self.selected_init_primary = true;
759
12
        }
760
12
    }
761

            
762
    /// Return the leg with the best (lowest) RTT.
763
    ///
764
    /// If `check_can_send` is true, selects the lowest RTT leg that is ready to send.
765
    ///
766
    /// Returns `None` if no suitable leg was found.
767
    #[cfg(feature = "conflux")]
768
1200
    fn select_primary_leg_min_rtt(&self, check_can_send: bool) -> Result<Option<UniqId>, Bug> {
769
1200
        let mut best: Option<(UniqId, u32)> = None;
770

            
771
2400
        for circ in self.legs.iter() {
772
2400
            let leg_id = circ.unique_id();
773
2400
            let join_point = self.join_point_hop(circ)?;
774
2400
            let ccontrol = join_point.ccontrol();
775

            
776
2400
            if check_can_send && !ccontrol.can_send() {
777
                continue;
778
2400
            }
779

            
780
2400
            let rtt = ccontrol.rtt();
781
3144
            let init_rtt_usec = || {
782
1488
                circ.init_rtt()
783
1488
                    .map(|rtt| u32::try_from(rtt.as_micros()).unwrap_or(u32::MAX))
784
1488
            };
785

            
786
2400
            let Some(ewma_rtt) = rtt.ewma_rtt_usec().or_else(init_rtt_usec) else {
787
                return Err(internal!(
788
                    "attempted to select primary leg before handshake completed?!"
789
                ));
790
            };
791

            
792
2400
            best = Some(match best.take() {
793
1200
                Some(best_so_far) if best_so_far.1 <= ewma_rtt => best_so_far,
794
1696
                None | Some(_) => (leg_id, ewma_rtt),
795
            });
796
        }
797

            
798
1200
        Ok(best.map(|(leg_id, _)| leg_id))
799
1200
    }
800

            
801
    /// Returns `true` if our conflux join point is blocked on congestion control
802
    /// on the specified `circuit`.
803
    ///
804
    /// Returns `false` if the join point is not blocked on cc,
805
    /// or if this is a single-path set.
806
    ///
807
    /// Returns an error if this is a multipath tunnel,
808
    /// but the joint point hop doesn't exist on the specified circuit.
809
    #[cfg(feature = "conflux")]
810
1454
    fn is_join_point_blocked_on_cc(join_hop: HopNum, circuit: &Circuit) -> Result<bool, Bug> {
811
1454
        let join_circhop = circuit.hop(join_hop).ok_or_else(|| {
812
            internal!(
813
                "Join point hop {} not found on circuit {}?!",
814
                join_hop.display(),
815
                circuit.unique_id(),
816
            )
817
        })?;
818

            
819
1454
        Ok(!join_circhop.ccontrol().can_send())
820
1454
    }
821

            
822
    /// Returns whether [`next_circ_event`](Self::next_circ_event)
823
    /// should avoid polling the join point streams entirely.
824
    #[cfg(feature = "conflux")]
825
5656
    fn should_skip_join_point(&self) -> Result<bool, Bug> {
826
5656
        let Some(primary_join_point) = self.primary_join_point() else {
827
            // Single-path, there is no join point
828
4202
            return Ok(false);
829
        };
830

            
831
1454
        let join_hop = primary_join_point.1;
832
1454
        let primary_blocked_on_cc = {
833
1454
            let primary = self
834
1454
                .leg(self.primary_id)
835
1454
                .ok_or_else(|| internal!("primary leg disappeared?!"))?;
836
1454
            Self::is_join_point_blocked_on_cc(join_hop, primary)?
837
        };
838

            
839
1454
        if !primary_blocked_on_cc {
840
            // Easy, we can just carry on
841
1426
            return Ok(false);
842
28
        }
843

            
844
        // Now, if the primary *is* blocked on cc, we may still be able to poll
845
        // the join point streams (if we're using the right desired UX)
846
28
        let should_skip = if self.desired_ux != V1DesiredUx::HIGH_THROUGHPUT {
847
            // The primary leg is blocked on cc, and we can't switch because we're
848
            // not using the high throughput algorithm, so we must stop reading
849
            // the join point streams.
850
            //
851
            // Note: if the selected algorithm is HIGH_THROUGHPUT,
852
            // it's okay to continue reading from the edge connection,
853
            // because maybe_update_primary_leg() will select a new,
854
            // non-blocked primary leg, just before sending.
855
28
            trace!(
856
                tunnel_id = %self.tunnel_id,
857
                join_point = ?primary_join_point,
858
                reason = "sending leg blocked on congestion control",
859
                "Pausing join point stream reads"
860
            );
861

            
862
28
            true
863
        } else {
864
            // Ah-ha, the desired UX is HIGH_THROUGHPUT, which means we can switch
865
            // to an unblocked leg before sending any cells over the join point,
866
            // as long as there are some unblocked legs.
867

            
868
            // TODO: figure out how to rewrite this with an idiomatic iterator combinator
869
            let mut all_blocked_on_cc = true;
870
            for leg in &self.legs {
871
                all_blocked_on_cc = Self::is_join_point_blocked_on_cc(join_hop, leg)?;
872
                if !all_blocked_on_cc {
873
                    break;
874
                }
875
            }
876

            
877
            if all_blocked_on_cc {
878
                // All legs are blocked on cc, so we must stop reading from
879
                // the join point streams for now.
880
                trace!(
881
                    tunnel_id = %self.tunnel_id,
882
                    join_point = ?primary_join_point,
883
                    reason = "all legs blocked on congestion control",
884
                    "Pausing join point stream reads"
885
                );
886

            
887
                true
888
            } else {
889
                // At least one leg is not blocked, so we can continue reading
890
                // from the join point streams
891
                false
892
            }
893
        };
894

            
895
28
        Ok(should_skip)
896
5656
    }
897

            
898
    /// Returns the next ready [`CircuitEvent`],
899
    /// obtained from processing the incoming/outgoing messages on all the circuits in this set.
900
    ///
901
    /// Will return an error if there are no circuits in this set,
902
    /// or other internal errors occur.
903
    ///
904
    /// This is cancellation-safe.
905
    #[allow(clippy::unnecessary_wraps)] // Can return Err if conflux is enabled
906
    #[instrument(level = "trace", skip_all)]
907
5974
    pub(super) async fn next_circ_event(
908
5974
        &mut self,
909
5974
        runtime: &tor_rtcompat::DynTimeProvider,
910
8802
    ) -> Result<SmallVec<[CircuitEvent; CIRC_EVENT_COUNT]>, crate::Error> {
911
        // Avoid polling the streams on the join point if our primary
912
        // leg is blocked on cc
913
        cfg_if::cfg_if! {
914
            if #[cfg(feature = "conflux")] {
915
                let mut should_poll_join_point = !self.should_skip_join_point()?;
916
            } else {
917
                let mut should_poll_join_point = true;
918
            }
919
        };
920
        let join_point = self.primary_join_point().map(|join_point| join_point.1);
921

            
922
        // Each circuit leg has a PollAll future (see poll_all_circ below)
923
        // that drives two futures: one that reads from input channel,
924
        // and another drives the application streams.
925
        //
926
        // *This* PollAll drives the PollAll futures of all circuit legs in lockstep,
927
        // ensuring they all get a chance to make some progress on every reactor iteration.
928
        //
929
        // IMPORTANT: if you want to push additional futures into this,
930
        // bear in mind that the ordering matters!
931
        // If multiple futures resolve at the same time, their results will be processed
932
        // in the order their corresponding futures were inserted into `PollAll`.
933
        // So if futures A and B resolve at the same time, and future A was pushed
934
        // into `PollAll` before future B, the result of future A will come
935
        // before future B's result in the result list returned by poll_all.await.
936
        //
937
        // This means that the events corresponding to the first circuit in the tunnel
938
        // will be executed first, followed by the events issued by the next circuit,
939
        // and so on.
940
        //
941
        let mut poll_all =
942
            PollAll::<MAX_CONFLUX_LEGS, SmallVec<[CircuitEvent; NUM_CIRC_FUTURES]>>::new();
943

            
944
        for leg in &mut self.legs {
945
            let unique_id = leg.unique_id();
946
            let circ_id = leg.circ_id();
947
5656
            let tunnel_id = self.tunnel_id;
948
            let runtime = runtime.clone();
949

            
950
            // Garbage-collect all halfstreams that have expired.
951
            //
952
            // Note: this will iterate over the closed streams of all hops.
953
            // If we think this will cause perf issues, one idea would be to make
954
            // StreamMap::closed_streams into a min-heap, and add a branch to the
955
            // select_biased! below to sleep until the first expiry is due
956
            // (but my gut feeling is that iterating is cheaper)
957
            leg.remove_expired_halfstreams(runtime.now());
958

            
959
            // The client SHOULD abandon and close circuit if the LINKED message takes too long to
960
            // arrive. This timeout MUST be no larger than the normal SOCKS/stream timeout in use for
961
            // RELAY_BEGIN, but MAY be the Circuit Build Timeout value, instead. (The C-Tor
962
            // implementation currently uses Circuit Build Timeout).
963
            let conflux_hs_timeout = leg.conflux_hs_timeout();
964

            
965
            let mut poll_all_circ = PollAll::<NUM_CIRC_FUTURES, CircuitEvent>::new();
966

            
967
536
            let input = leg.input.next().map(move |res| match res {
968
512
                Some(msg) => match msg.try_into() {
969
512
                    Ok(cell) => CircuitEvent::HandleCell {
970
512
                        leg: unique_id,
971
512
                        cell,
972
512
                    },
973
                    // A message outside our restricted set is either a fatal internal error or
974
                    // a protocol violation somehow so shutdown.
975
                    //
976
                    // TODO(relay): We have this spec ticket open about this behavior:
977
                    // https://gitlab.torproject.org/tpo/core/torspec/-/issues/385. It is plausible
978
                    // that we decide to either keep this circuit close behavior or close the
979
                    // entire channel in this case. Resolution of the above ticket needs to fix
980
                    // this part.
981
                    Err(e) => CircuitEvent::ProtoViolation { err: e },
982
                },
983
24
                None => CircuitEvent::RemoveLeg {
984
24
                    leg: unique_id,
985
24
                    reason: RemoveLegReason::ChannelClosed,
986
24
                },
987
536
            });
988
            poll_all_circ.push(input);
989

            
990
            // This future resolves when the chan_sender sink (i.e. the outgoing TCP connection)
991
            // becomes ready. We need it inside the next_ready_stream future below,
992
            // to prevent reading from the application streams before we are ready to send.
993
7138
            let chan_ready_fut = futures::future::poll_fn(|cx| {
994
                use futures::Sink as _;
995

            
996
                // Ensure the chan sender sink is ready before polling the ready streams.
997
7138
                Pin::new(&mut leg.chan_sender).poll_ready(cx)
998
7138
            });
999

            
            let exclude_hop = if should_poll_join_point {
                // Avoid polling the join point more than once per reactor loop.
                should_poll_join_point = false;
                None
            } else {
                join_point
            };
            let mut ready_streams = leg.hops.ready_streams_iterator(exclude_hop);
7106
            let next_ready_stream = async move {
                // Avoid polling the application streams if the outgoing sink is blocked
7106
                let _ = chan_ready_fut.await;
7106
                match ready_streams.next().await {
4238
                    Some(x) => x,
                    None => {
                        info!(
                            circ_uniq_id = %unique_id,
                            forward_circ_id = %circ_id,
                            "no ready streams (maybe blocked on cc?)"
                        );
                        // There are no ready streams (for example, they may all be
                        // blocked due to congestion control), so there is nothing
                        // to do.
                        // We await an infinitely pending future so that we don't
                        // immediately return a `None` in the `select_biased!` below.
                        // We'd rather wait on `input.next()` than immediately return with
                        // no `CircuitEvent`, which could put the reactor into a spin loop.
                        let () = std::future::pending().await;
                        unreachable!();
                    }
                }
4238
            };
            poll_all_circ.push(next_ready_stream.map(move |cmd| CircuitEvent::RunCmd {
4238
                leg: unique_id,
4238
                cmd,
4238
            }));
            let mut next_padding_event_fut = leg.padding_event_stream.next();
            // This selects between 3 events that cannot be handled concurrently.
            //
            // If the conflux handshake times out, we need to remove the circuit leg
            // (any pending padding events or application stream data should be discarded;
            // in fact, there shouldn't even be any open streams on circuits that are
            // in the conflux handshake phase).
            //
            // If there's a padding event, we need to handle it immediately,
            // because it might tell us to start blocking the chan_sender sink,
            // which, in turn, means we need to stop trying to read from the application streams.
            poll_all.push(
7106
                async move {
7106
                    let conflux_hs_timeout = if let Some(timeout) = conflux_hs_timeout {
                        // TODO: ask Diziet if we can have a sleep_until_instant() function
120
                        Box::pin(runtime.sleep_until_wallclock(timeout))
120
                            as Pin<Box<dyn Future<Output = ()> + Send>>
                    } else {
6986
                        Box::pin(std::future::pending())
                    };
7106
                    select_biased! {
7106
                        () = conflux_hs_timeout.fuse() => {
4
                            warn!(
                                tunnel_id = %tunnel_id,
                                circ_uniq_id = %unique_id,
                                forward_circ_id = %circ_id,
                                "Conflux handshake timed out on circuit"
                            );
                            // Conflux handshake has timed out, time to remove this circuit leg,
                            // and notify the handshake initiator.
4
                            smallvec![CircuitEvent::RemoveLeg {
                                leg: unique_id,
                                reason: RemoveLegReason::ConfluxHandshakeTimeout,
                            }]
                        }
                        padding_event = next_padding_event_fut => {
                            smallvec![CircuitEvent::PaddingAction {
                                leg: unique_id,
                                padding_event:
                                    padding_event.expect("PaddingEventStream, surprisingly, was terminated!"),
                            }]
                        }
7106
                        ret = poll_all_circ.fuse() => ret,
                    }
4756
                }
            );
        }
        // Flatten the nested SmallVecs to simplify the calling code
        // (which will handle all the returned events sequentially).
        Ok(poll_all.await.into_iter().flatten().collect())
4734
    }
    /// The join point on the current primary leg.
11376
    pub(super) fn primary_join_point(&self) -> Option<(UniqId, HopNum)> {
11376
        self.join_point
11376
            .as_ref()
12862
            .map(|join_point| (self.primary_id, join_point.hop))
11376
    }
    /// Does congestion control use stream SENDMEs for the given hop?
    ///
    /// Returns `None` if either the `leg` or `hop` don't exist.
    pub(super) fn uses_stream_sendme(&self, leg: UniqId, hop: HopNum) -> Option<bool> {
        self.leg(leg)?.uses_stream_sendme(hop)
    }
    /// Encode `msg`, encrypt it, and send it to the 'hop'th hop.
    ///
    /// See [`Circuit::send_relay_cell`].
    #[instrument(level = "trace", skip_all)]
4412
    pub(super) async fn send_relay_cell_on_leg(
4412
        &mut self,
4412
        msg: SendRelayCell,
4412
        leg: Option<UniqId>,
6618
    ) -> crate::Result<()> {
        let conflux_join_point = self.join_point.as_ref().map(|join_point| join_point.hop);
        let leg = if let Some(join_point) = conflux_join_point {
            let hop = msg.hop.expect("missing hop in client SendRelayCell?!");
            // Conflux circuits always send multiplexed relay commands to
            // to the last hop (the join point).
            if cmd_counts_towards_seqno(msg.cell.cmd()) {
                if hop != join_point {
                    // For leaky pipe, we must continue using the original leg
                    leg
                } else {
4412
                    let old_primary_leg = self.primary_id;
                    // Check if it's time to switch our primary leg.
                    #[cfg(feature = "conflux")]
                    if let Some(switch_cell) = self.maybe_update_primary_leg()? {
                        trace!(
                            old = ?old_primary_leg,
                            new = ?self.primary_id,
                            "Switching primary conflux leg..."
                        );
                        self.primary_leg_mut()?.send_relay_cell(switch_cell).await?;
                    }
                    // Use the possibly updated primary leg
                    Some(self.primary_id)
                }
            } else {
                // Non-multiplexed commands go on their original
                // circuit and hop
                leg
            }
        } else {
            // If there is no join point, it means this is not
            // a multi-path tunnel, so we continue using
            // the leg_id/hop the cmd came from.
            leg
        };
        let leg = leg.unwrap_or(self.primary_id);
        let circ = self
            .leg_mut(leg)
            .ok_or_else(|| internal!("leg disappeared?!"))?;
        circ.send_relay_cell(msg).await
4412
    }
    /// Send a LINK cell down each unlinked leg.
    #[cfg(feature = "conflux")]
52
    pub(super) async fn link_circuits(
52
        &mut self,
52
        runtime: &tor_rtcompat::DynTimeProvider,
78
    ) -> crate::Result<()> {
52
        let (_leg_id, join_point) = self
52
            .primary_join_point()
52
            .ok_or_else(|| internal!("no join point when trying to send LINK"))?;
        // Link all the circuits that haven't started the conflux handshake yet.
104
        for circ in self
52
            .legs
52
            .iter_mut()
            // TODO: it is an internal error if any of the legs don't have a conflux handler
            // (i.e. if conflux_status() returns None)
104
            .filter(|circ| circ.conflux_status() == Some(ConfluxStatus::Unlinked))
        {
104
            let v1_payload = V1LinkPayload::new(self.nonce, self.desired_ux);
104
            let link = ConfluxLink::new(v1_payload);
104
            let cell = AnyRelayMsgOuter::new(None, link.into());
104
            circ.begin_conflux_link(join_point, cell, runtime).await?;
        }
        // TODO(conflux): the caller should take care to not allow opening streams
        // until the conflux set is ready (i.e. until at least one of the legs completes
        // the handshake).
        //
        // We will probably need a channel for notifying the caller
        // of handshake completion/conflux set readiness
52
        Ok(())
52
    }
    /// Get the number of unlinked or non-conflux legs.
    #[cfg(feature = "conflux")]
60
    pub(super) fn num_unlinked(&self) -> usize {
60
        self.circuits()
90
            .filter(|circ| {
60
                let status = circ.conflux_status();
60
                status.is_none() || status == Some(ConfluxStatus::Unlinked)
60
            })
60
            .count()
60
    }
    /// Check if the specified sequence number is the sequence number of the
    /// next message we're expecting to handle.
36
    pub(super) fn is_seqno_in_order(&self, seq_recv: u64) -> bool {
36
        let last_seq_delivered = self.last_seq_delivered.load(atomic::Ordering::Acquire);
36
        seq_recv == last_seq_delivered + 1
36
    }
    /// Remove the circuit leg with the specified `UniqId` from this conflux set.
    ///
    /// Unlike [`ConfluxSet::remove`], this function does not check
    /// if the removal of the leg ought to trigger a reactor shutdown.
    ///
    /// Returns an error if the leg doesn't exit in the conflux set.
112
    fn remove_unchecked(&mut self, circ_uniq_id: UniqId) -> Result<Circuit, Bug> {
112
        let idx = self
112
            .legs
112
            .iter()
184
            .position(|circ| circ.unique_id() == circ_uniq_id)
112
            .ok_or_else(|| internal!("leg {circ_uniq_id:?} not found in conflux set"))?;
112
        Ok(self.legs.remove(idx))
112
    }
    /// Perform some circuit-padding-based event on the specified circuit.
    #[cfg(feature = "circ-padding")]
    pub(super) async fn run_padding_event(
        &mut self,
        circ_uniq_id: UniqId,
        padding_event: PaddingEvent,
    ) -> crate::Result<()> {
        use PaddingEvent as E;
        let Some(circ) = self.leg_mut(circ_uniq_id) else {
            // No such circuit; it must have gone away after generating this event.
            // Just ignore it.
            return Ok(());
        };
        match padding_event {
            E::SendPadding(send_padding) => {
                circ.send_padding(send_padding).await?;
            }
            E::StartBlocking(start_blocking) => {
                circ.start_blocking_for_padding(start_blocking);
            }
            E::StopBlocking => {
                circ.stop_blocking_for_padding();
            }
        }
        Ok(())
    }
}
/// An error returned when a method is expecting a single-leg conflux circuit,
/// but it is not single-leg.
#[derive(Clone, Debug, derive_more::Display, thiserror::Error)]
pub(super) struct NotSingleLegError(#[source] Bug);
impl From<NotSingleLegError> for Bug {
4
    fn from(e: NotSingleLegError) -> Self {
4
        e.0
4
    }
}
impl From<NotSingleLegError> for crate::Error {
    fn from(e: NotSingleLegError) -> Self {
        Self::from(e.0)
    }
}
impl From<NotSingleLegError> for ReactorError {
    fn from(e: NotSingleLegError) -> Self {
        Self::from(e.0)
    }
}
impl<I: Iterator> From<ExactlyOneError<I>> for NotSingleLegError {
1466
    fn from(e: ExactlyOneError<I>) -> Self {
        // TODO: cannot wrap the ExactlyOneError with into_bad_api_usage
        // because it's not Send + Sync
1466
        Self(bad_api_usage!("not a single leg conflux set ({e})"))
1466
    }
}
#[cfg(test)]
mod test {
    // Tested in [`crate::client::circuit::test`].
}