1#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4pub struct Http3Fingerprint {
5 pub alpn_protocols: Vec<Vec<u8>>,
6 pub transport: QuicTransportParams,
7 pub settings: H3Settings,
8 pub stream: H3StreamFingerprint,
9}
10
11impl Default for Http3Fingerprint {
12 fn default() -> Self {
13 Self::chrome()
14 }
15}
16
17impl Http3Fingerprint {
18 pub fn chrome() -> Self {
19 Self {
20 alpn_protocols: vec![b"h3".to_vec()],
21 transport: QuicTransportParams::chrome(),
22 settings: H3Settings::chrome(),
23 stream: H3StreamFingerprint::chrome(),
24 }
25 }
26
27 pub fn firefox() -> Self {
28 Self {
29 alpn_protocols: vec![b"h3".to_vec()],
30 transport: QuicTransportParams::firefox(),
31 settings: H3Settings::firefox(),
32 stream: H3StreamFingerprint::firefox(),
33 }
34 }
35
36 pub fn pool_key_string(&self) -> String {
37 let alpn = self
38 .alpn_protocols
39 .iter()
40 .map(|proto| String::from_utf8_lossy(proto).into_owned())
41 .collect::<Vec<_>>()
42 .join(",");
43 format!(
44 "alpn={alpn};transport={};settings={};stream={}",
45 self.transport.pool_key_string(),
46 self.settings.pool_key_string(),
47 self.stream.pool_key_string(),
48 )
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash)]
53pub struct RawQuicTransportParameter {
54 pub id: u64,
55 pub value: Vec<u8>,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub(crate) enum RawQuicTransportParameterConnectionId {
60 OriginalDestination,
61 InitialSource,
62 RetrySource,
63}
64
65impl RawQuicTransportParameter {
66 const ORIGINAL_DESTINATION_CONNECTION_ID_ID: u64 = 0x00;
67 const INITIAL_SOURCE_CONNECTION_ID_ID: u64 = 0x0f;
68 const RETRY_SOURCE_CONNECTION_ID_ID: u64 = 0x10;
69 const ORIGINAL_DESTINATION_CONNECTION_ID_PLACEHOLDER: &'static [u8] =
70 b"$specter:original_destination_connection_id";
71 const INITIAL_SOURCE_CONNECTION_ID_PLACEHOLDER: &'static [u8] =
72 b"$specter:initial_source_connection_id";
73 const RETRY_SOURCE_CONNECTION_ID_PLACEHOLDER: &'static [u8] =
74 b"$specter:retry_source_connection_id";
75
76 pub fn varint(id: u64, value: u64) -> Self {
77 Self {
78 id,
79 value: encode_quic_varint(value),
80 }
81 }
82
83 pub fn empty(id: u64) -> Self {
84 Self {
85 id,
86 value: Vec::new(),
87 }
88 }
89
90 pub fn original_destination_connection_id() -> Self {
91 Self {
92 id: Self::ORIGINAL_DESTINATION_CONNECTION_ID_ID,
93 value: Self::ORIGINAL_DESTINATION_CONNECTION_ID_PLACEHOLDER.to_vec(),
94 }
95 }
96
97 pub fn initial_source_connection_id() -> Self {
98 Self {
99 id: Self::INITIAL_SOURCE_CONNECTION_ID_ID,
100 value: Self::INITIAL_SOURCE_CONNECTION_ID_PLACEHOLDER.to_vec(),
101 }
102 }
103
104 pub fn retry_source_connection_id() -> Self {
105 Self {
106 id: Self::RETRY_SOURCE_CONNECTION_ID_ID,
107 value: Self::RETRY_SOURCE_CONNECTION_ID_PLACEHOLDER.to_vec(),
108 }
109 }
110
111 pub(crate) fn connection_id_placeholder(
112 &self,
113 ) -> Option<RawQuicTransportParameterConnectionId> {
114 match (self.id, self.value.as_slice()) {
115 (
116 Self::ORIGINAL_DESTINATION_CONNECTION_ID_ID,
117 Self::ORIGINAL_DESTINATION_CONNECTION_ID_PLACEHOLDER,
118 ) => Some(RawQuicTransportParameterConnectionId::OriginalDestination),
119 (
120 Self::INITIAL_SOURCE_CONNECTION_ID_ID,
121 Self::INITIAL_SOURCE_CONNECTION_ID_PLACEHOLDER,
122 ) => Some(RawQuicTransportParameterConnectionId::InitialSource),
123 (Self::RETRY_SOURCE_CONNECTION_ID_ID, Self::RETRY_SOURCE_CONNECTION_ID_PLACEHOLDER) => {
124 Some(RawQuicTransportParameterConnectionId::RetrySource)
125 }
126 _ => None,
127 }
128 }
129}
130
131fn encode_quic_varint(value: u64) -> Vec<u8> {
132 if value < 64 {
133 vec![value as u8]
134 } else if value < 16_384 {
135 let encoded = 0x4000 | value;
136 vec![(encoded >> 8) as u8, encoded as u8]
137 } else if value < 1_073_741_824 {
138 let encoded = 0x8000_0000 | value;
139 vec![
140 (encoded >> 24) as u8,
141 (encoded >> 16) as u8,
142 (encoded >> 8) as u8,
143 encoded as u8,
144 ]
145 } else {
146 let encoded = 0xc000_0000_0000_0000 | value;
147 encoded.to_be_bytes().to_vec()
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152pub enum QuicEcnCodepoint {
153 Ect0,
154 Ect1,
155}
156
157impl QuicEcnCodepoint {
158 pub fn ip_tos_bits(self) -> u32 {
159 match self {
160 Self::Ect0 => 0b10,
161 Self::Ect1 => 0b01,
162 }
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Hash)]
167pub struct QuicTransportParams {
168 pub max_idle_timeout_ms: u64,
169 pub max_recv_udp_payload_size: usize,
170 pub max_send_udp_payload_size: usize,
171 pub initial_datagram_size: usize,
172 pub initial_max_data: u64,
173 pub initial_max_stream_data_bidi_local: u64,
174 pub initial_max_stream_data_bidi_remote: u64,
175 pub initial_max_stream_data_uni: u64,
176 pub initial_max_streams_bidi: u64,
177 pub initial_max_streams_uni: u64,
178 pub ack_delay_exponent: u64,
179 pub max_ack_delay_ms: u64,
180 pub ack_eliciting_threshold: usize,
181 pub active_connection_id_limit: u64,
182 pub disable_active_migration: bool,
183 pub disable_dcid_reuse: bool,
184 pub grease: bool,
185 pub additional_transport_parameters: Vec<(u64, Vec<u8>)>,
186 pub raw_ordered_transport_parameters: Option<Vec<RawQuicTransportParameter>>,
187 pub max_datagram_frame_size: Option<u64>,
188 pub destination_connection_id_len: usize,
189 pub source_connection_id_len: usize,
190 pub max_amplification_factor: usize,
191 pub initial_rtt_ms: u64,
192 pub initial_congestion_window_packets: usize,
193 pub pacing_enabled: bool,
194 pub max_pacing_rate: Option<u64>,
195 pub relaxed_loss_threshold: bool,
196 pub max_connection_window: u64,
197 pub max_stream_window: u64,
198 pub ecn_codepoint: Option<QuicEcnCodepoint>,
199}
200
201impl QuicTransportParams {
202 pub fn chrome() -> Self {
203 Self {
204 max_idle_timeout_ms: 30_000,
205 max_recv_udp_payload_size: 65_535,
206 max_send_udp_payload_size: 1350,
207 initial_datagram_size: 1200,
208 initial_max_data: 15_663_105,
209 initial_max_stream_data_bidi_local: 1_000_000,
210 initial_max_stream_data_bidi_remote: 1_000_000,
211 initial_max_stream_data_uni: 1_000_000,
212 initial_max_streams_bidi: 100,
213 initial_max_streams_uni: 100,
214 ack_delay_exponent: 3,
215 max_ack_delay_ms: 25,
216 ack_eliciting_threshold: 10,
217 active_connection_id_limit: 2,
218 disable_active_migration: true,
219 disable_dcid_reuse: false,
220 grease: true,
221 additional_transport_parameters: Vec::new(),
222 raw_ordered_transport_parameters: None,
223 max_datagram_frame_size: None,
224 destination_connection_id_len: 16,
225 source_connection_id_len: 16,
226 max_amplification_factor: 3,
227 initial_rtt_ms: 333,
228 initial_congestion_window_packets: 10,
229 pacing_enabled: true,
230 max_pacing_rate: None,
231 relaxed_loss_threshold: false,
232 max_connection_window: 24 * 1024 * 1024,
233 max_stream_window: 16 * 1024 * 1024,
234 ecn_codepoint: None,
235 }
236 }
237
238 pub fn chrome_capture_ordered() -> Self {
239 let mut params = Self::chrome();
240 params.raw_ordered_transport_parameters =
241 Some(capture_ordered_transport_parameters(¶ms));
242 params
243 }
244
245 pub fn firefox() -> Self {
246 Self {
247 grease: false,
248 max_ack_delay_ms: 20,
249 ack_eliciting_threshold: 2,
250 initial_max_stream_data_bidi_local: 4 * 1024 * 1024,
251 initial_max_stream_data_bidi_remote: 4 * 1024 * 1024,
252 initial_max_stream_data_uni: 4 * 1024 * 1024,
253 ..Self::chrome()
254 }
255 }
256
257 pub fn firefox_capture_ordered() -> Self {
258 let mut params = Self::firefox();
259 params.raw_ordered_transport_parameters =
260 Some(capture_ordered_transport_parameters(¶ms));
261 params
262 }
263
264 pub fn pool_key_string(&self) -> String {
265 let additional_transport_parameters = self
266 .additional_transport_parameters
267 .iter()
268 .map(|(key, value)| {
269 let value_hex = value
270 .iter()
271 .map(|byte| format!("{byte:02x}"))
272 .collect::<String>();
273 format!("{key}:{value_hex}")
274 })
275 .collect::<Vec<_>>()
276 .join(",");
277 let raw_ordered_transport_parameters = self
278 .raw_ordered_transport_parameters
279 .as_ref()
280 .map(|parameters| {
281 parameters
282 .iter()
283 .map(|parameter| {
284 let value_hex = parameter
285 .value
286 .iter()
287 .map(|byte| format!("{byte:02x}"))
288 .collect::<String>();
289 format!("{}:{value_hex}", parameter.id)
290 })
291 .collect::<Vec<_>>()
292 .join(",")
293 })
294 .unwrap_or_else(|| "none".to_string());
295 format!(
296 "idle={};recv_udp={};send_udp={};initial_dgram={};max_data={};bidi_local={};bidi_remote={};uni_data={};bidi_streams={};uni_streams={};ack_exp={};ack_delay={};ack_threshold={};cid_limit={};disable_migration={};disable_dcid_reuse={};grease={};additional={additional_transport_parameters};raw_ordered={raw_ordered_transport_parameters};max_datagram={:?};dcid_len={};scid_len={};amp={};rtt={};cwnd={};pacing={};max_pacing={:?};relaxed_loss={};conn_win={};stream_win={};ecn={:?}",
297 self.max_idle_timeout_ms,
298 self.max_recv_udp_payload_size,
299 self.max_send_udp_payload_size,
300 self.initial_datagram_size,
301 self.initial_max_data,
302 self.initial_max_stream_data_bidi_local,
303 self.initial_max_stream_data_bidi_remote,
304 self.initial_max_stream_data_uni,
305 self.initial_max_streams_bidi,
306 self.initial_max_streams_uni,
307 self.ack_delay_exponent,
308 self.max_ack_delay_ms,
309 self.ack_eliciting_threshold,
310 self.active_connection_id_limit,
311 self.disable_active_migration,
312 self.disable_dcid_reuse,
313 self.grease,
314 self.max_datagram_frame_size,
315 self.destination_connection_id_len,
316 self.source_connection_id_len,
317 self.max_amplification_factor,
318 self.initial_rtt_ms,
319 self.initial_congestion_window_packets,
320 self.pacing_enabled,
321 self.max_pacing_rate,
322 self.relaxed_loss_threshold,
323 self.max_connection_window,
324 self.max_stream_window,
325 self.ecn_codepoint,
326 )
327 }
328}
329
330fn capture_ordered_transport_parameters(
331 params: &QuicTransportParams,
332) -> Vec<RawQuicTransportParameter> {
333 const TP_MAX_IDLE_TIMEOUT: u64 = 0x01;
334 const TP_MAX_UDP_PAYLOAD_SIZE: u64 = 0x03;
335 const TP_INITIAL_MAX_DATA: u64 = 0x04;
336 const TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: u64 = 0x05;
337 const TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: u64 = 0x06;
338 const TP_INITIAL_MAX_STREAM_DATA_UNI: u64 = 0x07;
339 const TP_INITIAL_MAX_STREAMS_BIDI: u64 = 0x08;
340 const TP_INITIAL_MAX_STREAMS_UNI: u64 = 0x09;
341 const TP_ACK_DELAY_EXPONENT: u64 = 0x0a;
342 const TP_MAX_ACK_DELAY: u64 = 0x0b;
343 const TP_DISABLE_ACTIVE_MIGRATION: u64 = 0x0c;
344 const TP_ACTIVE_CONNECTION_ID_LIMIT: u64 = 0x0e;
345 const TP_GREASE_RESERVED: u64 = 27;
346 const TP_MAX_DATAGRAM_FRAME_SIZE: u64 = 0x20;
347
348 let mut ordered = vec![
349 RawQuicTransportParameter::varint(TP_MAX_IDLE_TIMEOUT, params.max_idle_timeout_ms),
350 RawQuicTransportParameter::varint(
351 TP_MAX_UDP_PAYLOAD_SIZE,
352 params.max_recv_udp_payload_size as u64,
353 ),
354 RawQuicTransportParameter::varint(TP_INITIAL_MAX_DATA, params.initial_max_data),
355 RawQuicTransportParameter::varint(
356 TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
357 params.initial_max_stream_data_bidi_local,
358 ),
359 RawQuicTransportParameter::varint(
360 TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
361 params.initial_max_stream_data_bidi_remote,
362 ),
363 RawQuicTransportParameter::varint(
364 TP_INITIAL_MAX_STREAM_DATA_UNI,
365 params.initial_max_stream_data_uni,
366 ),
367 RawQuicTransportParameter::varint(
368 TP_INITIAL_MAX_STREAMS_BIDI,
369 params.initial_max_streams_bidi,
370 ),
371 RawQuicTransportParameter::varint(
372 TP_INITIAL_MAX_STREAMS_UNI,
373 params.initial_max_streams_uni,
374 ),
375 RawQuicTransportParameter::varint(TP_ACK_DELAY_EXPONENT, params.ack_delay_exponent),
376 RawQuicTransportParameter::varint(TP_MAX_ACK_DELAY, params.max_ack_delay_ms),
377 ];
378 if params.disable_active_migration {
379 ordered.push(RawQuicTransportParameter::empty(
380 TP_DISABLE_ACTIVE_MIGRATION,
381 ));
382 }
383 ordered.push(RawQuicTransportParameter::varint(
384 TP_ACTIVE_CONNECTION_ID_LIMIT,
385 params.active_connection_id_limit,
386 ));
387 ordered.push(RawQuicTransportParameter::initial_source_connection_id());
388 if let Some(value) = params.max_datagram_frame_size {
389 ordered.push(RawQuicTransportParameter::varint(
390 TP_MAX_DATAGRAM_FRAME_SIZE,
391 value,
392 ));
393 }
394 if params.grease {
395 ordered.push(RawQuicTransportParameter::empty(TP_GREASE_RESERVED));
396 }
397 ordered.extend(
398 params
399 .additional_transport_parameters
400 .iter()
401 .map(|(id, value)| RawQuicTransportParameter {
402 id: *id,
403 value: value.clone(),
404 }),
405 );
406 ordered
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Hash)]
410pub struct H3Settings {
411 pub qpack_max_table_capacity: Option<u64>,
412 pub qpack_blocked_streams: Option<u64>,
413 pub max_field_section_size: Option<u64>,
414 pub enable_extended_connect: bool,
415 pub additional_settings: Vec<(u64, u64)>,
416 pub raw_ordered_settings: Option<Vec<(u64, u64)>>,
417}
418
419impl H3Settings {
420 pub fn chrome() -> Self {
421 Self {
422 qpack_max_table_capacity: Some(0),
423 qpack_blocked_streams: Some(0),
424 max_field_section_size: None,
425 enable_extended_connect: true,
426 additional_settings: Vec::new(),
427 raw_ordered_settings: None,
428 }
429 }
430
431 pub fn firefox() -> Self {
432 Self {
433 enable_extended_connect: true,
434 ..Self::chrome()
435 }
436 }
437
438 pub fn pool_key_string(&self) -> String {
439 let additional = self
440 .additional_settings
441 .iter()
442 .map(|(key, value)| format!("{key}:{value}"))
443 .collect::<Vec<_>>()
444 .join(",");
445 let raw_ordered = self
446 .raw_ordered_settings
447 .as_ref()
448 .map(|settings| {
449 settings
450 .iter()
451 .map(|(key, value)| format!("{key}:{value}"))
452 .collect::<Vec<_>>()
453 .join(",")
454 })
455 .unwrap_or_default();
456 format!(
457 "qpack_table={:?};qpack_blocked={:?};max_field={:?};extended_connect={};additional={additional};raw_ordered={raw_ordered}",
458 self.qpack_max_table_capacity,
459 self.qpack_blocked_streams,
460 self.max_field_section_size,
461 self.enable_extended_connect,
462 )
463 }
464}
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
467pub enum QpackHeaderBlockStrategy {
468 StaticThenLiteral,
469 LiteralOnly,
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
473pub enum QpackStringEncodingStrategy {
474 Plain,
475 Huffman,
476 HuffmanIfSmaller,
477}
478
479#[derive(Debug, Clone, PartialEq, Eq, Hash)]
480pub struct H3StreamFingerprint {
481 pub open_control_stream_first: bool,
482 pub open_qpack_encoder_before_decoder: bool,
483 pub send_grease_stream: bool,
484 pub send_grease_frames: bool,
485 pub qpack_encoder_stream_payload: Vec<u8>,
486 pub qpack_decoder_stream_payload: Vec<u8>,
487 pub request_header_block_strategy: QpackHeaderBlockStrategy,
488 pub request_string_encoding: QpackStringEncodingStrategy,
489}
490
491impl H3StreamFingerprint {
492 pub fn chrome() -> Self {
493 Self {
494 open_control_stream_first: true,
495 open_qpack_encoder_before_decoder: true,
496 send_grease_stream: true,
497 send_grease_frames: true,
498 qpack_encoder_stream_payload: Vec::new(),
499 qpack_decoder_stream_payload: Vec::new(),
500 request_header_block_strategy: QpackHeaderBlockStrategy::StaticThenLiteral,
501 request_string_encoding: QpackStringEncodingStrategy::Plain,
502 }
503 }
504
505 pub fn firefox() -> Self {
506 Self {
507 send_grease_stream: false,
508 send_grease_frames: false,
509 ..Self::chrome()
510 }
511 }
512
513 pub fn pool_key_string(&self) -> String {
514 let qpack_encoder = self
515 .qpack_encoder_stream_payload
516 .iter()
517 .map(|byte| format!("{byte:02x}"))
518 .collect::<String>();
519 let qpack_decoder = self
520 .qpack_decoder_stream_payload
521 .iter()
522 .map(|byte| format!("{byte:02x}"))
523 .collect::<String>();
524 format!(
525 "control_first={};qpack_encoder_first={};grease_stream={};grease_frames={};qpack_encoder={qpack_encoder};qpack_decoder={qpack_decoder};request_header_strategy={:?};request_string_encoding={:?}",
526 self.open_control_stream_first,
527 self.open_qpack_encoder_before_decoder,
528 self.send_grease_stream,
529 self.send_grease_frames,
530 self.request_header_block_strategy,
531 self.request_string_encoding,
532 )
533 }
534}