1use strum::VariantArray;
2
3use super::{Emulation, EmulationOS, EmulationOption};
4
5const CHROME_WEIGHT: f32 = 0.714;
7const SAFARI_WEIGHT: f32 = 0.150;
8const EDGE_WEIGHT: f32 = 0.050;
9const FIREFOX_WEIGHT: f32 = 0.030;
10const OPERA_WEIGHT: f32 = 0.020;
11const OKHTTP_WEIGHT: f32 = 0.010;
12
13macro_rules! weight_variants {
16 ($weight:expr, $($v:expr),+ $(,)?) => {{
17 const COUNT: usize = [$(stringify!($v)),+].len();
18 const W: f32 = $weight / COUNT as f32;
19 &[$(($v, W)),+] as &[(Emulation, f32)]
20 }};
21}
22
23const WEIGHTED: &[&[(Emulation, f32)]] = &[
24 weight_variants!(
25 CHROME_WEIGHT,
26 Emulation::Chrome100,
27 Emulation::Chrome101,
28 Emulation::Chrome104,
29 Emulation::Chrome105,
30 Emulation::Chrome106,
31 Emulation::Chrome107,
32 Emulation::Chrome108,
33 Emulation::Chrome109,
34 Emulation::Chrome110,
35 Emulation::Chrome114,
36 Emulation::Chrome116,
37 Emulation::Chrome117,
38 Emulation::Chrome118,
39 Emulation::Chrome119,
40 Emulation::Chrome120,
41 Emulation::Chrome123,
42 Emulation::Chrome124,
43 Emulation::Chrome126,
44 Emulation::Chrome127,
45 Emulation::Chrome128,
46 Emulation::Chrome129,
47 Emulation::Chrome130,
48 Emulation::Chrome131,
49 Emulation::Chrome132,
50 Emulation::Chrome133,
51 Emulation::Chrome134,
52 Emulation::Chrome135,
53 Emulation::Chrome136,
54 Emulation::Chrome137,
55 Emulation::Chrome138,
56 Emulation::Chrome139,
57 Emulation::Chrome140,
58 Emulation::Chrome141,
59 Emulation::Chrome142,
60 Emulation::Chrome143,
61 Emulation::Chrome144,
62 Emulation::Chrome145,
63 Emulation::Chrome146,
64 Emulation::Chrome147,
65 Emulation::Chrome148,
66 Emulation::Chrome149,
67 ),
68 weight_variants!(
69 SAFARI_WEIGHT,
70 Emulation::SafariIos17_2,
71 Emulation::SafariIos17_4_1,
72 Emulation::SafariIos16_5,
73 Emulation::Safari15_3,
74 Emulation::Safari15_5,
75 Emulation::Safari15_6_1,
76 Emulation::Safari16,
77 Emulation::Safari16_5,
78 Emulation::Safari17_0,
79 Emulation::Safari17_2_1,
80 Emulation::Safari17_4_1,
81 Emulation::Safari17_5,
82 Emulation::Safari17_6,
83 Emulation::Safari18,
84 Emulation::SafariIPad18,
85 Emulation::Safari18_2,
86 Emulation::SafariIos18_1_1,
87 Emulation::Safari18_3,
88 Emulation::Safari18_3_1,
89 Emulation::Safari18_5,
90 Emulation::Safari26,
91 Emulation::Safari26_1,
92 Emulation::Safari26_2,
93 Emulation::SafariIPad26,
94 Emulation::SafariIPad26_2,
95 Emulation::SafariIos26,
96 Emulation::SafariIos26_2,
97 Emulation::Safari19,
98 Emulation::SafariIos19,
99 Emulation::SafariIPad19,
100 Emulation::Safari20,
101 Emulation::SafariIos20,
102 Emulation::SafariIPad20,
103 Emulation::Safari21,
104 Emulation::SafariIos21,
105 Emulation::SafariIPad21,
106 Emulation::Safari22,
107 Emulation::SafariIos22,
108 Emulation::SafariIPad22,
109 Emulation::Safari23,
110 Emulation::SafariIos23,
111 Emulation::SafariIPad23,
112 Emulation::Safari24,
113 Emulation::SafariIos24,
114 Emulation::SafariIPad24,
115 Emulation::Safari25,
116 Emulation::SafariIos25,
117 Emulation::SafariIPad25,
118 Emulation::Safari26_3,
119 Emulation::SafariIos26_3,
120 Emulation::SafariIPad26_3,
121 Emulation::Safari26_4,
122 Emulation::SafariIos26_4,
123 Emulation::SafariIPad26_4,
124 ),
125 weight_variants!(
126 EDGE_WEIGHT,
127 Emulation::Edge101,
128 Emulation::Edge122,
129 Emulation::Edge127,
130 Emulation::Edge131,
131 Emulation::Edge134,
132 Emulation::Edge135,
133 Emulation::Edge136,
134 Emulation::Edge137,
135 Emulation::Edge138,
136 Emulation::Edge139,
137 Emulation::Edge140,
138 Emulation::Edge141,
139 Emulation::Edge142,
140 Emulation::Edge143,
141 Emulation::Edge144,
142 Emulation::Edge145,
143 Emulation::Edge146,
144 Emulation::Edge147,
145 Emulation::Edge148,
146 ),
147 weight_variants!(
148 FIREFOX_WEIGHT,
149 Emulation::Firefox109,
150 Emulation::Firefox117,
151 Emulation::Firefox128,
152 Emulation::Firefox133,
153 Emulation::Firefox135,
154 Emulation::FirefoxPrivate135,
155 Emulation::FirefoxAndroid135,
156 Emulation::Firefox136,
157 Emulation::FirefoxPrivate136,
158 Emulation::Firefox137,
159 Emulation::Firefox138,
160 Emulation::Firefox139,
161 Emulation::Firefox140,
162 Emulation::Firefox141,
163 Emulation::Firefox142,
164 Emulation::Firefox143,
165 Emulation::Firefox144,
166 Emulation::Firefox145,
167 Emulation::Firefox146,
168 Emulation::Firefox147,
169 Emulation::Firefox148,
170 Emulation::Firefox149,
171 Emulation::Firefox150,
172 Emulation::Firefox151,
173 ),
174 weight_variants!(
175 OPERA_WEIGHT,
176 Emulation::Opera116,
177 Emulation::Opera117,
178 Emulation::Opera118,
179 Emulation::Opera119,
180 Emulation::Opera120,
181 Emulation::Opera121,
182 Emulation::Opera122,
183 Emulation::Opera123,
184 Emulation::Opera124,
185 Emulation::Opera125,
186 Emulation::Opera126,
187 Emulation::Opera127,
188 Emulation::Opera128,
189 Emulation::Opera129,
190 Emulation::Opera130,
191 Emulation::Opera131,
192 ),
193 weight_variants!(
194 OKHTTP_WEIGHT,
195 Emulation::OkHttp3_9,
196 Emulation::OkHttp3_11,
197 Emulation::OkHttp3_13,
198 Emulation::OkHttp3_14,
199 Emulation::OkHttp4_9,
200 Emulation::OkHttp4_10,
201 Emulation::OkHttp4_12,
202 Emulation::OkHttp5,
203 ),
204];
205
206impl Emulation {
207 #[inline]
225 pub fn random() -> EmulationOption {
226 let emulation = Emulation::VARIANTS;
227 let emulation_os = EmulationOS::VARIANTS;
228 let rand = rand::random::<u64>() as usize;
229 EmulationOption::builder()
230 .emulation(emulation[rand % emulation.len()])
231 .emulation_os(emulation_os[rand % emulation_os.len()])
232 .build()
233 }
234
235 #[inline]
251 pub fn weighted_random() -> EmulationOption {
252 let r = rand::random::<f32>();
253 let mut cumulative = 0.0f32;
254 let emulation = 'outer: {
255 for variants in WEIGHTED {
256 for &(emu, w) in *variants {
257 cumulative += w;
258 if r < cumulative {
259 break 'outer emu;
260 }
261 }
262 }
263 Emulation::OkHttp5
265 };
266 let emulation_os = EmulationOS::VARIANTS;
267 let os_idx = rand::random::<u64>() as usize % emulation_os.len();
268 EmulationOption::builder()
269 .emulation(emulation)
270 .emulation_os(emulation_os[os_idx])
271 .build()
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use std::{sync::Arc, thread};
278
279 use parking_lot::Mutex;
280
281 use super::*;
282
283 #[test]
284 fn test_concurrent_get_random_emulation() {
285 const THREAD_COUNT: usize = 10;
286 const ITERATIONS: usize = 100;
287
288 let results = Arc::new(Mutex::new(Vec::new()));
289
290 let mut handles = vec![];
291
292 for _ in 0..THREAD_COUNT {
293 let results = Arc::clone(&results);
294 let handle = thread::spawn(move || {
295 for _ in 0..ITERATIONS {
296 let emulation = Emulation::random();
297 let mut results = results.lock();
298 results.push(emulation);
299 }
300 });
301 handles.push(handle);
302 }
303
304 for handle in handles {
305 handle.join().expect("worker thread panicked");
306 }
307
308 let results = results.lock();
309 println!("Total results: {}", results.len());
310 }
311
312 #[test]
313 fn test_weighted_random_chrome_frequency() {
314 const N: usize = 10_000;
315 let mut chrome_count: usize = 0;
316 for _ in 0..N {
317 let opt = Emulation::weighted_random();
318 if format!("{:?}", opt.emulation).starts_with("Chrome") {
319 chrome_count += 1;
320 }
321 }
322 let freq = chrome_count as f64 / N as f64;
323 assert!(
324 (0.66..=0.77).contains(&freq),
325 "Chrome frequency {freq:.3} outside [0.66, 0.77]"
326 );
327 }
328}