Skip to main content

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}