1mod map;
4
5pub use map::{
6 generate_map, analyze_map, get_expansion_ratio,
7 GenerateMapOptions, TemperatureConfig, MapAnalysis,
8 get_temperature_profile, get_config_from_temperature,
9};
10
11use crate::Result;
12use alloc::{
13 collections::BTreeMap,
14 string::{String, ToString},
15 vec::Vec,
16};
17use serde::{Deserialize, Serialize};
18
19pub type ObfuscationMap = BTreeMap<char, Vec<String>>;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ObfuscationResult {
25 pub obfuscated: String,
27 pub stats: ObfuscationStats,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ObfuscationStats {
34 pub original_length: usize,
36 pub obfuscated_length: usize,
38 pub expansion_ratio: f64,
40 pub unique_chars_obfuscated: usize,
42 pub mappings_used: usize,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub enum SelectionStrategy {
49 #[default]
51 Random,
52 RoundRobin,
54 Shortest,
56 Longest,
58}
59
60#[derive(Debug, Clone)]
62pub struct ObfuscationOptions {
63 pub seed: String,
65 pub strategy: SelectionStrategy,
67}
68
69impl Default for ObfuscationOptions {
70 fn default() -> Self {
71 Self {
72 seed: "default-seed".to_string(),
73 strategy: SelectionStrategy::Random,
74 }
75 }
76}
77
78pub const DELIMITER: char = '\x1F';
82
83struct SeededRandom {
85 state: u64,
86}
87
88impl SeededRandom {
89 fn new(seed: &str) -> Self {
90 let mut hash: u64 = 0;
91 for (i, byte) in seed.bytes().enumerate() {
92 hash = hash.wrapping_add((byte as u64).wrapping_mul(31u64.wrapping_pow(i as u32)));
93 }
94 Self { state: hash.max(1) }
95 }
96
97 fn next(&mut self) -> u64 {
98 self.state = self.state.wrapping_mul(9301).wrapping_add(49297) % 233280;
99 self.state
100 }
101
102 fn next_usize(&mut self, max: usize) -> usize {
103 (self.next() as usize) % max
104 }
105}
106
107pub fn obfuscate(
109 text: &str,
110 map: &ObfuscationMap,
111 options: Option<ObfuscationOptions>,
112) -> ObfuscationResult {
113 let opts = options.unwrap_or_default();
114 let _rng = SeededRandom::new(&opts.seed);
115
116 let mut tokens: Vec<String> = Vec::with_capacity(text.len());
117 let mut unique_chars = std::collections::HashSet::new();
118 let mut mappings_used = 0;
119
120 for (i, char) in text.chars().enumerate() {
121 if let Some(mappings) = map.get(&char) {
122 if !mappings.is_empty() {
123 unique_chars.insert(char);
124
125 let selected = match opts.strategy {
126 SelectionStrategy::Random => {
127 let entropy = (char as u64).wrapping_add(i as u64);
129 let mut local_rng = SeededRandom::new(&format!("{}{}", opts.seed, entropy));
130 &mappings[local_rng.next_usize(mappings.len())]
131 }
132 SelectionStrategy::RoundRobin => {
133 &mappings[i % mappings.len()]
134 }
135 SelectionStrategy::Shortest => {
136 mappings.iter().min_by_key(|s| s.len()).unwrap()
137 }
138 SelectionStrategy::Longest => {
139 mappings.iter().max_by_key(|s| s.len()).unwrap()
140 }
141 };
142
143 tokens.push(selected.clone());
144 mappings_used += 1;
145 continue;
146 }
147 }
148
149 if char == DELIMITER {
153 tokens.push("_DELIM_".to_string());
156 } else {
157 tokens.push(char.to_string());
158 }
159 }
160
161 let obfuscated = tokens.join(&DELIMITER.to_string());
162 let original_length = text.len();
163 let obfuscated_length = obfuscated.len();
164
165 ObfuscationResult {
166 obfuscated,
167 stats: ObfuscationStats {
168 original_length,
169 obfuscated_length,
170 expansion_ratio: obfuscated_length as f64 / original_length as f64,
171 unique_chars_obfuscated: unique_chars.len(),
172 mappings_used,
173 },
174 }
175}
176
177pub fn deobfuscate(obfuscated: &str, map: &ObfuscationMap) -> String {
179 let mut reverse_map: BTreeMap<&str, char> = BTreeMap::new();
181 for (char, mappings) in map.iter() {
182 for mapping in mappings {
183 reverse_map.insert(mapping.as_str(), *char);
184 }
185 }
186
187 obfuscated
189 .split(DELIMITER)
190 .map(|token| {
191 if token == "_DELIM_" {
193 return DELIMITER.to_string();
194 }
195 if let Some(&original) = reverse_map.get(token) {
196 original.to_string()
197 } else {
198 token.to_string()
200 }
201 })
202 .collect()
203}
204
205pub fn test_roundtrip(
207 text: &str,
208 map: &ObfuscationMap,
209 options: Option<ObfuscationOptions>,
210) -> Result<bool> {
211 let result = obfuscate(text, map, options);
212 let deobfuscated = deobfuscate(&result.obfuscated, map);
213 Ok(text == deobfuscated)
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 fn create_test_map() -> ObfuscationMap {
221 let mut map = ObfuscationMap::new();
222 map.insert('a', vec!["alpha".to_string(), "apple".to_string()]);
223 map.insert('b', vec!["bravo".to_string(), "banana".to_string()]);
224 map.insert('c', vec!["charlie".to_string()]);
225 map.insert(' ', vec!["_space_".to_string()]);
226 map
227 }
228
229 #[test]
230 fn test_obfuscate_deobfuscate() {
231 let map = create_test_map();
232 let text = "abc";
233
234 let result = obfuscate(text, &map, None);
235 let deobfuscated = deobfuscate(&result.obfuscated, &map);
236
237 assert_eq!(text, deobfuscated);
238 }
239
240 #[test]
241 fn test_deterministic_with_seed() {
242 let map = create_test_map();
243 let text = "abc";
244 let options = ObfuscationOptions {
245 seed: "test-seed".to_string(),
246 strategy: SelectionStrategy::Random,
247 };
248
249 let result1 = obfuscate(text, &map, Some(options.clone()));
250 let result2 = obfuscate(text, &map, Some(options));
251
252 assert_eq!(result1.obfuscated, result2.obfuscated);
254 }
255
256 #[test]
257 fn test_different_seeds() {
258 let map = create_test_map();
259 let text = "aaa"; let result1 = obfuscate(text, &map, Some(ObfuscationOptions {
262 seed: "seed1".to_string(),
263 strategy: SelectionStrategy::Random,
264 }));
265
266 let result2 = obfuscate(text, &map, Some(ObfuscationOptions {
267 seed: "seed2".to_string(),
268 strategy: SelectionStrategy::Random,
269 }));
270
271 assert_eq!(deobfuscate(&result1.obfuscated, &map), text);
274 assert_eq!(deobfuscate(&result2.obfuscated, &map), text);
275 }
276
277 #[test]
278 fn test_selection_strategies() {
279 let map = create_test_map();
280 let text = "a";
281
282 let shortest = obfuscate(text, &map, Some(ObfuscationOptions {
283 seed: "test".to_string(),
284 strategy: SelectionStrategy::Shortest,
285 }));
286
287 let longest = obfuscate(text, &map, Some(ObfuscationOptions {
288 seed: "test".to_string(),
289 strategy: SelectionStrategy::Longest,
290 }));
291
292 assert_eq!(deobfuscate(&shortest.obfuscated, &map), "a");
295 assert_eq!(deobfuscate(&longest.obfuscated, &map), "a");
296 }
297
298 #[test]
299 fn test_unmapped_characters() {
300 let map = create_test_map();
301 let text = "a1b2c"; let result = obfuscate(text, &map, None);
304 let deobfuscated = deobfuscate(&result.obfuscated, &map);
305
306 assert_eq!(text, deobfuscated);
307 }
308
309 #[test]
310 fn test_roundtrip_fn() {
311 let map = create_test_map();
312 let text = "abc test";
313
314 assert!(super::test_roundtrip(text, &map, None).unwrap());
315 }
316}
317