Create an API for TransportId
This commit is contained in:
parent
a773a753b3
commit
63dbc7b22d
|
@ -3679,7 +3679,9 @@ name = "tor-linkspec"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64ct",
|
"base64ct",
|
||||||
|
"cfg-if",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"educe",
|
||||||
"hex",
|
"hex",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -11,9 +11,14 @@ keywords = ["tor", "arti"]
|
||||||
categories = ["network-programming"]
|
categories = ["network-programming"]
|
||||||
repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
|
repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
pt-client = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64ct = "1.5.1"
|
base64ct = "1.5.1"
|
||||||
|
cfg-if = "1.0.0"
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
|
educe = "0.4.6"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
serde = { version = "1.0.103", features = ["derive"] }
|
serde = { version = "1.0.103", features = ["derive"] }
|
||||||
strum = { version = "0.24", features = ["derive"] }
|
strum = { version = "0.24", features = ["derive"] }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
MODIFIED: Various new types to support transports.
|
|
@ -77,8 +77,10 @@ mod ids;
|
||||||
mod ls;
|
mod ls;
|
||||||
mod owned;
|
mod owned;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
mod transport;
|
||||||
|
|
||||||
pub use ids::{set::RelayIdSet, RelayId, RelayIdError, RelayIdRef, RelayIdType, RelayIdTypeIter};
|
pub use ids::{set::RelayIdSet, RelayId, RelayIdError, RelayIdRef, RelayIdType, RelayIdTypeIter};
|
||||||
pub use ls::LinkSpec;
|
pub use ls::LinkSpec;
|
||||||
pub use owned::{OwnedChanTarget, OwnedCircTarget, RelayIds};
|
pub use owned::{OwnedChanTarget, OwnedCircTarget, RelayIds};
|
||||||
pub use traits::{ChanTarget, CircTarget, HasAddrs, HasRelayIds, HasRelayIdsLegacy};
|
pub use traits::{ChanTarget, CircTarget, HasAddrs, HasRelayIds, HasRelayIdsLegacy};
|
||||||
|
pub use transport::{TransportId, TransportIdError};
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
//! 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 transports,
|
||||||
|
//! which use TLS over other protocols to avoid detection by censors.
|
||||||
|
|
||||||
|
/// 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(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This identifier is used to indicate the built-in transport.
|
||||||
|
const BUILT_IN_ID: &str = "<none>";
|
||||||
|
|
||||||
|
impl std::str::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")]
|
||||||
|
if is_well_formed_id(s) {
|
||||||
|
Ok(TransportId(Inner::Pluggable(s.to_string())))
|
||||||
|
} else {
|
||||||
|
Err(TransportIdError::BadId(s.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "pt-client"))]
|
||||||
|
Err(TransportIdError::NoSupport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TransportId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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'_')
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
use super::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[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(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue