specter/fingerprint/http2.rs
1//! HTTP/2 fingerprint configuration (SETTINGS frame).
2
3use std::time::Duration;
4
5/// PRIORITY frame pattern for browser fingerprinting.
6///
7/// Different browsers send PRIORITY frames with different dependency trees.
8/// Format: (stream_id, depends_on_stream_id, weight, exclusive)
9/// - exclusive: true means this stream replaces all dependencies of the parent
10/// - weight: 1-256, higher means more bandwidth allocation
11#[derive(Debug, Clone)]
12pub struct PriorityTree {
13 /// Priority frames to send: (stream_id, depends_on, weight, exclusive)
14 pub priorities: Vec<(u32, u32, u8, bool)>,
15}
16
17impl PriorityTree {
18 /// Chrome PRIORITY frame pattern.
19 ///
20 /// Chrome sends PRIORITY frames for streams 3,5,7,9,11:
21 /// - Stream 3: depends on 0 (root), weight 201
22 /// - Stream 5: depends on 0 (root), weight 101
23 /// - Stream 7: depends on 0 (root), weight 1
24 /// - Stream 9: depends on 7, weight 1
25 /// - Stream 11: depends on 3, weight 1
26 ///
27 /// Akamai format: `3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1`
28 pub fn chrome() -> Self {
29 Self {
30 priorities: vec![
31 (3, 0, 201, false), // High priority resource
32 (5, 0, 101, false), // Medium priority resource
33 (7, 0, 1, false), // Low priority resource
34 (9, 7, 1, false), // Depends on stream 7
35 (11, 3, 1, false), // Depends on stream 3
36 ],
37 }
38 }
39
40 /// Firefox PRIORITY frame pattern.
41 ///
42 /// Firefox sends PRIORITY frames for streams that haven't been opened yet,
43 /// establishing a dependency tree for future streams. Firefox uses a different
44 /// pattern than Chrome.
45 ///
46 /// The exact Firefox HTTP/2 fingerprint pattern requires verification against
47 /// real browser traffic captures.
48 /// This is a placeholder based on Firefox's known behavior of sending
49 /// PRIORITY frames for unopened streams.
50 pub fn firefox() -> Self {
51 // Firefox typically sends fewer PRIORITY frames than Chrome
52 // and uses different dependency patterns
53 Self {
54 priorities: vec![(3, 0, 201, false), (5, 0, 101, false), (7, 0, 1, false)],
55 }
56 }
57
58 /// No PRIORITY frames (some clients don't send them).
59 pub fn none() -> Self {
60 Self {
61 priorities: Vec::new(),
62 }
63 }
64}
65
66/// HTTP/2 SETTINGS for fingerprinting.
67#[derive(Debug, Clone)]
68pub struct Http2Settings {
69 pub header_table_size: u32,
70 pub enable_push: bool,
71 pub max_concurrent_streams: u32,
72 pub initial_window_size: u32,
73 pub max_frame_size: u32,
74 pub max_header_list_size: u32,
75 /// Initial connection-level WINDOW_UPDATE value sent after SETTINGS.
76 /// Chrome: 15663105 (15MB), Firefox: 12517377 (12MB)
77 pub initial_window_update: u32,
78 /// Whether to send all 6 SETTINGS parameters (Chrome) or only selective ones (Firefox).
79 /// Firefox only sends: HEADER_TABLE_SIZE (1), INITIAL_WINDOW_SIZE (4), MAX_FRAME_SIZE (5)
80 pub send_all_settings: bool,
81 /// PRIORITY frame pattern to send during connection setup.
82 /// Chrome sends PRIORITY frames for streams 3,5,7,9,11.
83 /// Firefox sends different PRIORITY patterns.
84 pub priority_tree: Option<PriorityTree>,
85 /// PING frame interval for connection keep-alive.
86 /// Chrome sends PING frames approximately every 45 seconds.
87 /// Set to None to disable automatic PING frames.
88 pub ping_interval: Option<Duration>,
89 /// Handshake timeout for waiting for server SETTINGS frame.
90 /// Default: 10 seconds (matches h2 crate behavior).
91 /// Set to None for no timeout (not recommended for production).
92 pub handshake_timeout: Option<Duration>,
93}
94
95impl Default for Http2Settings {
96 fn default() -> Self {
97 // Chrome defaults
98 Self {
99 header_table_size: 65536,
100 enable_push: false,
101 max_concurrent_streams: 1000,
102 initial_window_size: 6291456,
103 max_frame_size: 16384,
104 max_header_list_size: 262144,
105 initial_window_update: 15663105, // Chrome's 15MB window update
106 send_all_settings: true, // Chrome sends all 6 settings
107 priority_tree: Some(PriorityTree::chrome()), // Chrome sends PRIORITY frames
108 ping_interval: Some(Duration::from_secs(45)), // Chrome sends PING ~every 45s
109 handshake_timeout: Some(Duration::from_secs(10)),
110 }
111 }
112}
113
114impl Http2Settings {
115 /// Create Firefox 133 HTTP/2 settings.
116 ///
117 /// Firefox differs from Chrome:
118 /// - HEADER_TABLE_SIZE: 65536 (same)
119 /// - ENABLE_PUSH: not sent (omitted from SETTINGS frame)
120 /// - MAX_CONCURRENT_STREAMS: not sent (omitted, defaults to unlimited)
121 /// - INITIAL_WINDOW_SIZE: 131072 (128KB, vs Chrome's 6MB)
122 /// - MAX_FRAME_SIZE: 16384 (same)
123 /// - MAX_HEADER_LIST_SIZE: not sent (omitted)
124 ///
125 /// Expected Firefox Akamai SETTINGS: `1:65536;4:131072;5:16384`
126 /// Expected Firefox WINDOW_UPDATE: `12517377` (vs Chrome's 15663105)
127 pub fn firefox() -> Self {
128 Self {
129 header_table_size: 65536,
130 enable_push: true, // Firefox enables push, but doesn't send in SETTINGS
131 max_concurrent_streams: 100, // Firefox default, but not sent in SETTINGS
132 initial_window_size: 131072, // 128KB (desktop), vs Chrome's 6MB
133 max_frame_size: 16384,
134 max_header_list_size: 0, // Not sent in Firefox SETTINGS frame
135 initial_window_update: 12517377, // Firefox's 12MB window update
136 send_all_settings: false, // Firefox only sends 3 settings (1, 4, 5)
137 priority_tree: Some(PriorityTree::firefox()), // Firefox sends PRIORITY frames
138 ping_interval: Some(Duration::from_secs(30)), // Firefox sends PING ~every 30s
139 handshake_timeout: Some(Duration::from_secs(10)),
140 }
141 }
142}