tor_interface/
legacy_tor_client.rs

1// standard
2use std::collections::BTreeMap;
3use std::convert::From;
4use std::default::Default;
5use std::net::{SocketAddr, TcpListener};
6use std::option::Option;
7use std::path::PathBuf;
8use std::string::ToString;
9use std::sync::{atomic, Arc};
10use std::time::Duration;
11
12// extern crates
13use socks::Socks5Stream;
14
15// internal crates
16use crate::censorship_circumvention::*;
17use crate::legacy_tor_control_stream::*;
18use crate::legacy_tor_controller::*;
19use crate::legacy_tor_process::*;
20use crate::legacy_tor_version::*;
21use crate::proxy::*;
22use crate::tor_crypto::*;
23use crate::tor_provider;
24use crate::tor_provider::*;
25
26/// [`LegacyTorClient`]-specific error type
27#[derive(thiserror::Error, Debug)]
28pub enum Error {
29    #[error("failed to create LegacyTorProcess object")]
30    LegacyTorProcessCreationFailed(#[source] crate::legacy_tor_process::Error),
31
32    #[error("failed to create LegacyControlStream object")]
33    LegacyControlStreamCreationFailed(#[source] crate::legacy_tor_control_stream::Error),
34
35    #[error("failed to create LegacyTorController object")]
36    LegacyTorControllerCreationFailed(#[source] crate::legacy_tor_controller::Error),
37
38    #[error("failed to authenticate with the tor process")]
39    LegacyTorProcessAuthenticationFailed(#[source] crate::legacy_tor_controller::Error),
40
41    #[error("failed to determine the tor process version")]
42    GetInfoVersionFailed(#[source] crate::legacy_tor_controller::Error),
43
44    #[error("tor process version to old; found {0} but must be at least {1}")]
45    LegacyTorProcessTooOld(String, String),
46
47    #[error("failed to register for STATUS_CLIENT and HS_DESC events")]
48    SetEventsFailed(#[source] crate::legacy_tor_controller::Error),
49
50    #[error("failed to delete unused onion service")]
51    DelOnionFailed(#[source] crate::legacy_tor_controller::Error),
52
53    #[error("failed waiting for async events: {0}")]
54    WaitAsyncEventsFailed(#[source] crate::legacy_tor_controller::Error),
55
56    #[error("failed to begin bootstrap")]
57    SetConfDisableNetwork0Failed(#[source] crate::legacy_tor_controller::Error),
58
59    #[error("failed to setconf")]
60    SetConfFailed(#[source] crate::legacy_tor_controller::Error),
61
62    #[error("failed to add client auth for onion service")]
63    OnionClientAuthAddFailed(#[source] crate::legacy_tor_controller::Error),
64
65    #[error("failed to remove client auth from onion service")]
66    OnionClientAuthRemoveFailed(#[source] crate::legacy_tor_controller::Error),
67
68    #[error("failed to get socks listener")]
69    GetInfoNetListenersSocksFailed(#[source] crate::legacy_tor_controller::Error),
70
71    #[error("no socks listeners available to connect through")]
72    NoSocksListenersFound(),
73
74    #[error("invalid circuit token")]
75    CircuitTokenInvalid(),
76
77    #[error("unable to connect to socks listener")]
78    Socks5ConnectionFailed(#[source] std::io::Error),
79
80    #[error("unable to bind TCP listener")]
81    TcpListenerBindFailed(#[source] std::io::Error),
82
83    #[error("unable to get TCP listener's local address")]
84    TcpListenerLocalAddrFailed(#[source] std::io::Error),
85
86    #[error("failed to create onion service")]
87    AddOnionFailed(#[source] crate::legacy_tor_controller::Error),
88
89    #[error("tor not bootstrapped")]
90    LegacyTorNotBootstrapped(),
91
92    #[error("{0}")]
93    PluggableTransportConfigDirectoryCreationFailed(#[source] std::io::Error),
94
95    #[error("unable to create pluggable-transport directory because file with same name already exists: {0:?}")]
96    PluggableTransportDirectoryNameCollision(PathBuf),
97
98    #[error("{0}")]
99    PluggableTransportSymlinkRemovalFailed(#[source] std::io::Error),
100
101    #[error("{0}")]
102    PluggableTransportSymlinkCreationFailed(#[source] std::io::Error),
103
104    #[error("pluggable transport binary name not representable as utf8: {0:?}")]
105    PluggableTransportBinaryNameNotUtf8Representnable(std::ffi::OsString),
106
107    #[error("{0}")]
108    PluggableTransportConfigError(#[source] crate::censorship_circumvention::PluggableTransportConfigError),
109
110    #[error("pluggable transport multiply defines '{0}' bridge transport type")]
111    BridgeTransportTypeMultiplyDefined(String),
112
113    #[error("bridge transport '{0}' not supported by pluggable transport configuration")]
114    BridgeTransportNotSupported(String),
115
116    #[error("not implemented")]
117    NotImplemented(),
118}
119
120impl From<Error> for crate::tor_provider::Error {
121    fn from(error: Error) -> Self {
122        crate::tor_provider::Error::Generic(error.to_string())
123    }
124}
125
126//
127// CircuitToken Implementation
128//
129struct LegacyCircuitToken {
130    username: String,
131    password: String,
132}
133
134impl LegacyCircuitToken {
135    fn new() -> LegacyCircuitToken {
136        const CIRCUIT_TOKEN_USERNAME_LENGTH: usize = 32usize;
137        const CIRCUIT_TOKEN_PASSWORD_LENGTH: usize = 32usize;
138        let username = generate_password(CIRCUIT_TOKEN_USERNAME_LENGTH);
139        let password = generate_password(CIRCUIT_TOKEN_PASSWORD_LENGTH);
140
141        LegacyCircuitToken { username, password }
142    }
143}
144
145impl Default for LegacyCircuitToken {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151//
152// LegacyTorClientConfig
153//
154
155#[derive(Clone, Debug)]
156pub enum LegacyTorClientConfig {
157    BundledTor {
158        tor_bin_path: PathBuf,
159        data_directory: PathBuf,
160        proxy_settings: Option<ProxyConfig>,
161        allowed_ports: Option<Vec<u16>>,
162        pluggable_transports: Option<Vec<PluggableTransportConfig>>,
163        bridge_lines: Option<Vec<BridgeLine>>,
164    },
165    SystemTor {
166        tor_socks_addr: SocketAddr,
167        tor_control_addr: SocketAddr,
168        tor_control_passwd: String,
169    },
170}
171
172//
173// LegacyTorClient
174//
175
176/// A `LegacyTorClient` implements the [`TorProvider`] trait using a legacy c-tor daemon backend.
177///
178/// The tor process can either be launched and owned by `LegacyTorClient`, or it can use an already running tor-daemon. When using an already runnng tor-daemon, the [`TorProvider::bootstrap()`] automatically succeeds, presuming the connected tor-daemon has successfully bootstrapped.
179///
180/// The minimum supported c-tor is version 0.4.6.1.
181pub struct LegacyTorClient {
182    daemon: Option<LegacyTorProcess>,
183    version: LegacyTorVersion,
184    controller: LegacyTorController,
185    bootstrapped: bool,
186    socks_listener: Option<SocketAddr>,
187    // list of open onion services and their is_active flag
188    onion_services: Vec<(V3OnionServiceId, Arc<atomic::AtomicBool>)>,
189    // our list of circuit tokens for the tor daemon
190    circuit_token_counter: usize,
191    circuit_tokens: BTreeMap<CircuitToken, LegacyCircuitToken>,
192}
193
194impl LegacyTorClient {
195    /// Construct a new `LegacyTorClient` from a [`LegacyTorClientConfig`].
196    pub fn new(config: LegacyTorClientConfig) -> Result<LegacyTorClient, Error> {
197        let (daemon, mut controller, password, socks_listener) = match &config {
198            LegacyTorClientConfig::BundledTor {
199                tor_bin_path,
200                data_directory,
201                ..
202            } => {
203                // launch tor
204                let daemon =
205                    LegacyTorProcess::new(tor_bin_path.as_path(), data_directory.as_path())
206                        .map_err(Error::LegacyTorProcessCreationFailed)?;
207                // open a control stream
208                let control_stream =
209                    LegacyControlStream::new(daemon.get_control_addr(), Duration::from_millis(16))
210                        .map_err(Error::LegacyControlStreamCreationFailed)?;
211
212                // create a controler
213                let controller = LegacyTorController::new(control_stream)
214                    .map_err(Error::LegacyTorControllerCreationFailed)?;
215
216                let password = daemon.get_password().to_string();
217                (Some(daemon), controller, password, None)
218            }
219            LegacyTorClientConfig::SystemTor {
220                tor_socks_addr,
221                tor_control_addr,
222                tor_control_passwd,
223            } => {
224                // open a control stream
225                let control_stream =
226                    LegacyControlStream::new(&tor_control_addr, Duration::from_millis(16))
227                        .map_err(Error::LegacyControlStreamCreationFailed)?;
228
229                // create a controler
230                let controller = LegacyTorController::new(control_stream)
231                    .map_err(Error::LegacyTorControllerCreationFailed)?;
232
233                (
234                    None,
235                    controller,
236                    tor_control_passwd.clone(),
237                    Some(tor_socks_addr.clone()),
238                )
239            }
240        };
241
242        // authenticate
243        controller
244            .authenticate(&password)
245            .map_err(Error::LegacyTorProcessAuthenticationFailed)?;
246
247        // min required version for v3 client auth (see control-spec.txt)
248        let min_required_version = LegacyTorVersion {
249            major: 0u32,
250            minor: 4u32,
251            micro: 6u32,
252            patch_level: 1u32,
253            status_tag: None,
254        };
255
256        // verify version is recent enough
257        let version = controller
258            .getinfo_version()
259            .map_err(Error::GetInfoVersionFailed)?;
260
261        if version < min_required_version {
262            return Err(Error::LegacyTorProcessTooOld(
263                version.to_string(),
264                min_required_version.to_string(),
265            ));
266        }
267
268        // configure tor client
269        if let LegacyTorClientConfig::BundledTor {
270            data_directory,
271            proxy_settings,
272            allowed_ports,
273            pluggable_transports,
274            bridge_lines,
275            ..
276        } = config
277        {
278            // configure proxy
279            match proxy_settings {
280                Some(ProxyConfig::Socks4(Socks4ProxyConfig { address })) => {
281                    controller
282                        .setconf(&[("Socks4Proxy", address.to_string())])
283                        .map_err(Error::SetConfFailed)?;
284                }
285                Some(ProxyConfig::Socks5(Socks5ProxyConfig {
286                    address,
287                    username,
288                    password,
289                })) => {
290                    controller
291                        .setconf(&[("Socks5Proxy", address.to_string())])
292                        .map_err(Error::SetConfFailed)?;
293                    let username = username.unwrap_or("".to_string());
294                    if !username.is_empty() {
295                        controller
296                            .setconf(&[("Socks5ProxyUsername", username.to_string())])
297                            .map_err(Error::SetConfFailed)?;
298                    }
299                    let password = password.unwrap_or("".to_string());
300                    if !password.is_empty() {
301                        controller
302                            .setconf(&[("Socks5ProxyPassword", password.to_string())])
303                            .map_err(Error::SetConfFailed)?;
304                    }
305                }
306                Some(ProxyConfig::Https(HttpsProxyConfig {
307                    address,
308                    username,
309                    password,
310                })) => {
311                    controller
312                        .setconf(&[("HTTPSProxy", address.to_string())])
313                        .map_err(Error::SetConfFailed)?;
314                    let username = username.unwrap_or("".to_string());
315                    let password = password.unwrap_or("".to_string());
316                    if !username.is_empty() || !password.is_empty() {
317                        let authenticator = format!("{}:{}", username, password);
318                        controller
319                            .setconf(&[("HTTPSProxyAuthenticator", authenticator)])
320                            .map_err(Error::SetConfFailed)?;
321                    }
322                }
323                None => (),
324            }
325            // configure firewall
326            if let Some(allowed_ports) = allowed_ports {
327                let allowed_addresses: Vec<String> = allowed_ports
328                    .iter()
329                    .map(|port| format!("*{{}}:{port}"))
330                    .collect();
331                let allowed_addresses = allowed_addresses.join(", ");
332                controller
333                    .setconf(&[("ReachableAddresses", allowed_addresses)])
334                    .map_err(Error::SetConfFailed)?;
335            }
336            // configure pluggable transports
337            let mut supported_transports: std::collections::BTreeSet<String> = Default::default();
338            if let Some(pluggable_transports) = pluggable_transports {
339                // Legacy tor daemon cannot be configured to use pluggable-transports which
340                // exist in paths containing spaces. To work around this, we create a known, safe
341                // path in the tor daemon's working directory, and soft-link the provided
342                // binary path to this safe location. Finally, we configure tor to use the soft-linked
343                // binary in the ClientTransportPlugin setconf call.
344
345                // create pluggable-transport directory
346                let mut pt_directory = data_directory.clone();
347                pt_directory.push("pluggable-transports");
348                if !std::path::Path::exists(&pt_directory) {
349                    // path does not exist so create it
350                    std::fs::create_dir(&pt_directory)
351                        .map_err(Error::PluggableTransportConfigDirectoryCreationFailed)?;
352                } else if !std::path::Path::is_dir(&pt_directory) {
353                    // path exists but it is not a directory
354                    return Err(Error::PluggableTransportDirectoryNameCollision(
355                        pt_directory,
356                    ));
357                }
358
359                // symlink all our pts and configure tor
360                let mut conf: Vec<(&str, String)> = Default::default();
361                for pt_settings in &pluggable_transports {
362                    // symlink absolute path of pt binary to pt_directory in tor's working
363                    // directory
364                    let path_to_binary = pt_settings.path_to_binary();
365                    let binary_name = path_to_binary
366                        .file_name()
367                        .expect("file_name should be absolute path");
368                    let mut pt_symlink = pt_directory.clone();
369                    pt_symlink.push(binary_name);
370                    let binary_name = if let Some(binary_name) = binary_name.to_str() {
371                        binary_name
372                    } else {
373                        return Err(Error::PluggableTransportBinaryNameNotUtf8Representnable(
374                            binary_name.to_os_string(),
375                        ));
376                    };
377
378                    // remove any file that may exist with the same name
379                    if std::path::Path::exists(&pt_symlink) {
380                        std::fs::remove_file(&pt_symlink)
381                            .map_err(Error::PluggableTransportSymlinkRemovalFailed)?;
382                    }
383
384                    // create new symlink
385                    #[cfg(windows)]
386                    std::os::windows::fs::symlink_file(path_to_binary, &pt_symlink)
387                        .map_err(Error::PluggableTransportSymlinkCreationFailed)?;
388                    #[cfg(unix)]
389                    std::os::unix::fs::symlink(path_to_binary, &pt_symlink)
390                        .map_err(Error::PluggableTransportSymlinkCreationFailed)?;
391
392                    // verify a bridge-type support has not been defined for multiple pluggable-transports
393                    for transport in pt_settings.transports() {
394                        if supported_transports.contains(transport) {
395                            return Err(Error::BridgeTransportTypeMultiplyDefined(
396                                transport.to_string(),
397                            ));
398                        }
399                        supported_transports.insert(transport.to_string());
400                    }
401
402                    // finally construct our setconf value
403                    let transports = pt_settings.transports().join(",");
404                    use std::path::MAIN_SEPARATOR;
405                    let path_to_binary =
406                        format!("pluggable-transports{MAIN_SEPARATOR}{binary_name}");
407                    let options = pt_settings.options().join(" ");
408
409                    let value = format!("{transports} exec {path_to_binary} {options}");
410                    conf.push(("ClientTransportPlugin", value));
411                }
412                controller
413                    .setconf(conf.as_slice())
414                    .map_err(Error::SetConfFailed)?;
415            }
416            // configure bridge lines
417            if let Some(bridge_lines) = bridge_lines {
418                let mut conf: Vec<(&str, String)> = Default::default();
419                for bridge_line in &bridge_lines {
420                    if !supported_transports.contains(bridge_line.transport()) {
421                        return Err(Error::BridgeTransportNotSupported(
422                            bridge_line.transport().to_string(),
423                        ));
424                    }
425                    let value = bridge_line.as_legacy_tor_setconf_value();
426                    conf.push(("Bridge", value));
427                }
428                conf.push(("UseBridges", "1".to_string()));
429                controller
430                    .setconf(conf.as_slice())
431                    .map_err(Error::SetConfFailed)?;
432            }
433        }
434
435        // register for STATUS_CLIENT async events
436        controller
437            .setevents(&["STATUS_CLIENT", "HS_DESC"])
438            .map_err(Error::SetEventsFailed)?;
439
440        Ok(LegacyTorClient {
441            daemon,
442            version,
443            controller,
444            bootstrapped: false,
445            socks_listener,
446            onion_services: Default::default(),
447            circuit_token_counter: 0usize,
448            circuit_tokens: Default::default(),
449        })
450    }
451
452    /// Get the version of the connected c-tor daemon.
453    pub fn version(&mut self) -> LegacyTorVersion {
454        self.version.clone()
455    }
456}
457
458impl TorProvider for LegacyTorClient {
459    fn update(&mut self) -> Result<Vec<TorEvent>, tor_provider::Error> {
460        let mut i = 0;
461        while i < self.onion_services.len() {
462            // remove onion services with no active listeners
463            if !self.onion_services[i].1.load(atomic::Ordering::Relaxed) {
464                let entry = self.onion_services.swap_remove(i);
465                let service_id = entry.0;
466
467                self.controller
468                    .del_onion(&service_id)
469                    .map_err(Error::DelOnionFailed)?;
470            } else {
471                i += 1;
472            }
473        }
474
475        let mut events: Vec<TorEvent> = Default::default();
476        for async_event in self
477            .controller
478            .wait_async_events()
479            .map_err(Error::WaitAsyncEventsFailed)?
480            .iter()
481        {
482            match async_event {
483                AsyncEvent::StatusClient {
484                    severity,
485                    action,
486                    arguments,
487                } => {
488                    if severity == "NOTICE" && action == "BOOTSTRAP" {
489                        let mut progress: u32 = 0;
490                        let mut tag: String = Default::default();
491                        let mut summary: String = Default::default();
492                        for (key, val) in arguments.iter() {
493                            match key.as_str() {
494                                "PROGRESS" => progress = val.parse().unwrap_or(0u32),
495                                "TAG" => tag = val.to_string(),
496                                "SUMMARY" => summary = val.to_string(),
497                                _ => {} // ignore unexpected arguments
498                            }
499                        }
500                        events.push(TorEvent::BootstrapStatus {
501                            progress,
502                            tag,
503                            summary,
504                        });
505                        if progress == 100u32 {
506                            events.push(TorEvent::BootstrapComplete);
507                            self.bootstrapped = true;
508                        }
509                    }
510                }
511                AsyncEvent::HsDesc { action, hs_address } => {
512                    if action == "UPLOADED" {
513                        events.push(TorEvent::OnionServicePublished {
514                            service_id: hs_address.clone(),
515                        });
516                    }
517                }
518                AsyncEvent::Unknown { lines } => {
519                    println!("Received Unknown Event:");
520                    for line in lines.iter() {
521                        println!(" {}", line);
522                    }
523                }
524            }
525        }
526
527        if let Some(daemon) = &mut self.daemon {
528            // bundled tor gives us log-lines
529            for log_line in daemon.wait_log_lines().iter_mut() {
530                events.push(TorEvent::LogReceived {
531                    line: std::mem::take(log_line),
532                });
533            }
534        } else if !self.bootstrapped {
535            // system tor needs to send a bootstrap complete event *once*
536            events.push(TorEvent::BootstrapComplete);
537            self.bootstrapped = true;
538        }
539
540        Ok(events)
541    }
542
543    fn bootstrap(&mut self) -> Result<(), tor_provider::Error> {
544        if !self.bootstrapped {
545            self.controller
546                .setconf(&[("DisableNetwork", "0".to_string())])
547                .map_err(Error::SetConfDisableNetwork0Failed)?;
548        }
549        Ok(())
550    }
551
552    fn add_client_auth(
553        &mut self,
554        service_id: &V3OnionServiceId,
555        client_auth: &X25519PrivateKey,
556    ) -> Result<(), tor_provider::Error> {
557        Ok(self
558            .controller
559            .onion_client_auth_add(service_id, client_auth, None, &Default::default())
560            .map_err(Error::OnionClientAuthAddFailed)?)
561    }
562
563    fn remove_client_auth(
564        &mut self,
565        service_id: &V3OnionServiceId,
566    ) -> Result<(), tor_provider::Error> {
567        Ok(self
568            .controller
569            .onion_client_auth_remove(service_id)
570            .map_err(Error::OnionClientAuthRemoveFailed)?)
571    }
572
573    // connect to an onion service and returns OnionStream
574    fn connect(
575        &mut self,
576        target: TargetAddr,
577        circuit: Option<CircuitToken>,
578    ) -> Result<OnionStream, tor_provider::Error> {
579        if !self.bootstrapped {
580            return Err(Error::LegacyTorNotBootstrapped().into());
581        }
582
583        if self.socks_listener.is_none() {
584            let mut listeners = self
585                .controller
586                .getinfo_net_listeners_socks()
587                .map_err(Error::GetInfoNetListenersSocksFailed)?;
588            if listeners.is_empty() {
589                return Err(Error::NoSocksListenersFound())?;
590            }
591            self.socks_listener = Some(listeners.swap_remove(0));
592        }
593
594        let socks_listener = match self.socks_listener {
595            Some(socks_listener) => socks_listener,
596            None => unreachable!(),
597        };
598
599        // our target
600        let socks_target = match target.clone() {
601            TargetAddr::Socket(socket_addr) => socks::TargetAddr::Ip(socket_addr),
602            TargetAddr::Domain(domain_addr) => {
603                socks::TargetAddr::Domain(domain_addr.domain().to_string(), domain_addr.port())
604            }
605            TargetAddr::OnionService(OnionAddr::V3(OnionAddrV3 {
606                service_id,
607                virt_port,
608            })) => socks::TargetAddr::Domain(format!("{}.onion", service_id), virt_port),
609        };
610
611        // readwrite stream
612        let stream = match &circuit {
613            None => Socks5Stream::connect(socks_listener, socks_target),
614            Some(circuit) => {
615                if let Some(circuit) = self.circuit_tokens.get(circuit) {
616                    Socks5Stream::connect_with_password(
617                        socks_listener,
618                        socks_target,
619                        &circuit.username,
620                        &circuit.password,
621                    )
622                } else {
623                    return Err(Error::CircuitTokenInvalid())?;
624                }
625            }
626        }
627        .map_err(Error::Socks5ConnectionFailed)?;
628
629        Ok(OnionStream {
630            stream: stream.into_inner(),
631            local_addr: None,
632            peer_addr: Some(target),
633        })
634    }
635
636    // stand up an onion service and return an LegacyOnionListener
637    fn listener(
638        &mut self,
639        private_key: &Ed25519PrivateKey,
640        virt_port: u16,
641        authorized_clients: Option<&[X25519PublicKey]>,
642    ) -> Result<OnionListener, tor_provider::Error> {
643        if !self.bootstrapped {
644            return Err(Error::LegacyTorNotBootstrapped().into());
645        }
646
647        // try to bind to a local address, let OS pick our port
648        let socket_addr = SocketAddr::from(([127, 0, 0, 1], 0u16));
649        let listener = TcpListener::bind(socket_addr).map_err(Error::TcpListenerBindFailed)?;
650        let socket_addr = listener
651            .local_addr()
652            .map_err(Error::TcpListenerLocalAddrFailed)?;
653
654        let mut flags = AddOnionFlags {
655            discard_pk: true,
656            ..Default::default()
657        };
658        if authorized_clients.is_some() {
659            flags.v3_auth = true;
660        }
661
662        let onion_addr = OnionAddr::V3(OnionAddrV3::new(
663            V3OnionServiceId::from_private_key(private_key),
664            virt_port,
665        ));
666
667        // start onion service
668        let (_, service_id) = self
669            .controller
670            .add_onion(
671                Some(private_key),
672                &flags,
673                None,
674                virt_port,
675                Some(socket_addr),
676                authorized_clients,
677            )
678            .map_err(Error::AddOnionFailed)?;
679
680        let is_active = Arc::new(atomic::AtomicBool::new(true));
681        self.onion_services
682            .push((service_id, Arc::clone(&is_active)));
683
684        Ok(OnionListener::new(listener, onion_addr, is_active, |is_active| {
685            is_active.store(false, atomic::Ordering::Relaxed);
686        }))
687    }
688
689    fn generate_token(&mut self) -> CircuitToken {
690        let new_token = self.circuit_token_counter;
691        self.circuit_token_counter += 1;
692        self.circuit_tokens
693            .insert(new_token, LegacyCircuitToken::new());
694        new_token
695    }
696
697    fn release_token(&mut self, circuit_token: CircuitToken) {
698        self.circuit_tokens.remove(&circuit_token);
699    }
700}