string_analyze/
entropy.rs1use std::cell::RefCell;
8
9#[inline(always)]
16fn calc_entropy<'a, I>(counts: I, total_grams: f64) -> f64
17where
18 I: Iterator<Item = &'a u32>,
19{
20 let sum_c_log2_c: f64 = counts
21 .filter(|&&c| c > 0)
22 .map(|&c| {
23 let c_f64 = c as f64;
24 c_f64 * c_f64.log2()
25 })
26 .sum();
27 total_grams.log2() - (sum_c_log2_c / total_grams)
28}
29
30pub fn entropy(bytes: &[u8]) -> f64 {
38 if bytes.is_empty() {
39 return 0.0;
40 }
41
42 let mut counts = [0u32; 256];
43 for &b in bytes {
44 counts[b as usize] += 1;
45 }
46 calc_entropy(counts.iter(), bytes.len() as f64)
47}
48
49pub fn delta_entropy(bytes: &[u8]) -> f64 {
58 let len = bytes.len();
59
60 if len < 2 {
61 return 0.0;
62 }
63
64 let mut counts = [0u32; 256];
65 for window in bytes.windows(2) {
66 let delta = window[1].wrapping_sub(window[0]);
67 counts[delta as usize] += 1;
68 }
69
70 calc_entropy(counts.iter(), (len - 1) as f64)
71}
72
73pub fn gram_entropy2(bytes: &[u8]) -> f64 {
82 thread_local! {
83 static GRAM_BUFFER: RefCell<(Vec<u32>, Vec<u16>)> = RefCell::new((vec![0; 65536], Vec::with_capacity(4096)));
85 }
86 let len = bytes.len();
87
88 if len < 2 {
89 return 0.0;
90 }
91
92 GRAM_BUFFER.with(|buf| {
93 let (counts, visited) = &mut *buf.borrow_mut();
94 let total_grams = (len - 1) as f64;
95
96 for w in bytes.windows(2) {
97 let idx = ((w[0] as usize) << 8) | (w[1] as usize);
98 if counts[idx] == 0 {
99 visited.push(idx as u16);
100 }
101 counts[idx] += 1;
102 }
103
104 let mut sum_c_log2_c = 0.0;
105 for &idx in visited.iter() {
106 let u_idx = idx as usize;
107 let c = counts[u_idx];
108 counts[u_idx] = 0; let c_f64 = c as f64;
111 sum_c_log2_c += c_f64 * c_f64.log2();
112 }
113
114 visited.clear();
115
116 total_grams.log2() - (sum_c_log2_c / total_grams)
117 })
118}
119
120pub fn composite_entropy(bytes: &[u8]) -> f64 {
133 let len = bytes.len();
134 if len < 4 {
135 return 0.0;
136 }
137
138 let e1 = entropy(bytes);
139 let e2 = delta_entropy(bytes);
140 let e3 = gram_entropy2(bytes);
141
142 const EPS: f64 = 1e-6;
143 let n1 = (e1 / 8.0).clamp(EPS, 1.0);
144 let n2 = (e2 / 8.0).clamp(EPS, 1.0);
145
146 let n3_raw = (e3 / 16.0).clamp(EPS, 1.0);
147
148 let n3 = {
150 let x = n3_raw;
151 (x * x * (10.0 + x * (-20.0 + x * (15.0 - 4.0 * x)))).clamp(EPS, 1.0)
152 };
153
154 const W1: f64 = 0.6; const W2: f64 = 0.3; const W3: f64 = 0.1; const P: f64 = -1.6; let mean_pow = W1 * n1.powf(P) + W2 * n2.powf(P) + W3 * n3.powf(P);
162 let combined = mean_pow.powf(1.0 / P);
163
164 (combined * 10.0).clamp(0.0, 10.0)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use rand::{RngExt, random, rng};
171
172 #[test]
173 fn test_entropy() {
174 let input = &[
175 "unittests src/lib.rs (target/debug/deps/string_analyze-551ebe6d3e6cc1ac)",
176 "0123456789ABCDEF0123456789abcdef0123456789ABCDEF0123456789abcdef",
177 "96ef6443c6effd748c7202c04f0577f92761b821db19fdfea2a5c2364b439209",
178 "horizontal-scroll-mode",
179 "112233445566778899aabbccddeeffxxyyzz112233445566778899aabbccddeeffxxyyzz",
180 "aGVsbG8=",
181 "MDEyMzQ1Njc4OUFNREV5TXpRMU5qYzRPVUZDUTBSRlJqQXhNak09QkNERUYwMTIz",
182 "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==",
183 "12~3",
184 "1[82~3",
185 "Bitcoin/IPFS",
186 " ",
187 "~呜嗷~~嗷嗷呜~嗷呜喵呜喵呜喵~嗷~~呜~喵喵呜嗷喵嗷呜嗷呜嗷~喵~呜呜呜喵嗷呜喵喵喵呜喵喵",
188 "~呜嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵",
189 "~~~~~~~~~~~!!!!!!!!!!!!!!###############",
190 "ccVoejdMVQnfrzpgIunpQchW6WXcCnuX7leuRhzY3Ripc26KwOtCqxBRcjmHbN9PiaesCgHTi9iF",
191 r#"M_2cL`Eqq:;|z_hiRTvhW@RLyq&d+UPXf6$ZarHI+9AiM[ZcQ"8egZ4m3Ur>J*AJE>kitQ@5Exo!C3$z.c[E#{*Rq5D"#,
192 "豫章故郡,洪都新府。星分翼軫,地接衡廬。襟三江而帶五湖,控蠻荊而引甌越。物華天寶,龍光射牛斗之墟;人傑地靈,徐孺下陳蕃之榻。雄州霧列,俊彩星馳。臺隍枕夷夏之交,賓主盡東南之美。都督閻公之雅望,棨戟遙臨;宇文新州之懿範,襜帷暫駐。十旬休假,勝友如雲。千里逢迎,高朋滿座。騰蛟起鳳,孟學士之詞宗;紫電青霜,王將軍之武庫。家君作宰,路出名區。童子何知?躬逢勝餞。",
193 ];
194
195 for s in input {
196 println!(
197 "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
198 );
199 println!(
200 "[entropy ] [{:<5.3}]\t{s:.128}",
201 entropy(s.as_bytes())
202 );
203 println!(
204 "[delta_entropy ] [{:<5.3}]\t{s:.128}",
205 delta_entropy(s.as_bytes())
206 );
207 println!(
208 "[gram_entropy2 ] [{:<5.3}]\t{s:.128}",
209 gram_entropy2(s.as_bytes())
210 );
211 println!(
212 "[composite_entropy] [{:<5.3}]\t{s:.128}",
213 composite_entropy(s.as_bytes())
214 );
215 }
216 let input = &mut vec![
217 b"\xd6\x23\x15\x9e\xbd\xe2\x90\xf9\xaa\xa0\x2e\xa0\x80\x9a\xa6\xf3\xcd\x31\xaa\x0d\x04\x6f\x51\x9c\xf1\x34\xcd\xef\x41\x29\xa4\x28\x44\x0c\xf7\x2d\xbb\x3d\x69\xf2\x03\xff\x9d\x54\x95\x25\x2d\x83\xd2\x80\x8d\x44\xef\xef\xf5\x6a\xf8\xc3\x61\x99\x9c\xe9\x2c\xec\x23\x3b\xea\xf3\x43\x1f\x57\xbd\x45\xce\x0e\x96\x4c\xb7\xbf\x28\x08\x16\x77\x53\x83\x59\x16\xd3\xac".as_slice()
218 ];
219 let x256 = random::<[u8; 256]>();
220 input.push(x256.as_slice());
221 let x512 = random::<[u8; 512]>();
222 input.push(x512.as_slice());
223
224 let x1024 = random::<[u8; 1024]>();
225 input.push(x1024.as_slice());
226
227 let mut x4096 = vec![0u8; 4096];
228 rng().fill(&mut x4096);
229 input.push(x4096.as_slice());
230
231 for b in input {
232 println!(
233 "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
234 );
235 println!(
236 "[entropy ] [{:<5.3}]\t{:.128}",
237 entropy(b),
238 format!("{b:?}")
239 );
240 println!(
241 "[delta_entropy ] [{:<5.3}]\t{:.128}",
242 delta_entropy(b),
243 format!("{b:?}")
244 );
245 println!(
246 "[gram_entropy2 ] [{:<5.3}]\t{:.128}",
247 gram_entropy2(b),
248 format!("{b:?}")
249 );
250 println!(
251 "[composite_entropy] [{:<5.3}]\t{:.128}",
252 composite_entropy(b),
253 format!("{b:?}")
254 );
255 }
256 }
257}