1#![allow(dead_code, unused_imports)]
2mod behavior;
4mod canvas;
5pub mod engine_props;
6pub mod hooks;
7mod http2;
8mod navigator;
9mod profile;
10pub mod stealth_wire;
11mod tls;
12mod webgl_audio;
13
14pub use behavior::{BehaviorConfig, BehaviorSimulator, ClickEvent, ClickEventType, TypingEvent};
15pub use canvas::CanvasNoise;
16pub use hooks::StealthHooks;
17pub use http2::Http2Fingerprint;
18pub use http2::{PriorityFrame, PriorityFrameMode};
19pub use http2::{global_http2_fingerprint, set_global_http2_fingerprint};
20pub use navigator::{NavigatorProfile, ScreenProfile};
21pub use profile::StealthProfile;
22pub use profile::{
23 BatteryConfig, ClientRectsConfig, FontConfig, ScreenDisplayConfig, TimingConfig, WebRtcMode,
24};
25pub use stealth_wire::StealthTlsWireConfig;
26pub use tls::TlsFingerprint;
27pub use tls::TlsFingerprintConfig;
28pub use tls::boringssl_cipher_list_string;
31pub use tls::boringssl_curves_list_string;
32pub use tls::boringssl_sigalgs_list_string;
33pub use tls::cipher_suite_boringssl_supported;
34pub use tls::cipher_suite_openssl_name;
35pub use tls::group_boringssl_supported;
36pub use tls::group_openssl_name;
37pub use tls::sigalg_openssl_name;
38pub use webgl_audio::{AudioProfile, WebGLProfile};
39
40pub struct StealthEngine {
41 profile: StealthProfile,
42}
43
44impl StealthEngine {
45 pub fn new(profile: StealthProfile) -> Self {
46 StealthEngine { profile }
47 }
48
49 #[allow(clippy::new_ret_no_self)]
50 pub fn default_engine() -> Self {
51 Self::new(StealthProfile::firefox_default())
52 }
53
54 pub fn profile(&self) -> &StealthProfile {
55 &self.profile
56 }
57
58 pub fn tls_config(&self) -> &TlsFingerprint {
59 &self.profile.tls
60 }
61
62 pub fn http2_config(&self) -> &Http2Fingerprint {
63 &self.profile.http2
64 }
65
66 pub fn canvas_noise(&self) -> &CanvasNoise {
67 &self.profile.canvas
68 }
69
70 pub fn navigator(&self) -> &NavigatorProfile {
71 &self.profile.navigator
72 }
73
74 pub fn screen(&self) -> &ScreenProfile {
75 &self.profile.screen
76 }
77
78 pub fn webgl(&self) -> &WebGLProfile {
79 &self.profile.webgl
80 }
81
82 pub fn audio(&self) -> &AudioProfile {
83 &self.profile.audio
84 }
85
86 pub fn behavior(&self) -> &BehaviorSimulator {
87 &self.profile.behavior
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn new_stores_profile() {
97 let profile = StealthProfile::firefox_default();
98 let engine = StealthEngine::new(profile.clone());
99 assert_eq!(
100 engine.profile().navigator.user_agent,
101 profile.navigator.user_agent
102 );
103 }
104
105 #[test]
106 fn default_engine_is_firefox() {
107 let engine = StealthEngine::default_engine();
108 let firefox = StealthProfile::firefox_default();
109 assert_eq!(
110 engine.profile().navigator.user_agent,
111 firefox.navigator.user_agent
112 );
113 }
114
115 #[test]
116 fn tls_config_matches_profile() {
117 let engine = StealthEngine::default_engine();
118 assert_eq!(engine.tls_config().ja3_hash, engine.profile().tls.ja3_hash);
119 }
120
121 #[test]
122 fn http2_config_matches_profile() {
123 let engine = StealthEngine::default_engine();
124 assert_eq!(
125 engine.http2_config().header_table_size,
126 engine.profile().http2.header_table_size
127 );
128 }
129
130 #[test]
131 fn canvas_noise_matches_profile() {
132 let engine = StealthEngine::default_engine();
133 assert_eq!(engine.canvas_noise().seed(), engine.profile().canvas.seed());
134 }
135
136 #[test]
137 fn navigator_matches_profile() {
138 let engine = StealthEngine::default_engine();
139 assert_eq!(
140 engine.navigator().user_agent,
141 engine.profile().navigator.user_agent
142 );
143 }
144
145 #[test]
146 fn screen_matches_profile() {
147 let engine = StealthEngine::default_engine();
148 assert_eq!(engine.screen().width, engine.profile().screen.width);
149 assert_eq!(engine.screen().height, engine.profile().screen.height);
150 }
151
152 #[test]
153 fn webgl_matches_profile() {
154 let engine = StealthEngine::default_engine();
155 assert_eq!(engine.webgl().vendor, engine.profile().webgl.vendor);
156 assert_eq!(engine.webgl().renderer, engine.profile().webgl.renderer);
157 }
158
159 #[test]
160 fn audio_matches_profile() {
161 let engine = StealthEngine::default_engine();
162 assert!(
163 (engine.audio().noise_amplitude() - engine.profile().audio.noise_amplitude()).abs()
164 < f64::EPSILON
165 );
166 }
167
168 #[test]
169 fn behavior_matches_profile() {
170 let engine = StealthEngine::default_engine();
171 assert_eq!(engine.behavior().seed(), engine.profile().behavior.seed());
172 }
173}