Skip to main content

rama/cli/service/
wire_names.rs

1//! Display names for QUIC and HTTP/3 codepoints.
2//!
3//! Shared by the echo and fingerprint services, so both name a codepoint identically.
4
5use std::{borrow::Cow, fmt};
6
7use serde::{Serialize, Serializer};
8
9use crate::http::proto::h3::{Code, FrameType as H3FrameType, SettingId, StreamType};
10#[cfg(feature = "quic")]
11use crate::quic::proto::{
12    Version, frame::FrameType as QuicFrameType, transport_parameters::TransportParameterId,
13};
14
15/// The display name of one protocol codepoint.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum CodepointName {
18    /// A registered name.
19    Known(Cow<'static, str>),
20    /// A value reserved for greasing.
21    Grease,
22    /// A value reserved by the registry without a meaning.
23    Reserved,
24    /// A value this implementation does not recognise.
25    Unknown,
26}
27
28impl CodepointName {
29    /// The name, or `grease`, `reserved` or `unknown`.
30    #[must_use]
31    pub fn as_str(&self) -> &str {
32        match self {
33            Self::Known(name) => name,
34            Self::Grease => "grease",
35            Self::Reserved => "reserved",
36            Self::Unknown => "unknown",
37        }
38    }
39
40    /// A label for human display: the known name, or the class with the hex value.
41    #[must_use]
42    pub fn label(&self, raw: u64) -> String {
43        match self {
44            Self::Known(name) => name.clone().into_owned(),
45            other => format!("{} {raw:#x}", other.as_str()),
46        }
47    }
48
49    fn from_parts(name: Option<&'static str>, grease: bool, reserved: bool) -> Self {
50        match name {
51            Some(name) => Self::Known(Cow::Borrowed(name)),
52            None if grease => Self::Grease,
53            None if reserved => Self::Reserved,
54            None => Self::Unknown,
55        }
56    }
57}
58
59impl fmt::Display for CodepointName {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str(self.as_str())
62    }
63}
64
65impl Serialize for CodepointName {
66    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
67        serializer.serialize_str(self.as_str())
68    }
69}
70
71/// A raw codepoint together with its display name.
72#[derive(Debug, Clone, Serialize)]
73pub struct Named<R> {
74    pub raw: R,
75    pub name: CodepointName,
76}
77
78/// A QUIC version: `v1`, `v2`, `grease` for `0x?a?a?a?a` (RFC 9000 §15) or `unknown`.
79#[cfg(feature = "quic")]
80#[must_use]
81pub fn quic_version_name(version: Version) -> CodepointName {
82    match version {
83        Version::V1 => CodepointName::Known(Cow::Borrowed("v1")),
84        Version::V2 => CodepointName::Known(Cow::Borrowed("v2")),
85        other if other.is_reserved() => CodepointName::Grease,
86        _ => CodepointName::Unknown,
87    }
88}
89
90/// A QUIC version with its 32-bit wire value as hex text.
91#[cfg(feature = "quic")]
92#[must_use]
93pub fn named_quic_version(version: Version) -> Named<String> {
94    Named {
95        raw: format!("{:#010x}", version.as_u32()),
96        name: quic_version_name(version),
97    }
98}
99
100/// A transport parameter by its registry name, such as `initial_max_streams_bidi`.
101#[cfg(feature = "quic")]
102#[must_use]
103pub fn transport_parameter_name(id: TransportParameterId) -> CodepointName {
104    match id {
105        TransportParameterId::Unknown(_) if id.is_reserved() => CodepointName::Grease,
106        TransportParameterId::Unknown(_) => CodepointName::Unknown,
107        known => CodepointName::Known(Cow::Owned(snake_case(&known.variant_name()))),
108    }
109}
110
111/// A transport parameter identifier with its name.
112#[cfg(feature = "quic")]
113#[must_use]
114pub fn named_transport_parameter(id: TransportParameterId) -> Named<u64> {
115    Named {
116        raw: u64::from(id),
117        name: transport_parameter_name(id),
118    }
119}
120
121/// A QUIC frame type (RFC 9000 §19).
122#[cfg(feature = "quic")]
123#[must_use]
124pub fn quic_frame_name(frame_type: u64) -> CodepointName {
125    CodepointName::from_parts(QuicFrameType::from_u64(frame_type).name(), false, false)
126}
127
128/// An HTTP/3 setting identifier (RFC 9114 §7.2.4).
129#[must_use]
130pub fn h3_setting_name(id: u64) -> CodepointName {
131    let id = SettingId::new(id);
132    CodepointName::from_parts(id.name(), id.is_reserved(), id.is_h2_forbidden())
133}
134
135/// An HTTP/3 unidirectional stream type (RFC 9114 §6.2).
136#[must_use]
137pub fn h3_stream_type_name(stream_type: u64) -> CodepointName {
138    let ty = StreamType::new(stream_type);
139    CodepointName::from_parts(ty.name(), ty.is_reserved(), false)
140}
141
142/// An HTTP/3 frame type (RFC 9114 §7.2).
143#[must_use]
144pub fn h3_frame_type_name(frame_type: u64) -> CodepointName {
145    let ty = H3FrameType::new(frame_type);
146    CodepointName::from_parts(ty.name(), ty.is_reserved(), ty.is_h2_reserved())
147}
148
149/// An HTTP/3 error code (RFC 9114 §8.1).
150#[must_use]
151pub fn h3_error_name(code: u64) -> CodepointName {
152    let code = Code::new(code);
153    CodepointName::from_parts(code.name(), code.is_reserved(), false)
154}
155
156/// Pair a raw value with the name `name_of` gives it.
157#[must_use]
158pub fn named(raw: u64, name_of: fn(u64) -> CodepointName) -> Named<u64> {
159    Named {
160        raw,
161        name: name_of(raw),
162    }
163}
164
165#[cfg(feature = "quic")]
166fn snake_case(name: &str) -> String {
167    let mut out = String::with_capacity(name.len() + 8);
168    for (index, c) in name.chars().enumerate() {
169        if c.is_ascii_uppercase() {
170            if index > 0 {
171                out.push('_');
172            }
173            out.push(c.to_ascii_lowercase());
174        } else {
175            out.push(c);
176        }
177    }
178    out
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use serde_json::{json, to_value};
185
186    #[cfg(feature = "quic")]
187    #[test]
188    fn names_cover_quic_codepoints() {
189        assert_eq!(quic_version_name(Version::V1).as_str(), "v1");
190        assert_eq!(quic_version_name(Version::V2).as_str(), "v2");
191        assert_eq!(
192            quic_version_name(Version::from_u32(0x0a1a_2a3a)).as_str(),
193            "grease"
194        );
195        assert_eq!(
196            quic_version_name(Version::from_u32(0xff00_001d)).as_str(),
197            "unknown"
198        );
199        assert_eq!(named_quic_version(Version::V2).raw, "0x6b3343cf");
200
201        for (id, name) in [
202            (0x08, "initial_max_streams_bidi"),
203            (0x11, "version_information"),
204            (0x2ab2, "grease_quic_bit"),
205            (0xff04_de1b, "min_ack_delay_draft07"),
206            (31 * 5 + 27, "grease"),
207            (0x4243, "unknown"),
208        ] {
209            assert_eq!(
210                transport_parameter_name(TransportParameterId::from(id)).as_str(),
211                name
212            );
213        }
214
215        assert_eq!(quic_frame_name(0x06).as_str(), "CRYPTO");
216        assert_eq!(quic_frame_name(0x0a).as_str(), "STREAM");
217        assert_eq!(quic_frame_name(0x4243).as_str(), "unknown");
218    }
219
220    #[test]
221    fn names_cover_known_grease_reserved_and_unknown_h3_codepoints() {
222        assert_eq!(h3_setting_name(0x06).as_str(), "MAX_FIELD_SECTION_SIZE");
223        assert_eq!(h3_setting_name(0x21).as_str(), "grease");
224        assert_eq!(h3_setting_name(0x02).as_str(), "reserved");
225        assert_eq!(h3_setting_name(0x4d45).as_str(), "unknown");
226        assert_eq!(h3_stream_type_name(0x02).as_str(), "QPACK_ENCODER");
227        assert_eq!(h3_frame_type_name(0x04).as_str(), "SETTINGS");
228        assert_eq!(h3_frame_type_name(0x06).as_str(), "reserved");
229        assert_eq!(h3_error_name(0x0101).as_str(), "H3_GENERAL_PROTOCOL_ERROR");
230
231        assert_eq!(h3_setting_name(0x21).label(0x21), "grease 0x21");
232        assert_eq!(
233            h3_setting_name(0x01).label(0x01),
234            "QPACK_MAX_TABLE_CAPACITY"
235        );
236    }
237
238    #[test]
239    fn named_serializes_raw_and_name() {
240        assert_eq!(
241            to_value(named(8, h3_setting_name)).unwrap(),
242            json!({"raw": 8, "name": "ENABLE_CONNECT_PROTOCOL"})
243        );
244        #[cfg(feature = "quic")]
245        assert_eq!(
246            to_value(named_quic_version(Version::V1)).unwrap(),
247            json!({"raw": "0x00000001", "name": "v1"})
248        );
249    }
250}