1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
//! Support for identifying a particular transport.
//!
//! A "transport" is a mechanism to connect to a relay on the Tor network and
//! make a `Channel`. Currently, two types of transports exist: the "built-in"
//! transport, which uses TLS over TCP, and various anti-censorship "pluggable
//! transports", which use TLS over other protocols to avoid detection by
//! censors.

use std::fmt::{self, Display};
use std::str::FromStr;

#[cfg(feature = "pt-client")]
use std::sync::Arc;

/// Identify a type of Transport.
///
/// If this crate is compiled with the `pt-client` feature, this type can
/// support pluggable transports; otherwise, only the built-in transport type is
/// supported.
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
pub struct TransportId(Inner);

/// Helper type to implement [`TransportId`].
///
/// This is a separate type so that TransportId can be opaque.
#[derive(Debug, Clone, Eq, PartialEq, Hash, educe::Educe)]
#[educe(Default)]
enum Inner {
    /// The built-in transport type.
    #[educe(Default)]
    BuiltIn,

    /// A pluggable transport type, specified by its name.
    #[cfg(feature = "pt-client")]
    Pluggable(PtTransportName),
}

/// Pluggable transport name
///
/// The name for a pluggable transport.
/// The name has been syntax checked.
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
#[cfg(feature = "pt-client")]
pub struct PtTransportName(String);

#[cfg(feature = "pt-client")]
impl FromStr for PtTransportName {
    type Err = TransportIdError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.to_string().try_into()
    }
}

#[cfg(feature = "pt-client")]
impl TryFrom<String> for PtTransportName {
    type Error = TransportIdError;

    fn try_from(s: String) -> Result<PtTransportName, Self::Error> {
        if is_well_formed_id(&s) {
            Ok(PtTransportName(s))
        } else {
            Err(TransportIdError::BadId(s))
        }
    }
}

#[cfg(feature = "pt-client")]
impl AsRef<str> for PtTransportName {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

#[cfg(feature = "pt-client")]
impl PtTransportName {
    /// Return the name as a `String`
    pub fn into_inner(self) -> String {
        self.0
    }
}

#[cfg(feature = "pt-client")]
impl Display for PtTransportName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

/// This identifier is used to indicate the built-in transport.
//
// Actual pluggable transport names are restricted to the syntax of C identifiers.
// This string deliberately is not in that syntax so as to avoid clashes.
const BUILT_IN_ID: &str = "<none>";

impl FromStr for TransportId {
    type Err = TransportIdError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == BUILT_IN_ID {
            return Ok(TransportId(Inner::BuiltIn));
        };

        #[cfg(feature = "pt-client")]
        {
            let name: PtTransportName = s.parse()?;
            Ok(TransportId(Inner::Pluggable(name)))
        }

        #[cfg(not(feature = "pt-client"))]
        Err(TransportIdError::NoSupport)
    }
}

impl Display for TransportId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.0 {
            Inner::BuiltIn => write!(f, "{}", BUILT_IN_ID),
            #[cfg(feature = "pt-client")]
            Inner::Pluggable(name) => write!(f, "{}", name),
        }
    }
}

/// Return true if `s` is a well-formed transport ID.
///
/// According to the specification, a well-formed transport ID follows the same
/// rules as a C99 identifier: It must follow the regular expression
/// `[a-zA-Z_][a-zA-Z0-9_]*`.
#[cfg(feature = "pt-client")]
fn is_well_formed_id(s: &str) -> bool {
    // It's okay to use a bytes iterator, since non-ascii strings are not
    // allowed.
    let mut bytes = s.bytes();

    if let Some(first) = bytes.next() {
        (first.is_ascii_alphabetic() || first == b'_')
            && bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_')
            && !s.eq_ignore_ascii_case("bridge")
    } else {
        false
    }
}

/// An error related to parsing a TransportId.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TransportIdError {
    /// Arti was compiled without client-side pluggable transport support, and
    /// we tried to use a pluggable transport.
    #[error("Not compiled with pluggable transport support")]
    NoSupport,

    /// Tried to parse a pluggable transport whose name was not well-formed.
    #[error("{0:?} is not a valid pluggable transport ID.")]
    BadId(String),
}

impl TransportId {
    /// Return true if this is the built-in transport.
    pub fn is_builtin(&self) -> bool {
        self.0 == Inner::BuiltIn
    }
}

/// This identifier is used to indicate no transport address.
const NONE_ADDR: &str = "<none>";

/// An address that an be passed to a pluggable transport to tell it where to
/// connect (typically, to a bridge).
///
/// Not every transport accepts all kinds of addresses.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PtTargetAddr {
    /// An IP address and port for a Tor relay.
    ///
    /// This is the only address type supported by the BuiltIn transport.
    IpPort(std::net::SocketAddr),
    /// A hostname-and-port target address.  Some transports may support this.
    #[cfg(feature = "pt-client")]
    HostPort(String, u16),
    /// A completely absent target address.  Some transports support this.
    #[cfg(feature = "pt-client")]
    None,
}

/// An error from parsing a [`PtTargetAddr`].
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PtAddrError {
    /// We were compiled without support for addresses of this type.
    #[error("Not compiled with pluggable transport support.")]
    NoSupport,
    /// We cannot parse this address.
    #[error("Cannot parse {0:?} as an address.")]
    BadAddress(String),
}

// TODO pt-client: decide whether to inline these.
#[allow(clippy::unnecessary_wraps)]
impl PtTargetAddr {
    /// Helper: Construct a `HostPort` instance or return a `NoSupport` error.
    ///
    /// (This is a private convenience function, to simplify the `FromStr`
    /// implementation.)
    fn host_port(host: &str, port: u16) -> Result<Self, PtAddrError> {
        #[cfg(feature = "pt-client")]
        {
            Ok(PtTargetAddr::HostPort(host.to_string(), port))
        }
        #[cfg(not(feature = "pt-client"))]
        {
            let _ = (host, port);
            Err(PtAddrError::NoSupport)
        }
    }

    /// Helper: Construct a `None` instance or return a `NoSupport` error.
    ///
    /// (This is a private convenience function, to simplify the `FromStr`
    /// implementation.)
    fn none() -> Result<Self, PtAddrError> {
        #[cfg(feature = "pt-client")]
        {
            Ok(PtTargetAddr::None)
        }
        #[cfg(not(feature = "pt-client"))]
        {
            Err(PtAddrError::NoSupport)
        }
    }
}

impl FromStr for PtTargetAddr {
    type Err = PtAddrError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(addr) = s.parse() {
            Ok(PtTargetAddr::IpPort(addr))
        } else if let Some((name, port)) = s.rsplit_once(':') {
            let port = port
                .parse()
                .map_err(|_| PtAddrError::BadAddress(s.to_string()))?;

            Self::host_port(name, port)
        } else if s == NONE_ADDR {
            Self::none()
        } else {
            Err(PtAddrError::BadAddress(s.to_string()))
        }
    }
}

impl Display for PtTargetAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PtTargetAddr::IpPort(addr) => write!(f, "{}", addr),
            #[cfg(feature = "pt-client")]
            PtTargetAddr::HostPort(host, port) => write!(f, "{}:{}", host, port),
            #[cfg(feature = "pt-client")]
            PtTargetAddr::None => write!(f, "{}", NONE_ADDR),
        }
    }
}

/// A set of options to be passed along to a pluggable transport along with a
/// single target bridge relay.
///
/// These options typically describe aspects of the targeted bridge relay that
/// are not included in its address and Tor keys, such as additional
/// transport-specific keys or parameters.
///
/// This type is _not_ for settings that apply to _all_ of the connections over
/// a transport.
#[cfg(feature = "pt-client")]
#[derive(Clone, Debug)]
#[allow(dead_code)] // TODO pt-client: we will need to parse and access these values.

// TODO pt-client: I am not sure we will want to keep this type, rather than
// just inlining it.  I am leaving it as a separate type for now, though, for a
// few reasons:
// 1) to avoid confusing it with the parameters passed to a transport when it
//    starts;
// 2) to give us some flexibility about the representation.
pub struct PtTargetSettings {
    /// A list of (key,value) pairs
    settings: Vec<(String, String)>,
}

/// The set of information passed to the  pluggable transport subsystem in order
/// to establish a connection to a bridge relay.
#[derive(Clone, Debug)]
#[cfg(feature = "pt-client")]
#[allow(dead_code)] // TODO pt-client: Needs functions to access and construct
pub struct PtTarget {
    /// The transport to be used.
    transport: PtTransportName,
    /// The address of the bridge relay, if any.
    addr: PtTargetAddr,
    /// Any additional settings used by the transport.
    settings: Arc<PtTargetSettings>,
}

/// The way to approach a single relay in order to open a channel.
///
/// For direct connections, this is simply an address.  For connections via a
/// pluggable transport, this includes information about the transport, and any
/// address and settings information that transport requires.
#[derive(Clone, Debug)]
#[allow(clippy::exhaustive_enums)]
// TODO pt-client: I am not in love with this enum name --nm.
pub enum ChannelMethod {
    /// Connect to the relay directly at a given address.
    Direct(std::net::SocketAddr),

    /// Connect to a bridge relay via a pluggable transport.
    #[cfg(feature = "pt-client")]
    Pluggable(PtTarget),
}

#[cfg(test)]
mod test {
    #![allow(clippy::unwrap_used)]
    use super::*;

    #[test]
    fn builtin() {
        assert!(TransportId::default().is_builtin());
        assert_eq!(
            TransportId::default(),
            "<none>".parse().expect("Couldn't parse default ID")
        );
    }

    #[test]
    #[cfg(not(feature = "pt-client"))]
    fn nosupport() {
        // We should get this error whenever we parse a non-default PT and we have no PT support.
        assert!(matches!(
            TransportId::from_str("obfs4"),
            Err(TransportIdError::NoSupport)
        ));
    }

    #[test]
    #[cfg(feature = "pt-client")]
    fn wellformed() {
        for id in &["snowflake", "obfs4", "_ohai", "Z", "future_WORK2"] {
            assert!(is_well_formed_id(id));
        }

        for id in &[" ", "Mölm", "12345", ""] {
            assert!(!is_well_formed_id(id));
        }
    }

    #[test]
    #[cfg(feature = "pt-client")]
    fn parsing() {
        let obfs = TransportId::from_str("obfs4").unwrap();
        let dflt = TransportId::default();
        let dflt2 = TransportId::from_str("<none>").unwrap();
        let snow = TransportId::from_str("snowflake").unwrap();
        let obfs_again = TransportId::from_str("obfs4").unwrap();

        assert_eq!(obfs, obfs_again);
        assert_eq!(dflt, dflt2);
        assert_ne!(snow, obfs);
        assert_ne!(snow, dflt);

        assert!(matches!(
            TransportId::from_str("12345"),
            Err(TransportIdError::BadId(_))
        ));
        assert!(matches!(
            TransportId::from_str("bridge"),
            Err(TransportIdError::BadId(_))
        ));
    }

    #[test]
    #[cfg(feature = "pt-client")]
    fn addr() {
        for addr in &["1.2.3.4:555", "[::1]:9999"] {
            let a: PtTargetAddr = addr.parse().unwrap();
            assert_eq!(&a.to_string(), addr);
        }

        for addr in &["www.example.com:9100", "<none>"] {
            if cfg!(feature = "pt-client") {
                let a: PtTargetAddr = addr.parse().unwrap();
                assert_eq!(&a.to_string(), addr);
            } else {
                let e = PtTargetAddr::from_str(addr).unwrap_err();
                assert!(matches!(e, PtAddrError::NoSupport));
            }
        }

        for addr in &["foobar", "<<<>>>"] {
            let e = PtTargetAddr::from_str(addr).unwrap_err();
            assert!(matches!(e, PtAddrError::BadAddress(_)));
        }
    }
}