1use 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#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum CodepointName {
18 Known(Cow<'static, str>),
20 Grease,
22 Reserved,
24 Unknown,
26}
27
28impl CodepointName {
29 #[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 #[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#[derive(Debug, Clone, Serialize)]
73pub struct Named<R> {
74 pub raw: R,
75 pub name: CodepointName,
76}
77
78#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}