iroh_relay/
http.rs

1//! HTTP-specific constants for the relay server and client.
2
3use http::{HeaderName, HeaderValue};
4use n0_error::stack_error;
5
6#[cfg(feature = "server")]
7pub(crate) const WEBSOCKET_UPGRADE_PROTOCOL: &str = "websocket";
8#[cfg(feature = "server")] // only used in the server for now
9pub(crate) const SUPPORTED_WEBSOCKET_VERSION: &str = "13";
10
11/// The HTTP path under which the relay accepts relaying connections
12/// (over websockets and a custom upgrade protocol).
13pub const RELAY_PATH: &str = "/relay";
14/// The HTTP path under which the relay allows doing latency queries for testing.
15pub const RELAY_PROBE_PATH: &str = "/ping";
16
17/// The HTTP header name for relay client authentication
18pub const CLIENT_AUTH_HEADER: HeaderName = HeaderName::from_static("x-iroh-relay-client-auth-v1");
19
20/// The URL query parameter name used to pass the authorization token when
21/// HTTP headers are not available (notably, in browsers).
22#[cfg(any(wasm_browser, feature = "server"))]
23pub(crate) const AUTH_TOKEN_URL_QUERY_PARAM: &str = "token";
24
25/// The relay protocol version negotiated between client and server.
26///
27/// Sent as the websocket sub-protocol header `Sec-Websocket-Protocol` from
28/// the client. The server picks the best supported version and replies with it.
29///
30/// Variants are ordered by preference (highest first), so the [`Ord`] impl
31/// can be used during negotiation to pick the best version.
32#[derive(
33    Clone,
34    Copy,
35    Debug,
36    PartialEq,
37    Eq,
38    PartialOrd,
39    Ord,
40    Default,
41    strum::EnumString,
42    strum::Display,
43    strum::IntoStaticStr,
44)]
45// Only used by the `all_is_exhaustive` to validate that `Self::ALL` is up to date.
46#[cfg_attr(test, derive(strum::EnumCount, test_strategy::Arbitrary))]
47#[strum(parse_err_ty = UnsupportedRelayProtocolVersion, parse_err_fn = strum_err_fn)]
48#[non_exhaustive]
49// Needs to be ordered with newest version last, so that the `Ord` impl orders by latest version as max.
50pub enum ProtocolVersion {
51    /// Version 1 (the only version supported until iroh 0.98.0)
52    #[strum(serialize = "iroh-relay-v1")]
53    V1,
54    /// Version 2 (added in iroh 0.98.0)
55    /// - Removed `Health` frame (id 11)
56    /// - Added `Status` frame (id 13)
57    #[default]
58    #[strum(serialize = "iroh-relay-v2")]
59    V2,
60}
61
62impl ProtocolVersion {
63    /// All supported protocol versions, in order of preference (newest first).
64    //
65    // This list needs to be maintained by hand; the `all_is_exhaustive` test in this module
66    // asserts that the length matches the actual variant count via a `cfg(test)`-only
67    // `strum::EnumCount` derive.
68    pub const ALL: &'static [Self] = &[Self::V2, Self::V1];
69
70    /// Returns an iterator of all supported protocol version identifiers, in order of preference.
71    pub fn all() -> impl Iterator<Item = &'static str> {
72        Self::ALL.iter().map(ProtocolVersion::to_str)
73    }
74
75    /// Returns a comma-separated string of all supported protocol version identifiers.
76    pub fn all_joined() -> String {
77        Self::all().collect::<Vec<_>>().join(", ")
78    }
79
80    /// Returns all supported protocol versions in a comma-separated string as an HTTP header value.
81    pub fn all_as_header_value() -> HeaderValue {
82        HeaderValue::from_bytes(Self::all_joined().as_bytes()).expect("valid header name")
83    }
84
85    /// Returns the protocol version identifier string.
86    pub fn to_str(&self) -> &'static str {
87        self.into()
88    }
89
90    /// Tries to parse a [`ProtocolVersion`] from `s`.
91    ///
92    /// Returns `None` if `s` is not a valid protocol version string.
93    pub fn match_from_str(s: &str) -> Option<Self> {
94        Self::try_from(s).ok()
95    }
96
97    /// Returns this protocol version as an HTTP header value.
98    pub fn to_header_value(&self) -> HeaderValue {
99        HeaderValue::from_static(self.to_str())
100    }
101}
102
103/// Error returned when the relay protocol version is not recognized.
104#[stack_error(derive)]
105#[error("Relay protocol version is not supported")]
106pub struct UnsupportedRelayProtocolVersion;
107
108fn strum_err_fn(_item: &str) -> UnsupportedRelayProtocolVersion {
109    UnsupportedRelayProtocolVersion::new()
110}
111
112#[cfg(test)]
113mod tests {
114    use std::str::FromStr;
115
116    use strum::EnumCount;
117
118    use super::*;
119
120    #[test]
121    fn all_is_exhaustive() {
122        // `EnumCount::COUNT` is the actual variant count, derived at compile
123        // time. If a new variant is added without being listed in `ALL`, the
124        // lengths diverge and this test fails.
125        assert_eq!(ProtocolVersion::ALL.len(), ProtocolVersion::COUNT);
126        for &v in ProtocolVersion::ALL {
127            assert_eq!(ProtocolVersion::from_str(v.to_str()).unwrap(), v);
128        }
129    }
130}