1#![allow(
4 clippy::must_use_candidate,
5 clippy::cast_precision_loss,
6 clippy::cast_lossless,
7 clippy::manual_clamp,
8 clippy::unused_self,
9 clippy::unnecessary_wraps,
10 clippy::similar_names
11)]
12
13use core::fmt;
19
20use crate::{
21 Error,
22 Result,
23};
24
25#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
27pub struct EntropyQuality {
28 pub overall: f64,
30 pub uniformity: f64,
32 pub independence: f64,
34 pub predictability: f64,
36}
37
38impl EntropyQuality {
39 pub fn new(overall: f64, uniformity: f64, independence: f64, predictability: f64) -> Self {
41 Self {
42 overall,
43 uniformity,
44 independence,
45 predictability,
46 }
47 }
48
49 pub fn is_acceptable(&self, threshold: f64) -> bool {
51 self.overall >= threshold
52 }
53
54 pub fn is_excellent(&self) -> bool {
56 self.overall >= 0.95
57 }
58
59 pub fn is_good(&self) -> bool {
61 self.overall >= 0.8
62 }
63
64 pub fn is_poor(&self) -> bool {
66 self.overall < 0.5
67 }
68}
69
70impl fmt::Display for EntropyQuality {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(
73 f,
74 "EntropyQuality(overall: {:.3}, uniformity: {:.3}, independence: {:.3}, predictability: {:.3})",
75 self.overall, self.uniformity, self.independence, self.predictability
76 )
77 }
78}
79
80#[derive(Debug, Clone)]
85pub struct EntropyValidator {
86 min_entropy_bits: usize,
88 max_entropy_bits: usize,
90 quality_threshold: f64,
92 strict_mode: bool,
94}
95
96impl EntropyValidator {
97 pub fn new() -> Self {
99 Self {
100 min_entropy_bits: 128,
101 max_entropy_bits: 4096,
102 quality_threshold: 0.8,
103 strict_mode: false,
104 }
105 }
106
107 pub fn with_settings(
109 min_entropy_bits: usize,
110 max_entropy_bits: usize,
111 quality_threshold: f64,
112 strict_mode: bool,
113 ) -> Self {
114 Self {
115 min_entropy_bits,
116 max_entropy_bits,
117 quality_threshold,
118 strict_mode,
119 }
120 }
121
122 pub fn validate_entropy(&self, data: &[u8]) -> Result<EntropyQuality> {
133 if data.is_empty() {
134 return Err(Error::entropy_validation_failed("Empty entropy data", 0.0));
135 }
136
137 if data.len() < self.min_entropy_bits / 8 {
138 return Err(Error::entropy_validation_failed(
139 "Insufficient entropy data length",
140 0.0,
141 ));
142 }
143
144 if data.len() > self.max_entropy_bits / 8 {
145 return Err(Error::entropy_validation_failed(
146 "Excessive entropy data length",
147 0.0,
148 ));
149 }
150
151 let quality = self.assess_entropy_quality(data)?;
152
153 if !quality.is_acceptable(self.quality_threshold) {
154 return Err(Error::entropy_validation_failed(
155 "Entropy quality below threshold",
156 quality.overall,
157 ));
158 }
159
160 Ok(quality)
161 }
162
163 fn assess_entropy_quality(&self, data: &[u8]) -> Result<EntropyQuality> {
168 let uniformity = self.test_uniformity(data)?;
169 let independence = self.test_independence(data)?;
170 let predictability = self.test_predictability(data)?;
171
172 let overall = (uniformity * 0.4 + independence * 0.4 + (1.0 - predictability) * 0.2)
174 .max(0.0)
175 .min(1.0);
176
177 Ok(EntropyQuality::new(
178 overall,
179 uniformity,
180 independence,
181 predictability,
182 ))
183 }
184
185 fn test_uniformity(&self, data: &[u8]) -> Result<f64> {
190 if data.is_empty() {
191 return Ok(0.0);
192 }
193
194 let mut byte_counts = [0u32; 256];
195 for &byte in data {
196 byte_counts[byte as usize] += 1;
197 }
198
199 let expected = data.len() as f64 / 256.0;
201 let mut chi_square = 0.0;
202
203 for &count in &byte_counts {
204 let diff = count as f64 - expected;
205 chi_square += (diff * diff) / expected;
206 }
207
208 let expected_chi_square = 255.0;
211 let quality = if chi_square <= expected_chi_square {
212 1.0 - (chi_square / expected_chi_square) * 0.5
213 } else {
214 0.5 - ((chi_square - expected_chi_square) / expected_chi_square) * 0.5
215 };
216
217 Ok(quality.max(0.0).min(1.0))
218 }
219
220 fn test_independence(&self, data: &[u8]) -> Result<f64> {
225 if data.len() < 2 {
226 return Ok(1.0);
227 }
228
229 let mut sum_x = 0u64;
231 let mut sum_y = 0u64;
232 let mut sum_xy = 0u64;
233 let mut sum_x2 = 0u64;
234 let mut sum_y2 = 0u64;
235
236 for window in data.windows(2) {
237 let x = window[0] as u64;
238 let y = window[1] as u64;
239 sum_x += x;
240 sum_y += y;
241 sum_xy += x * y;
242 sum_x2 += x * x;
243 sum_y2 += y * y;
244 }
245
246 let n = (data.len() - 1) as f64;
247 let mean_x = sum_x as f64 / n;
248 let mean_y = sum_y as f64 / n;
249
250 let numerator = sum_xy as f64 - n * mean_x * mean_y;
251 let denominator = {
252 let x_var = sum_x2 as f64 - n * mean_x * mean_x;
253 let y_var = sum_y2 as f64 - n * mean_y * mean_y;
254 let product = x_var * y_var;
255 if product >= 0.0 {
256 #[cfg(feature = "std")]
257 {
258 product.sqrt()
259 }
260 #[cfg(not(feature = "std"))]
261 {
262 if product == 0.0 {
264 0.0
265 } else {
266 let mut x = product;
268 for _ in 0..10 {
269 x = f64::midpoint(x, product / x);
270 }
271 x
272 }
273 }
274 } else {
275 0.0
276 }
277 };
278
279 let correlation = if denominator > 0.0 {
280 numerator / denominator
281 } else {
282 0.0
283 };
284
285 let quality = 1.0 - correlation.abs();
287 Ok(quality.max(0.0).min(1.0))
288 }
289
290 fn test_predictability(&self, data: &[u8]) -> Result<f64> {
295 if data.is_empty() {
296 return Ok(0.0);
297 }
298
299 let mut runs = 0;
300 let mut max_run_length = 0;
301 let mut current_run_length = 1;
302
303 for window in data.windows(2) {
305 if window[0] == window[1] {
306 current_run_length += 1;
307 } else {
308 if current_run_length > 1 {
309 runs += 1;
310 max_run_length = max_run_length.max(current_run_length);
311 }
312 current_run_length = 1;
313 }
314 }
315
316 if current_run_length > 1 {
317 runs += 1;
318 max_run_length = max_run_length.max(current_run_length);
319 }
320
321 let run_ratio = runs as f64 / data.len() as f64;
323 let max_run_ratio = max_run_length as f64 / data.len() as f64;
324
325 let quality = 1.0 - (run_ratio + max_run_ratio) * 0.5;
327 Ok(quality.max(0.0).min(1.0))
328 }
329
330 pub fn is_strict_mode(&self) -> bool {
332 self.strict_mode
333 }
334
335 pub fn quality_threshold(&self) -> f64 {
337 self.quality_threshold
338 }
339
340 pub fn min_entropy_bits(&self) -> usize {
342 self.min_entropy_bits
343 }
344
345 pub fn max_entropy_bits(&self) -> usize {
347 self.max_entropy_bits
348 }
349}
350
351impl Default for EntropyValidator {
352 fn default() -> Self {
353 Self::new()
354 }
355}
356
357pub fn quick_entropy_check(data: &[u8]) -> bool {
371 if data.is_empty() || data.len() < 16 {
372 return false;
373 }
374
375 let mut byte_counts = [0u8; 256];
377 for &byte in data {
378 byte_counts[byte as usize] += 1;
379 }
380
381 let max_count = byte_counts.iter().max().copied().unwrap_or(0);
383 let threshold = (data.len() / 8).max(1);
384
385 if max_count as usize > threshold {
386 return false;
387 }
388
389 let mut identical_pairs = 0;
391 for window in data.windows(2) {
392 if window[0] == window[1] {
393 identical_pairs += 1;
394 }
395 }
396
397 let pair_ratio = identical_pairs as f64 / (data.len() - 1) as f64;
398 pair_ratio < 0.1
399}
400
401#[cfg(test)]
402mod tests {
403 #[cfg(feature = "alloc")]
404 use alloc::vec::Vec;
405
406 use super::*;
407
408 #[test]
409 fn test_entropy_quality_creation() {
410 let quality = EntropyQuality::new(0.8, 0.9, 0.7, 0.1);
411 #[allow(clippy::float_cmp)]
412 {
413 assert_eq!(quality.overall, 0.8);
414 assert_eq!(quality.uniformity, 0.9);
415 assert_eq!(quality.independence, 0.7);
416 assert_eq!(quality.predictability, 0.1);
417 }
418 }
419
420 #[test]
421 fn test_entropy_quality_assessment() {
422 let quality = EntropyQuality::new(0.95, 0.9, 0.8, 0.05);
423 assert!(quality.is_excellent());
424 assert!(quality.is_good());
425 assert!(!quality.is_poor());
426 assert!(quality.is_acceptable(0.8));
427 }
428
429 #[test]
430 fn test_entropy_validator_creation() {
431 let validator = EntropyValidator::new();
432 assert_eq!(validator.min_entropy_bits(), 128);
433 #[allow(clippy::float_cmp)]
434 {
435 assert_eq!(validator.quality_threshold(), 0.8);
436 }
437 assert!(!validator.is_strict_mode());
438 }
439
440 #[test]
441 fn test_entropy_validator_custom_settings() {
442 let validator = EntropyValidator::with_settings(256, 2048, 0.9, true);
443 assert_eq!(validator.min_entropy_bits(), 256);
444 assert_eq!(validator.max_entropy_bits(), 2048);
445 #[allow(clippy::float_cmp)]
446 {
447 assert_eq!(validator.quality_threshold(), 0.9);
448 }
449 assert!(validator.is_strict_mode());
450 }
451
452 #[test]
453 fn test_entropy_validation_empty_data() {
454 let validator = EntropyValidator::new();
455 let result = validator.validate_entropy(&[]);
456 assert!(result.is_err());
457 }
458
459 #[test]
460 fn test_entropy_validation_insufficient_data() {
461 let validator = EntropyValidator::new();
462 let data = [1, 2, 3, 4, 5]; let result = validator.validate_entropy(&data);
464 assert!(result.is_err());
465 }
466
467 #[test]
468 fn test_quick_entropy_check() {
469 let good_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
471 assert!(quick_entropy_check(&good_data));
472
473 let bad_data = [0u8; 16];
475 assert!(!quick_entropy_check(&bad_data));
476
477 let pattern_data = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
479 assert!(!quick_entropy_check(&pattern_data));
480 }
481
482 #[test]
483 #[cfg(feature = "alloc")]
484 fn test_uniformity_test() {
485 let validator = EntropyValidator::new();
486
487 let uniform_data: Vec<u8> = (0..=255).cycle().take(1024).collect();
489 let quality = validator.test_uniformity(&uniform_data).unwrap();
490 assert!(quality > 0.8);
491
492 let non_uniform_data = [0u8; 1024];
494 let quality = validator.test_uniformity(&non_uniform_data).unwrap();
495 assert!(quality < 0.5);
496 }
497
498 #[test]
499 #[cfg(feature = "alloc")]
500 fn test_independence_test() {
501 let validator = EntropyValidator::new();
502
503 let independent_data: Vec<u8> = (0..=255).cycle().take(1024).collect();
505 let quality = validator.test_independence(&independent_data).unwrap();
506 assert!(quality > 0.0); let correlated_data: Vec<u8> = (0..128).flat_map(|i| [i, i]).take(1024).collect();
511 let quality = validator.test_independence(&correlated_data).unwrap();
512 assert!(quality < 1.0);
514 }
515
516 #[test]
517 #[cfg(feature = "alloc")]
518 fn test_predictability_test() {
519 let validator = EntropyValidator::new();
520
521 let unpredictable_data: Vec<u8> = (0..=255).cycle().take(1024).collect();
523 let quality = validator.test_predictability(&unpredictable_data).unwrap();
524 assert!(quality > 0.8);
525
526 let predictable_data = [0u8; 1024];
528 let quality = validator.test_predictability(&predictable_data).unwrap();
529 assert!(quality < 0.5);
530 }
531}