1pub const MIN_STRING_LEN: usize = 6;
8
9pub fn compute_entropy(data: &[u8]) -> f32 {
13 if data.is_empty() {
14 return 0.0;
15 }
16 let mut freq = [0u32; 256];
17 for &b in data {
18 freq[b as usize] += 1;
19 }
20 let len = data.len() as f32;
21 let mut entropy = 0.0_f32;
22 for &count in &freq {
23 if count > 0 {
24 let p = count as f32 / len;
25 entropy -= p * p.log2();
26 }
27 }
28 entropy
29}
30
31pub fn extract_ascii(bytes: &[u8], min_len: usize) -> Vec<String> {
36 let mut results = Vec::new();
37 let mut current = String::new();
38 for &b in bytes {
39 if (0x20..=0x7E).contains(&b) {
40 current.push(b as char);
41 } else {
42 if current.len() >= min_len {
43 results.push(current.clone());
44 }
45 current.clear();
46 }
47 }
48 if current.len() >= min_len {
49 results.push(current);
50 }
51 results
52}
53
54pub fn extract_utf16le(bytes: &[u8], min_len: usize) -> Vec<String> {
60 let mut results = Vec::new();
61 let mut current = String::new();
62 let mut i = 0;
63 while i + 1 < bytes.len() {
64 let lo = bytes[i];
65 let hi = bytes[i + 1];
66 if hi == 0x00 && (0x20..=0x7E).contains(&lo) {
67 current.push(lo as char);
68 i += 2;
69 } else {
70 if current.len() >= min_len {
71 results.push(current.clone());
72 }
73 current.clear();
74 i += 1;
75 }
76 }
77 if current.len() >= min_len {
78 results.push(current);
79 }
80 results
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
90 fn entropy_of_empty_is_zero() {
91 assert!(compute_entropy(&[]).abs() < 1e-6);
92 }
93
94 #[test]
95 fn entropy_of_uniform_bytes_is_zero() {
96 assert!(compute_entropy(&[0x41; 64]).abs() < 1e-6);
98 }
99
100 #[test]
101 fn entropy_of_two_equiprobable_symbols_is_one_bit() {
102 let data: Vec<u8> = (0..256u32).map(|i| (i % 2) as u8).collect();
104 assert!((compute_entropy(&data) - 1.0).abs() < 1e-6);
105 }
106
107 #[test]
108 fn entropy_of_all_256_values_is_eight_bits() {
109 let data: Vec<u8> = (0..=255u8).collect();
111 assert!((compute_entropy(&data) - 8.0).abs() < 1e-6);
112 }
113
114 #[test]
117 fn ascii_extracts_simple_string() {
118 let input = b"Hello, World!";
119 let strings = extract_ascii(input, 6);
120 assert_eq!(strings, vec!["Hello, World!"]);
121 }
122
123 #[test]
124 fn ascii_skips_short_runs() {
125 let input = b"AB\x00CDEFGH";
126 let strings = extract_ascii(input, 6);
127 assert!(
128 strings.iter().all(|s| s.len() >= 6),
129 "all returned strings must be >= min_len chars"
130 );
131 assert!(
132 !strings.iter().any(|s| s == "AB"),
133 "two-char run must be filtered"
134 );
135 }
136
137 #[test]
138 fn ascii_empty_input_returns_empty() {
139 assert!(extract_ascii(&[], 6).is_empty());
140 }
141
142 #[test]
143 fn ascii_extracts_multiple_strings() {
144 let mut buf = Vec::new();
145 buf.extend_from_slice(b"VirtualAlloc");
146 buf.push(0x00);
147 buf.extend_from_slice(b"CreateRemoteThread");
148 let strings = extract_ascii(&buf, 6);
149 assert!(strings.contains(&"VirtualAlloc".to_string()));
150 assert!(strings.contains(&"CreateRemoteThread".to_string()));
151 }
152
153 #[test]
154 fn ascii_handles_all_non_printable() {
155 let input = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
156 assert!(extract_ascii(&input, 6).is_empty());
157 }
158
159 #[test]
160 fn ascii_returns_exact_min_len_string() {
161 let input = b"ABCDEF"; let strings = extract_ascii(input, 6);
163 assert!(strings.contains(&"ABCDEF".to_string()));
164 }
165
166 #[test]
169 fn utf16le_extracts_simple_string() {
170 let input: Vec<u8> = "Hello!".encode_utf16().flat_map(u16::to_le_bytes).collect();
172 let strings = extract_utf16le(&input, 6);
173 assert!(
174 strings.contains(&"Hello!".to_string()),
175 "UTF-16LE 'Hello!' must be extracted"
176 );
177 }
178
179 #[test]
180 fn utf16le_empty_input_returns_empty() {
181 assert!(extract_utf16le(&[], 6).is_empty());
182 }
183
184 #[test]
185 fn utf16le_skips_short_runs() {
186 let input: Vec<u8> = "AB".encode_utf16().flat_map(u16::to_le_bytes).collect();
188 let strings = extract_utf16le(&input, 6);
189 assert!(
190 strings.iter().all(|s| s.len() >= 6),
191 "two-char UTF-16LE run must be filtered"
192 );
193 }
194
195 #[test]
196 fn utf16le_mixed_with_binary_extracts_only_strings() {
197 let mut buf: Vec<u8> = vec![0xDE, 0xAD, 0xBE, 0xEF];
198 buf.extend("VirtualAlloc".encode_utf16().flat_map(u16::to_le_bytes));
199 buf.extend_from_slice(&[0xFF, 0xFE]);
200 let strings = extract_utf16le(&buf, 6);
201 assert!(strings.contains(&"VirtualAlloc".to_string()));
202 }
203}