Commits:
-
6752dfdd
by Elena at 2026-09-09T22:34:24+02:00
fixup! TB 44806: Implement the tor integration in Rust.
TB 44930: Implement the commands on the Rust control port
Created TorController
-
14c31b41
by Elena at 2026-09-09T22:34:24+02:00
fixup! TB 44806: Implement the tor integration in Rust.
TB 44930: Implement the commands on the Rust control port
Implemented the authenticate method.
-
304b6706
by Elena at 2026-09-09T22:34:24+02:00
fixup! TB 44806: Implement the tor integration in Rust.
TB 44930: Implement the commands on the Rust control port
Implemented a few simple commands.
23 changed files:
Changes:
Cargo.lock
| ... |
... |
@@ -8122,6 +8122,7 @@ name = "tor_provider" |
|
8122
|
8122
|
version = "0.1.0"
|
|
8123
|
8123
|
dependencies = [
|
|
8124
|
8124
|
"bytes",
|
|
|
8125
|
+ "hex",
|
|
8125
|
8126
|
"log",
|
|
8126
|
8127
|
"memchr",
|
|
8127
|
8128
|
"thiserror 2.0.12",
|
toolkit/components/tor-integration/tor_provider/Cargo.toml
| ... |
... |
@@ -6,6 +6,7 @@ edition = "2021" |
|
6
|
6
|
|
|
7
|
7
|
[dependencies]
|
|
8
|
8
|
bytes = "1.4.0"
|
|
|
9
|
+hex = "0.4.3"
|
|
9
|
10
|
log = "0.4"
|
|
10
|
11
|
memchr = "2.7.4"
|
|
11
|
12
|
thiserror = "2" |
toolkit/components/tor-integration/tor_provider/src/ctor/control_port/control_port.rs
| ... |
... |
@@ -15,8 +15,20 @@ use super::{ |
|
15
|
15
|
error::ControlPortError,
|
|
16
|
16
|
message_pump::{MessagePump, ReadAction},
|
|
17
|
17
|
};
|
|
|
18
|
+
|
|
18
|
19
|
use crate::ctor::reply_parser::{Reply, ReplyDispatcher, ReplyError};
|
|
19
|
20
|
|
|
|
21
|
+pub trait ControlPortInterface {
|
|
|
22
|
+ fn send_command(
|
|
|
23
|
+ &self,
|
|
|
24
|
+ command: Bytes,
|
|
|
25
|
+ handler: Box<dyn FnOnce(Result<Reply, ControlPortError>)>,
|
|
|
26
|
+ );
|
|
|
27
|
+ fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>);
|
|
|
28
|
+ fn close(&self) -> Result<(), ControlSocketError>;
|
|
|
29
|
+ fn set_close_handler(&self, cb: Box<dyn FnOnce()>);
|
|
|
30
|
+}
|
|
|
31
|
+
|
|
20
|
32
|
/// The lower-level part of the control port implementation.
|
|
21
|
33
|
/// It contains the logic for actually sending the command, and it hides its
|
|
22
|
34
|
/// reference-counted nature from actual consumers.
|
| ... |
... |
@@ -172,6 +184,9 @@ impl Drop for ControlPortInner { |
|
172
|
184
|
}
|
|
173
|
185
|
}
|
|
174
|
186
|
|
|
|
187
|
+// Wrap the inner type to make it clearer that its ownership is not supposed to
|
|
|
188
|
+// be shared even though it uses an Rc, but the use of the Rc is due to the
|
|
|
189
|
+// callback structure.
|
|
175
|
190
|
pub struct ControlPort(Rc<ControlPortInner>);
|
|
176
|
191
|
|
|
177
|
192
|
impl ControlPort {
|
| ... |
... |
@@ -179,11 +194,11 @@ impl ControlPort { |
|
179
|
194
|
pub fn new(socket: Box<dyn ControlSocket>) -> Result<Self, ControlSocketError> {
|
|
180
|
195
|
Ok(Self(ControlPortInner::new(Rc::from(socket))?))
|
|
181
|
196
|
}
|
|
|
197
|
+}
|
|
182
|
198
|
|
|
183
|
|
- // TODO: Keep only the methods speicifc to commands and remove this one
|
|
184
|
|
- // (tor-browser#44930).
|
|
|
199
|
+impl ControlPortInterface for ControlPort {
|
|
185
|
200
|
#[inline]
|
|
186
|
|
- pub fn send_command(
|
|
|
201
|
+ fn send_command(
|
|
187
|
202
|
&self,
|
|
188
|
203
|
command: Bytes,
|
|
189
|
204
|
handler: Box<dyn FnOnce(Result<Reply, ControlPortError>)>,
|
| ... |
... |
@@ -192,17 +207,17 @@ impl ControlPort { |
|
192
|
207
|
}
|
|
193
|
208
|
|
|
194
|
209
|
#[inline]
|
|
195
|
|
- pub fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>) {
|
|
|
210
|
+ fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>) {
|
|
196
|
211
|
self.0.set_async_handler(cb);
|
|
197
|
212
|
}
|
|
198
|
213
|
|
|
199
|
214
|
#[inline]
|
|
200
|
|
- pub fn close(&self) -> Result<(), ControlSocketError> {
|
|
|
215
|
+ fn close(&self) -> Result<(), ControlSocketError> {
|
|
201
|
216
|
self.0.close()
|
|
202
|
217
|
}
|
|
203
|
218
|
|
|
204
|
219
|
#[inline]
|
|
205
|
|
- pub fn set_close_handler(&self, cb: Box<dyn FnOnce()>) {
|
|
|
220
|
+ fn set_close_handler(&self, cb: Box<dyn FnOnce()>) {
|
|
206
|
221
|
self.0.set_close_handler(cb);
|
|
207
|
222
|
}
|
|
208
|
223
|
} |
toolkit/components/tor-integration/tor_provider/src/ctor/control_port/mod.rs
| ... |
... |
@@ -9,6 +9,6 @@ mod control_socket; |
|
9
|
9
|
mod error;
|
|
10
|
10
|
mod message_pump;
|
|
11
|
11
|
|
|
12
|
|
-pub use control_port::ControlPort;
|
|
|
12
|
+pub use control_port::*;
|
|
13
|
13
|
pub use control_socket::*;
|
|
14
|
14
|
pub use error::ControlPortError; |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/authenticate.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use super::Command;
|
|
|
7
|
+use crate::ctor::controller::{parsers::parse_ack, ControllerError};
|
|
|
8
|
+
|
|
|
9
|
+pub fn authenticate(password: &[u8]) -> Result<Command<u16>, ControllerError> {
|
|
|
10
|
+ const COMMAND: &str = "AUTHENTICATE";
|
|
|
11
|
+ let mut command = String::new();
|
|
|
12
|
+ command.reserve(COMMAND.len() + 1 + password.len() * 2 + 2);
|
|
|
13
|
+ command.push_str(COMMAND);
|
|
|
14
|
+ if !password.is_empty() {
|
|
|
15
|
+ command.push(' ');
|
|
|
16
|
+ command.push_str(hex::encode(password).as_str());
|
|
|
17
|
+ }
|
|
|
18
|
+ command.push_str("\r\n");
|
|
|
19
|
+ Ok(Command {
|
|
|
20
|
+ command,
|
|
|
21
|
+ handler: Box::new(parse_ack),
|
|
|
22
|
+ })
|
|
|
23
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/command.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use crate::ctor::{controller::ControllerError, reply_parser::Reply};
|
|
|
7
|
+
|
|
|
8
|
+pub struct Command<T> {
|
|
|
9
|
+ pub command: String,
|
|
|
10
|
+ pub handler: Box<dyn Fn(Reply) -> Result<T, ControllerError>>,
|
|
|
11
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/mod.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+mod authenticate;
|
|
|
7
|
+mod command;
|
|
|
8
|
+mod reset_conf;
|
|
|
9
|
+mod save_conf;
|
|
|
10
|
+mod set_events;
|
|
|
11
|
+mod signal;
|
|
|
12
|
+mod take_ownership;
|
|
|
13
|
+
|
|
|
14
|
+pub use authenticate::authenticate;
|
|
|
15
|
+pub use command::Command;
|
|
|
16
|
+pub use reset_conf::reset_owning_controller_process;
|
|
|
17
|
+pub use save_conf::save_conf;
|
|
|
18
|
+pub use set_events::set_events;
|
|
|
19
|
+pub use signal::signal_newnym;
|
|
|
20
|
+pub use take_ownership::take_ownership; |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/reset_conf.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use super::Command;
|
|
|
7
|
+use crate::ctor::controller::{parsers::parse_ack, ControllerError};
|
|
|
8
|
+
|
|
|
9
|
+pub fn reset_owning_controller_process() -> Result<Command<u16>, ControllerError> {
|
|
|
10
|
+ Ok(Command {
|
|
|
11
|
+ command: String::from("RESETCONF __OwningControllerProcess\r\n"),
|
|
|
12
|
+ handler: Box::new(parse_ack),
|
|
|
13
|
+ })
|
|
|
14
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/save_conf.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use super::Command;
|
|
|
7
|
+use crate::ctor::controller::{parsers::parse_ack, ControllerError};
|
|
|
8
|
+
|
|
|
9
|
+pub fn save_conf() -> Result<Command<u16>, ControllerError> {
|
|
|
10
|
+ Ok(Command {
|
|
|
11
|
+ command: String::from("SAVECONF\r\n"),
|
|
|
12
|
+ handler: Box::new(parse_ack),
|
|
|
13
|
+ })
|
|
|
14
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/set_events.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use super::Command;
|
|
|
7
|
+use crate::ctor::controller::{parsers::parse_ack, ControllerError};
|
|
|
8
|
+
|
|
|
9
|
+pub fn set_events(events: &[&str]) -> Result<Command<u16>, ControllerError> {
|
|
|
10
|
+ let mut command = String::from("SETEVENTS");
|
|
|
11
|
+ for e in events {
|
|
|
12
|
+ command.push(' ');
|
|
|
13
|
+ command.push_str(e);
|
|
|
14
|
+ }
|
|
|
15
|
+ command.push_str("\r\n");
|
|
|
16
|
+ Ok(Command {
|
|
|
17
|
+ command,
|
|
|
18
|
+ handler: Box::new(parse_ack),
|
|
|
19
|
+ })
|
|
|
20
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/signal.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use super::Command;
|
|
|
7
|
+use crate::ctor::controller::{parsers::parse_ack, ControllerError};
|
|
|
8
|
+
|
|
|
9
|
+pub fn signal_newnym() -> Result<Command<u16>, ControllerError> {
|
|
|
10
|
+ Ok(Command {
|
|
|
11
|
+ command: String::from("SIGNAL NEWNYM\r\n"),
|
|
|
12
|
+ handler: Box::new(parse_ack),
|
|
|
13
|
+ })
|
|
|
14
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/take_ownership.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use super::Command;
|
|
|
7
|
+use crate::ctor::controller::{parsers::parse_ack, ControllerError};
|
|
|
8
|
+
|
|
|
9
|
+pub fn take_ownership() -> Result<Command<u16>, ControllerError> {
|
|
|
10
|
+ Ok(Command {
|
|
|
11
|
+ command: String::from("TAKEOWNERSHIP\r\n"),
|
|
|
12
|
+ handler: Box::new(parse_ack),
|
|
|
13
|
+ })
|
|
|
14
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/controller.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use bytes::Bytes;
|
|
|
7
|
+
|
|
|
8
|
+use super::{
|
|
|
9
|
+ commands::{self, Command},
|
|
|
10
|
+ error::ControllerError,
|
|
|
11
|
+};
|
|
|
12
|
+use crate::ctor::{
|
|
|
13
|
+ control_port::{ControlPortInterface, ControlSocketError},
|
|
|
14
|
+ reply_parser::Reply,
|
|
|
15
|
+};
|
|
|
16
|
+
|
|
|
17
|
+/// A controller for a tor daemon.
|
|
|
18
|
+///
|
|
|
19
|
+/// TorController wraps an implementation of a ControlPortInterface (the lower-level transport that
|
|
|
20
|
+/// exposes a command-based interface) and exposes a set of async, callback-based methods.
|
|
|
21
|
+/// Every method correspond to a control port command.
|
|
|
22
|
+///
|
|
|
23
|
+/// Callers should authenticate before issuing any other command, even when authentication is
|
|
|
24
|
+/// disabled on the server side, and close the connection when done with it.
|
|
|
25
|
+/// The tor daemon can be configured so that this controller is its owner, so that when it closes
|
|
|
26
|
+/// the connection, the remote daemon shuts down.
|
|
|
27
|
+pub struct TorController<CP: ControlPortInterface>(CP);
|
|
|
28
|
+
|
|
|
29
|
+impl<CP: ControlPortInterface> TorController<CP> {
|
|
|
30
|
+ #[inline]
|
|
|
31
|
+ pub fn new(control_port: CP) -> Self {
|
|
|
32
|
+ Self(control_port)
|
|
|
33
|
+ }
|
|
|
34
|
+
|
|
|
35
|
+ #[inline]
|
|
|
36
|
+ pub fn close(&self) -> Result<(), ControllerError> {
|
|
|
37
|
+ match self.0.close() {
|
|
|
38
|
+ Ok(()) => Ok(()),
|
|
|
39
|
+ Err(ControlSocketError::ConnectionClosed) => Ok(()),
|
|
|
40
|
+ Err(ControlSocketError::ImplementationError(rv)) => {
|
|
|
41
|
+ Err(ControllerError::ConnectionError(rv))
|
|
|
42
|
+ }
|
|
|
43
|
+ }
|
|
|
44
|
+ }
|
|
|
45
|
+
|
|
|
46
|
+ fn send_command<T>(
|
|
|
47
|
+ &self,
|
|
|
48
|
+ command: Result<Command<T>, ControllerError>,
|
|
|
49
|
+ handler: Box<dyn FnOnce(Result<T, ControllerError>)>,
|
|
|
50
|
+ ) where
|
|
|
51
|
+ T: 'static,
|
|
|
52
|
+ {
|
|
|
53
|
+ let Command {
|
|
|
54
|
+ command,
|
|
|
55
|
+ handler: command_handler,
|
|
|
56
|
+ } = match command {
|
|
|
57
|
+ Ok(c) => c,
|
|
|
58
|
+ Err(e) => {
|
|
|
59
|
+ handler(Err(e));
|
|
|
60
|
+ return;
|
|
|
61
|
+ }
|
|
|
62
|
+ };
|
|
|
63
|
+ self.0.send_command(
|
|
|
64
|
+ command.into(),
|
|
|
65
|
+ Box::new(move |r| match r {
|
|
|
66
|
+ Ok(reply) => handler(command_handler(reply)),
|
|
|
67
|
+ Err(e) => handler(Err(e.into())),
|
|
|
68
|
+ }),
|
|
|
69
|
+ );
|
|
|
70
|
+ }
|
|
|
71
|
+
|
|
|
72
|
+ // Setup
|
|
|
73
|
+
|
|
|
74
|
+ /// Authenticate to the tor daemon.
|
|
|
75
|
+ /// Notice that a failure in the authentication makes the connection close.
|
|
|
76
|
+ pub fn authenticate(
|
|
|
77
|
+ &self,
|
|
|
78
|
+ password: &[u8],
|
|
|
79
|
+ handler: Box<dyn FnOnce(Result<u16, ControllerError>)>,
|
|
|
80
|
+ ) {
|
|
|
81
|
+ self.send_command(commands::authenticate(password), handler);
|
|
|
82
|
+ }
|
|
|
83
|
+
|
|
|
84
|
+ pub fn take_ownership(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) {
|
|
|
85
|
+ self.send_command(commands::take_ownership(), handler);
|
|
|
86
|
+ }
|
|
|
87
|
+
|
|
|
88
|
+ pub fn reset_owning_controller_process(
|
|
|
89
|
+ &self,
|
|
|
90
|
+ handler: Box<dyn FnOnce(Result<u16, ControllerError>)>,
|
|
|
91
|
+ ) {
|
|
|
92
|
+ self.send_command(commands::reset_owning_controller_process(), handler);
|
|
|
93
|
+ }
|
|
|
94
|
+
|
|
|
95
|
+ pub fn set_events(
|
|
|
96
|
+ &self,
|
|
|
97
|
+ events: &[&str],
|
|
|
98
|
+ handler: Box<dyn FnOnce(Result<u16, ControllerError>)>,
|
|
|
99
|
+ ) {
|
|
|
100
|
+ self.send_command(commands::set_events(events), handler);
|
|
|
101
|
+ }
|
|
|
102
|
+
|
|
|
103
|
+ // Connection management
|
|
|
104
|
+
|
|
|
105
|
+ pub fn save_conf(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) {
|
|
|
106
|
+ self.send_command(commands::save_conf(), handler);
|
|
|
107
|
+ }
|
|
|
108
|
+
|
|
|
109
|
+ // Circuit display
|
|
|
110
|
+
|
|
|
111
|
+ // Onion authentication
|
|
|
112
|
+
|
|
|
113
|
+ // Miscellaneous
|
|
|
114
|
+
|
|
|
115
|
+ pub fn signal_newnym(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) {
|
|
|
116
|
+ self.send_command(commands::signal_newnym(), handler);
|
|
|
117
|
+ }
|
|
|
118
|
+}
|
|
|
119
|
+
|
|
|
120
|
+// TODO: Remove once we merge all parts of tor-browser#44930.
|
|
|
121
|
+impl<CP: ControlPortInterface> TorController<CP> {
|
|
|
122
|
+ #[inline]
|
|
|
123
|
+ pub fn send_raw_command(
|
|
|
124
|
+ &self,
|
|
|
125
|
+ command: Bytes,
|
|
|
126
|
+ handler: Box<dyn FnOnce(Result<Reply, ControllerError>)>,
|
|
|
127
|
+ ) {
|
|
|
128
|
+ self.0.send_command(
|
|
|
129
|
+ command,
|
|
|
130
|
+ Box::new(|res| handler(res.map_err(|e| ControllerError::from(e)))),
|
|
|
131
|
+ );
|
|
|
132
|
+ }
|
|
|
133
|
+
|
|
|
134
|
+ #[inline]
|
|
|
135
|
+ pub fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>) {
|
|
|
136
|
+ self.0.set_async_handler(cb);
|
|
|
137
|
+ }
|
|
|
138
|
+
|
|
|
139
|
+ #[inline]
|
|
|
140
|
+ pub fn set_close_handler(&self, cb: Box<dyn FnOnce()>) {
|
|
|
141
|
+ self.0.set_close_handler(cb);
|
|
|
142
|
+ }
|
|
|
143
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/error.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use thiserror::Error;
|
|
|
7
|
+
|
|
|
8
|
+use crate::ctor::{
|
|
|
9
|
+ control_port::ControlPortError,
|
|
|
10
|
+ reply_parser::{Reply, ReplyError},
|
|
|
11
|
+};
|
|
|
12
|
+
|
|
|
13
|
+#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
|
|
14
|
+pub enum ControllerError {
|
|
|
15
|
+ #[error("connection error: {0:#x}")]
|
|
|
16
|
+ ConnectionError(u32),
|
|
|
17
|
+ #[error("protocol violation: {0}")]
|
|
|
18
|
+ ProtocolError(#[from] ReplyError),
|
|
|
19
|
+ #[error("unsuccessful command ({code}): {message}")]
|
|
|
20
|
+ TorError {
|
|
|
21
|
+ code: u16,
|
|
|
22
|
+ // Notice: tor does not dive any guarantee about charsets.
|
|
|
23
|
+ // However, we expect errors to be ASCII strings, and they are used only
|
|
|
24
|
+ // for logs, they will never be directly user-facing.
|
|
|
25
|
+ message: String,
|
|
|
26
|
+ },
|
|
|
27
|
+ #[error("the reply contains lines with mixed codes")]
|
|
|
28
|
+ MixedCodes,
|
|
|
29
|
+ #[error("the reply does not match the expected format: {0}")]
|
|
|
30
|
+ WrongFormat(String),
|
|
|
31
|
+ #[error("the requested key {0} was not found")]
|
|
|
32
|
+ KeyNotFound(String),
|
|
|
33
|
+ #[error("malformed reply: {0}")]
|
|
|
34
|
+ MalformedReply(String),
|
|
|
35
|
+}
|
|
|
36
|
+
|
|
|
37
|
+impl ControllerError {
|
|
|
38
|
+ pub(super) fn from_reply(reply: &Reply) -> Option<Self> {
|
|
|
39
|
+ if reply.end_line().is_error() {
|
|
|
40
|
+ Some(Self::TorError {
|
|
|
41
|
+ code: reply.end_line().code,
|
|
|
42
|
+ message: String::from_utf8_lossy(&reply.end_line().line).into_owned(),
|
|
|
43
|
+ })
|
|
|
44
|
+ } else {
|
|
|
45
|
+ None
|
|
|
46
|
+ }
|
|
|
47
|
+ }
|
|
|
48
|
+}
|
|
|
49
|
+
|
|
|
50
|
+impl From<ControlPortError> for ControllerError {
|
|
|
51
|
+ fn from(value: ControlPortError) -> Self {
|
|
|
52
|
+ match value {
|
|
|
53
|
+ ControlPortError::ConnectionError(rv) => ControllerError::ConnectionError(rv),
|
|
|
54
|
+ ControlPortError::ProtocolError(err) => ControllerError::ProtocolError(err),
|
|
|
55
|
+ }
|
|
|
56
|
+ }
|
|
|
57
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/mod.rs
| ... |
... |
@@ -3,47 +3,10 @@ |
|
3
|
3
|
// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
4
|
4
|
// copied, modified, or distributed except according to those terms.
|
|
5
|
5
|
|
|
6
|
|
-mod escape;
|
|
7
|
|
-mod unescape;
|
|
|
6
|
+mod commands;
|
|
|
7
|
+mod controller;
|
|
|
8
|
+mod error;
|
|
|
9
|
+mod parsers;
|
|
8
|
10
|
|
|
9
|
|
-use escape::*;
|
|
10
|
|
-use unescape::*;
|
|
11
|
|
-
|
|
12
|
|
-#[cfg(test)]
|
|
13
|
|
-mod tests {
|
|
14
|
|
- use super::*;
|
|
15
|
|
-
|
|
16
|
|
- fn round_trip(buf: &[u8]) {
|
|
17
|
|
- let mut escaped = String::new();
|
|
18
|
|
- tor_escape_into(buf, &mut escaped);
|
|
19
|
|
- assert_eq!(&*tor_unescape(escaped.as_bytes()).unwrap(), buf);
|
|
20
|
|
- }
|
|
21
|
|
-
|
|
22
|
|
- #[test]
|
|
23
|
|
- fn round_trip_simple() {
|
|
24
|
|
- round_trip(b"test");
|
|
25
|
|
- }
|
|
26
|
|
-
|
|
27
|
|
- #[test]
|
|
28
|
|
- fn round_trip_empty() {
|
|
29
|
|
- round_trip(b"");
|
|
30
|
|
- }
|
|
31
|
|
-
|
|
32
|
|
- #[test]
|
|
33
|
|
- fn round_trip_special_chars() {
|
|
34
|
|
- round_trip(b"'\"\\\r\n\t");
|
|
35
|
|
- }
|
|
36
|
|
-
|
|
37
|
|
- #[test]
|
|
38
|
|
- fn round_trip_non_utf8() {
|
|
39
|
|
- round_trip(b"\xF5\xFF\xFE");
|
|
40
|
|
- }
|
|
41
|
|
-
|
|
42
|
|
- #[test]
|
|
43
|
|
- fn round_trip_all_bytes() {
|
|
44
|
|
- // Every possible byte value, escaped then unescaped,
|
|
45
|
|
- // must come back unchanged.
|
|
46
|
|
- let all_bytes: Vec<u8> = (0..=255).collect();
|
|
47
|
|
- round_trip(&all_bytes);
|
|
48
|
|
- }
|
|
49
|
|
-} |
|
|
11
|
+pub use controller::*;
|
|
|
12
|
+pub use error::*; |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/ack.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use crate::ctor::{reply_parser::Reply, ControllerError};
|
|
|
7
|
+
|
|
|
8
|
+/// Check whether a reply contains a successful reply code and return it.
|
|
|
9
|
+/// To be used with commands we only want to check whether they were successful.
|
|
|
10
|
+/// It can be used also for notifications (and it will return their code), but
|
|
|
11
|
+/// it does not make much sense to do it.
|
|
|
12
|
+pub fn parse_ack(reply: Reply) -> Result<u16, ControllerError> {
|
|
|
13
|
+ // Ignore any detail line on purpose to allow compatbility with future
|
|
|
14
|
+ // protocol/command versions.
|
|
|
15
|
+ match ControllerError::from_reply(&reply) {
|
|
|
16
|
+ Some(e) => Err(e),
|
|
|
17
|
+ None => Ok(reply.end_line().code),
|
|
|
18
|
+ }
|
|
|
19
|
+}
|
|
|
20
|
+
|
|
|
21
|
+#[cfg(test)]
|
|
|
22
|
+mod tests {
|
|
|
23
|
+ use super::*;
|
|
|
24
|
+ use crate::ctor::reply_parser::make_reply;
|
|
|
25
|
+
|
|
|
26
|
+ #[test]
|
|
|
27
|
+ fn successful() {
|
|
|
28
|
+ assert_eq!(parse_ack(make_reply(b"250 OK\r\n")).unwrap(), 250);
|
|
|
29
|
+ assert_eq!(
|
|
|
30
|
+ parse_ack(make_reply(b"251 Also successful\r\n")).unwrap(),
|
|
|
31
|
+ 251,
|
|
|
32
|
+ );
|
|
|
33
|
+ assert_eq!(
|
|
|
34
|
+ parse_ack(make_reply(b"399 Yet another one\r\n")).unwrap(),
|
|
|
35
|
+ 399,
|
|
|
36
|
+ );
|
|
|
37
|
+ assert_eq!(parse_ack(make_reply(b"650 Notification\r\n")).unwrap(), 650);
|
|
|
38
|
+ }
|
|
|
39
|
+
|
|
|
40
|
+ #[test]
|
|
|
41
|
+ fn error_code() {
|
|
|
42
|
+ assert_eq!(
|
|
|
43
|
+ parse_ack(make_reply(b"400 An error\r\n")).unwrap_err(),
|
|
|
44
|
+ ControllerError::TorError {
|
|
|
45
|
+ code: 400,
|
|
|
46
|
+ message: String::from("An error"),
|
|
|
47
|
+ },
|
|
|
48
|
+ );
|
|
|
49
|
+ assert_eq!(
|
|
|
50
|
+ parse_ack(make_reply(b"599 Last error\r\n")).unwrap_err(),
|
|
|
51
|
+ ControllerError::TorError {
|
|
|
52
|
+ code: 599,
|
|
|
53
|
+ message: String::from("Last error"),
|
|
|
54
|
+ },
|
|
|
55
|
+ );
|
|
|
56
|
+ }
|
|
|
57
|
+
|
|
|
58
|
+ #[test]
|
|
|
59
|
+ fn invalid_unicode() {
|
|
|
60
|
+ assert_eq!(parse_ack(make_reply(b"250 \xFD\r\n")).unwrap(), 250);
|
|
|
61
|
+ assert_eq!(
|
|
|
62
|
+ parse_ack(make_reply(b"252-Line\r\n252 \xFD\r\n")).unwrap(),
|
|
|
63
|
+ 252
|
|
|
64
|
+ );
|
|
|
65
|
+ assert_eq!(
|
|
|
66
|
+ parse_ack(make_reply(b"500 Invalid \xFD codepoint\r\n")).unwrap_err(),
|
|
|
67
|
+ ControllerError::TorError {
|
|
|
68
|
+ code: 500,
|
|
|
69
|
+ message: String::from("Invalid \u{FFFD} codepoint"),
|
|
|
70
|
+ },
|
|
|
71
|
+ );
|
|
|
72
|
+ }
|
|
|
73
|
+
|
|
|
74
|
+ #[test]
|
|
|
75
|
+ fn details_ignored() {
|
|
|
76
|
+ assert_eq!(
|
|
|
77
|
+ parse_ack(make_reply(b"250-Details\r\n250 OK\r\n")).unwrap(),
|
|
|
78
|
+ 250
|
|
|
79
|
+ );
|
|
|
80
|
+ assert_eq!(
|
|
|
81
|
+ parse_ack(make_reply(
|
|
|
82
|
+ b"255-Changing status code\r\n260 Does not impact\r\n"
|
|
|
83
|
+ ))
|
|
|
84
|
+ .unwrap(),
|
|
|
85
|
+ 260
|
|
|
86
|
+ );
|
|
|
87
|
+ assert_eq!(
|
|
|
88
|
+ parse_ack(make_reply(b"550-Details with error code\r\n250 OK\r\n")).unwrap(),
|
|
|
89
|
+ 250
|
|
|
90
|
+ );
|
|
|
91
|
+ assert_eq!(
|
|
|
92
|
+ parse_ack(make_reply(b"250-Succesful details\r\n450 Error status\r\n")).unwrap_err(),
|
|
|
93
|
+ ControllerError::TorError {
|
|
|
94
|
+ code: 450,
|
|
|
95
|
+ message: String::from("Error status"),
|
|
|
96
|
+ },
|
|
|
97
|
+ );
|
|
|
98
|
+ }
|
|
|
99
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/escape.rs
→
toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/escape.rs
toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/mod.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+mod ack;
|
|
|
7
|
+mod escape;
|
|
|
8
|
+mod unescape;
|
|
|
9
|
+
|
|
|
10
|
+#[cfg(test)]
|
|
|
11
|
+mod tests;
|
|
|
12
|
+
|
|
|
13
|
+pub use ack::parse_ack;
|
|
|
14
|
+pub use escape::tor_escape_into;
|
|
|
15
|
+pub use unescape::tor_unescape; |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/tests.rs
|
|
1
|
+// Licensed under the Apache License, Version 2.0,
|
|
|
2
|
+// <http://apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
3
|
+// <http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
|
4
|
+// copied, modified, or distributed except according to those terms.
|
|
|
5
|
+
|
|
|
6
|
+use super::{tor_escape_into, tor_unescape};
|
|
|
7
|
+
|
|
|
8
|
+fn round_trip(buf: &[u8]) {
|
|
|
9
|
+ let mut escaped = String::new();
|
|
|
10
|
+ tor_escape_into(buf, &mut escaped);
|
|
|
11
|
+ assert_eq!(&*tor_unescape(escaped.as_bytes()).unwrap(), buf);
|
|
|
12
|
+}
|
|
|
13
|
+
|
|
|
14
|
+#[test]
|
|
|
15
|
+fn round_trip_simple() {
|
|
|
16
|
+ round_trip(b"test");
|
|
|
17
|
+}
|
|
|
18
|
+
|
|
|
19
|
+#[test]
|
|
|
20
|
+fn round_trip_empty() {
|
|
|
21
|
+ round_trip(b"");
|
|
|
22
|
+}
|
|
|
23
|
+
|
|
|
24
|
+#[test]
|
|
|
25
|
+fn round_trip_special_chars() {
|
|
|
26
|
+ round_trip(b"'\"\\\r\n\t");
|
|
|
27
|
+}
|
|
|
28
|
+
|
|
|
29
|
+#[test]
|
|
|
30
|
+fn round_trip_non_utf8() {
|
|
|
31
|
+ round_trip(b"\xF5\xFF\xFE");
|
|
|
32
|
+}
|
|
|
33
|
+
|
|
|
34
|
+#[test]
|
|
|
35
|
+fn round_trip_all_bytes() {
|
|
|
36
|
+ // Every possible byte value, escaped then unescaped,
|
|
|
37
|
+ // must come back unchanged.
|
|
|
38
|
+ let all_bytes: Vec<u8> = (0..=255).collect();
|
|
|
39
|
+ round_trip(&all_bytes);
|
|
|
40
|
+} |
toolkit/components/tor-integration/tor_provider/src/ctor/controller/unescape.rs
→
toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/unescape.rs
toolkit/components/tor-integration/tor_provider/src/ctor/mod.rs
| ... |
... |
@@ -7,5 +7,6 @@ mod control_port; |
|
7
|
7
|
mod controller;
|
|
8
|
8
|
mod reply_parser;
|
|
9
|
9
|
|
|
10
|
|
-pub use control_port::{ControlPort, ControlPortError, ControlSocket, ControlSocketError};
|
|
|
10
|
+pub use control_port::{ControlPort, ControlSocket, ControlSocketError};
|
|
|
11
|
+pub use controller::*;
|
|
11
|
12
|
pub use reply_parser::{ReplyDispatcher, ReplyError}; |
toolkit/components/tor-integration/tor_provider/src/ctor/reply_parser/line.rs
| ... |
... |
@@ -9,6 +9,7 @@ use std::{ |
|
9
|
9
|
ops::Deref,
|
|
10
|
10
|
};
|
|
11
|
11
|
|
|
|
12
|
+pub const ERROR_START: u16 = 400;
|
|
12
|
13
|
pub const ASYNC_START: u16 = 600;
|
|
13
|
14
|
|
|
14
|
15
|
/// An enum to represent MidReplyLine and DataReplyLine form the control port
|
| ... |
... |
@@ -110,6 +111,11 @@ pub struct EndReplyLine { |
|
110
|
111
|
}
|
|
111
|
112
|
|
|
112
|
113
|
impl EndReplyLine {
|
|
|
114
|
+ /// Tells whether the line is an error.
|
|
|
115
|
+ pub fn is_error(&self) -> bool {
|
|
|
116
|
+ self.code >= ERROR_START && self.code < ASYNC_START
|
|
|
117
|
+ }
|
|
|
118
|
+
|
|
113
|
119
|
/// Tells whether the line is part of an async reply.
|
|
114
|
120
|
pub fn is_async(&self) -> bool {
|
|
115
|
121
|
self.code >= ASYNC_START
|
| ... |
... |
@@ -146,29 +152,106 @@ mod tests { |
|
146
|
152
|
}
|
|
147
|
153
|
|
|
148
|
154
|
#[test]
|
|
149
|
|
- fn is_async() {
|
|
|
155
|
+ fn check_type() {
|
|
150
|
156
|
{
|
|
151
|
157
|
let r = EndReplyLine {
|
|
152
|
158
|
code: 250,
|
|
153
|
159
|
line: Bytes::from_static(b"OK"),
|
|
154
|
160
|
};
|
|
|
161
|
+ assert!(!r.is_error());
|
|
|
162
|
+ assert!(!r.is_async());
|
|
|
163
|
+ }
|
|
|
164
|
+ {
|
|
|
165
|
+ let r = EndReplyLine {
|
|
|
166
|
+ code: 399,
|
|
|
167
|
+ line: Bytes::from_static(b"Success"),
|
|
|
168
|
+ };
|
|
|
169
|
+ assert!(!r.is_error());
|
|
155
|
170
|
assert!(!r.is_async());
|
|
156
|
171
|
}
|
|
|
172
|
+ {
|
|
|
173
|
+ let r = EndReplyLine {
|
|
|
174
|
+ code: 400,
|
|
|
175
|
+ line: Bytes::from_static(b"Temporary error"),
|
|
|
176
|
+ };
|
|
|
177
|
+ assert!(r.is_error());
|
|
|
178
|
+ assert!(!r.is_async());
|
|
|
179
|
+ }
|
|
|
180
|
+ {
|
|
|
181
|
+ let r = EndReplyLine {
|
|
|
182
|
+ code: 500,
|
|
|
183
|
+ line: Bytes::from_static(b"Permanent error"),
|
|
|
184
|
+ };
|
|
|
185
|
+ assert!(r.is_error());
|
|
|
186
|
+ assert!(!r.is_async());
|
|
|
187
|
+ }
|
|
|
188
|
+ {
|
|
|
189
|
+ let r = EndReplyLine {
|
|
|
190
|
+ code: 599,
|
|
|
191
|
+ line: Bytes::from_static(b"Yet another error"),
|
|
|
192
|
+ };
|
|
|
193
|
+ assert!(r.is_error());
|
|
|
194
|
+ assert!(!r.is_async());
|
|
|
195
|
+ }
|
|
|
196
|
+ {
|
|
|
197
|
+ let r = EndReplyLine {
|
|
|
198
|
+ code: 600,
|
|
|
199
|
+ line: Bytes::from_static(b"Notification"),
|
|
|
200
|
+ };
|
|
|
201
|
+ assert!(!r.is_error());
|
|
|
202
|
+ assert!(r.is_async());
|
|
|
203
|
+ }
|
|
157
|
204
|
{
|
|
158
|
205
|
let r = EndReplyLine {
|
|
159
|
206
|
code: 650,
|
|
160
|
207
|
line: Bytes::from_static(b"CIRC BUILT"),
|
|
161
|
208
|
};
|
|
|
209
|
+ assert!(!r.is_error());
|
|
162
|
210
|
assert!(r.is_async());
|
|
163
|
211
|
}
|
|
164
|
212
|
|
|
165
|
213
|
{
|
|
166
|
214
|
let r = DetailReplyLine::MidReplyLine {
|
|
167
|
215
|
code: 250,
|
|
168
|
|
- line: Bytes::from_static(b"key=value"),
|
|
|
216
|
+ line: Bytes::from_static(b"OK"),
|
|
|
217
|
+ };
|
|
|
218
|
+ assert!(!r.is_async());
|
|
|
219
|
+ }
|
|
|
220
|
+ {
|
|
|
221
|
+ let r = DetailReplyLine::MidReplyLine {
|
|
|
222
|
+ code: 399,
|
|
|
223
|
+ line: Bytes::from_static(b"Success"),
|
|
|
224
|
+ };
|
|
|
225
|
+ assert!(!r.is_async());
|
|
|
226
|
+ }
|
|
|
227
|
+ {
|
|
|
228
|
+ let r = DetailReplyLine::MidReplyLine {
|
|
|
229
|
+ code: 400,
|
|
|
230
|
+ line: Bytes::from_static(b"Temporary error"),
|
|
|
231
|
+ };
|
|
|
232
|
+ assert!(!r.is_async());
|
|
|
233
|
+ }
|
|
|
234
|
+ {
|
|
|
235
|
+ let r = DetailReplyLine::MidReplyLine {
|
|
|
236
|
+ code: 500,
|
|
|
237
|
+ line: Bytes::from_static(b"Permanent error"),
|
|
169
|
238
|
};
|
|
170
|
239
|
assert!(!r.is_async());
|
|
171
|
240
|
}
|
|
|
241
|
+ {
|
|
|
242
|
+ let r = DetailReplyLine::MidReplyLine {
|
|
|
243
|
+ code: 599,
|
|
|
244
|
+ line: Bytes::from_static(b"Yet another error"),
|
|
|
245
|
+ };
|
|
|
246
|
+ assert!(!r.is_async());
|
|
|
247
|
+ }
|
|
|
248
|
+ {
|
|
|
249
|
+ let r = DetailReplyLine::MidReplyLine {
|
|
|
250
|
+ code: 600,
|
|
|
251
|
+ line: Bytes::from_static(b"Notification"),
|
|
|
252
|
+ };
|
|
|
253
|
+ assert!(r.is_async());
|
|
|
254
|
+ }
|
|
172
|
255
|
{
|
|
173
|
256
|
let r = DetailReplyLine::MidReplyLine {
|
|
174
|
257
|
code: 650,
|
toolkit/components/tor-integration/tor_service/src/control_port.rs
| ... |
... |
@@ -3,9 +3,9 @@ |
|
3
|
3
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4
|
4
|
|
|
5
|
5
|
use nserror::nsresult;
|
|
6
|
|
-use nserror::{NS_ERROR_NOT_CONNECTED, NS_OK};
|
|
|
6
|
+use nserror::{NS_ERROR_NOT_CONNECTED, NS_ERROR_UNEXPECTED, NS_OK};
|
|
7
|
7
|
use nsstring::{nsACString, nsCString};
|
|
8
|
|
-use tor_provider::ctor::{ControlPort, ControlSocketError};
|
|
|
8
|
+use tor_provider::ctor::{ControlPort, ControlSocketError, ControllerError, TorController};
|
|
9
|
9
|
use xpcom::interfaces::{nsIFile, torITorControlPortReceiver, torITorMessageHandler};
|
|
10
|
10
|
use xpcom::RefPtr;
|
|
11
|
11
|
|
| ... |
... |
@@ -17,7 +17,7 @@ use super::control_socket::ControlSocketXpcom; |
|
17
|
17
|
|
|
18
|
18
|
#[xpcom(implement(torITorControlPort), atomic)]
|
|
19
|
19
|
pub struct ControlPortXpcom {
|
|
20
|
|
- control_port: ControlPort,
|
|
|
20
|
+ control_port: TorController<ControlPort>,
|
|
21
|
21
|
}
|
|
22
|
22
|
|
|
23
|
23
|
impl ControlPortXpcom {
|
| ... |
... |
@@ -30,7 +30,7 @@ impl ControlPortXpcom { |
|
30
|
30
|
}
|
|
31
|
31
|
|
|
32
|
32
|
fn new(socket: Box<ControlSocketXpcom>) -> Result<RefPtr<Self>, nsresult> {
|
|
33
|
|
- let control_port = ControlPort::new(socket).map_err(Self::map_err)?;
|
|
|
33
|
+ let control_port = TorController::new(ControlPort::new(socket).map_err(Self::map_err)?);
|
|
34
|
34
|
Ok(Self::allocate(InitControlPortXpcom { control_port }))
|
|
35
|
35
|
}
|
|
36
|
36
|
|
| ... |
... |
@@ -78,7 +78,7 @@ impl ControlPortXpcom { |
|
78
|
78
|
command.extend_from_slice(b"\r\n");
|
|
79
|
79
|
}
|
|
80
|
80
|
let handler = RefPtr::new(handler);
|
|
81
|
|
- self.control_port.send_command(
|
|
|
81
|
+ self.control_port.send_raw_command(
|
|
82
|
82
|
command.into(),
|
|
83
|
83
|
Box::new(move |reply| {
|
|
84
|
84
|
let mut buf = Vec::new();
|
| ... |
... |
@@ -110,7 +110,10 @@ impl ControlPortXpcom { |
|
110
|
110
|
|
|
111
|
111
|
xpcom_method!(close => Close());
|
|
112
|
112
|
pub fn close(&self) -> Result<(), nsresult> {
|
|
113
|
|
- self.control_port.close().map_err(Self::map_err)
|
|
|
113
|
+ self.control_port.close().map_err(|e| match e {
|
|
|
114
|
+ ControllerError::ConnectionError(rv) => nsresult(rv),
|
|
|
115
|
+ _ => NS_ERROR_UNEXPECTED,
|
|
|
116
|
+ })
|
|
114
|
117
|
}
|
|
115
|
118
|
|
|
116
|
119
|
fn map_err(e: ControlSocketError) -> nsresult {
|
|