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}