1use std::collections::hash_map::DefaultHasher;
9use std::collections::HashMap;
10use std::hash::{Hash, Hasher};
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum HashAlgorithm {
21 SipHash,
23 Fnv1a32,
25 Fnv1a64,
27}
28
29impl Default for HashAlgorithm {
30 fn default() -> Self {
31 Self::SipHash
32 }
33}
34
35impl HashAlgorithm {
36 pub fn as_str(&self) -> &'static str {
38 match self {
39 Self::SipHash => "siphash",
40 Self::Fnv1a32 => "fnv1a32",
41 Self::Fnv1a64 => "fnv1a64",
42 }
43 }
44
45 pub fn parse_name(name: &str) -> Option<Self> {
47 match name.to_lowercase().as_str() {
48 "siphash" => Some(Self::SipHash),
49 "fnv1a32" => Some(Self::Fnv1a32),
50 "fnv1a64" => Some(Self::Fnv1a64),
51 _ => None,
52 }
53 }
54
55 pub fn hash_hex(&self, input: &str) -> String {
57 match self {
58 Self::SipHash => {
59 let mut hasher = DefaultHasher::new();
60 input.hash(&mut hasher);
61 format!("{:016x}", hasher.finish())
62 }
63 Self::Fnv1a32 => format!("{:08x}", fnv1a_32(input.as_bytes())),
64 Self::Fnv1a64 => format!("{:016x}", fnv1a_64(input.as_bytes())),
65 }
66 }
67}
68
69fn fnv1a_32(bytes: &[u8]) -> u32 {
71 const FNV_OFFSET: u32 = 0x811c9dc5;
72 const FNV_PRIME: u32 = 0x01000193;
73 let mut hash = FNV_OFFSET;
74 for &byte in bytes {
75 hash ^= u32::from(byte);
76 hash = hash.wrapping_mul(FNV_PRIME);
77 }
78 hash
79}
80
81fn fnv1a_64(bytes: &[u8]) -> u64 {
83 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
84 const FNV_PRIME: u64 = 0x00000100000001b3;
85 let mut hash = FNV_OFFSET;
86 for &byte in bytes {
87 hash ^= u64::from(byte);
88 hash = hash.wrapping_mul(FNV_PRIME);
89 }
90 hash
91}
92
93#[derive(Debug, Clone)]
98pub struct HashMasker {
99 algorithm: HashAlgorithm,
100 keep_prefix: usize,
101 suffix: String,
102}
103
104impl Default for HashMasker {
105 fn default() -> Self {
106 Self {
107 algorithm: HashAlgorithm::default(),
108 keep_prefix: 12,
109 suffix: "...".to_string(),
110 }
111 }
112}
113
114impl HashMasker {
115 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn with_algorithm(mut self, algo: HashAlgorithm) -> Self {
122 self.algorithm = algo;
123 self
124 }
125
126 pub fn with_keep_prefix(mut self, n: usize) -> Self {
128 self.keep_prefix = n;
129 self
130 }
131
132 pub fn with_suffix(mut self, suffix: &str) -> Self {
134 self.suffix = suffix.to_string();
135 self
136 }
137
138 pub fn algorithm(&self) -> HashAlgorithm {
140 self.algorithm
141 }
142
143 pub fn keep_prefix(&self) -> usize {
145 self.keep_prefix
146 }
147
148 pub fn suffix(&self) -> &str {
150 &self.suffix
151 }
152
153 pub fn mask(&self, value: &str) -> String {
155 if value.is_empty() {
156 return self.suffix.clone();
157 }
158 let hex = self.algorithm.hash_hex(value);
159 let prefix = if hex.len() <= self.keep_prefix {
160 hex.as_str()
161 } else {
162 &hex[..self.keep_prefix]
163 };
164 format!("{}{}", prefix, self.suffix)
165 }
166
167 pub fn mask_fields(
169 &self,
170 fields: &[String],
171 data: &HashMap<String, String>,
172 ) -> HashMap<String, String> {
173 data.iter()
174 .map(|(k, v)| {
175 if fields.contains(k) {
176 (k.clone(), self.mask(v))
177 } else {
178 (k.clone(), v.clone())
179 }
180 })
181 .collect()
182 }
183
184 pub fn mask_json(&self, fields: &[String], json: &str) -> String {
186 let Ok(mut value) = serde_json::from_str::<serde_json::Value>(json) else {
187 return json.to_string();
188 };
189 let Some(obj) = value.as_object_mut() else {
190 return json.to_string();
191 };
192 for field in fields {
193 if let Some(serde_json::Value::String(s)) = obj.get_mut(field) {
194 *s = self.mask(s);
195 }
196 }
197 serde_json::to_string(&value).unwrap_or_else(|_| json.to_string())
198 }
199
200 pub fn mask_batch(&self, values: &[String]) -> Vec<String> {
202 values.iter().map(|v| self.mask(v)).collect()
203 }
204}
205
206#[derive(Debug, Clone)]
215pub struct PartialDisplayMasker {
216 prefix_keep: usize,
217 suffix_keep: usize,
218 mask_char: char,
219 min_mask_length: usize,
220 fallback: String,
221}
222
223impl Default for PartialDisplayMasker {
224 fn default() -> Self {
225 Self {
226 prefix_keep: 3,
227 suffix_keep: 4,
228 mask_char: '*',
229 min_mask_length: 3,
230 fallback: "***".to_string(),
231 }
232 }
233}
234
235impl PartialDisplayMasker {
236 pub fn new() -> Self {
238 Self::default()
239 }
240
241 pub fn with_prefix(mut self, n: usize) -> Self {
243 self.prefix_keep = n;
244 self
245 }
246
247 pub fn with_suffix_keep(mut self, n: usize) -> Self {
249 self.suffix_keep = n;
250 self
251 }
252
253 pub fn with_mask_char(mut self, c: char) -> Self {
255 self.mask_char = c;
256 self
257 }
258
259 pub fn with_min_mask_length(mut self, n: usize) -> Self {
261 self.min_mask_length = n;
262 self
263 }
264
265 pub fn with_fallback(mut self, fallback: &str) -> Self {
267 self.fallback = fallback.to_string();
268 self
269 }
270
271 pub fn prefix_keep(&self) -> usize {
273 self.prefix_keep
274 }
275
276 pub fn suffix_keep(&self) -> usize {
278 self.suffix_keep
279 }
280
281 pub fn mask_char(&self) -> char {
283 self.mask_char
284 }
285
286 pub fn mask(&self, value: &str) -> String {
288 let chars: Vec<char> = value.chars().collect();
289 let len = chars.len();
290 if len == 0 {
291 return self.fallback.clone();
292 }
293 let need = self.prefix_keep + self.suffix_keep;
294 if len <= need {
295 return self.fallback.clone();
296 }
297 let hidden = len - need;
298 let mask_len = hidden.max(self.min_mask_length);
299 let mut out = String::with_capacity(len + mask_len);
300 for &c in &chars[..self.prefix_keep] {
301 out.push(c);
302 }
303 for _ in 0..mask_len {
304 out.push(self.mask_char);
305 }
306 for &c in &chars[len - self.suffix_keep..] {
307 out.push(c);
308 }
309 out
310 }
311
312 pub fn mask_fields(
314 &self,
315 fields: &[String],
316 data: &HashMap<String, String>,
317 ) -> HashMap<String, String> {
318 data.iter()
319 .map(|(k, v)| {
320 if fields.contains(k) {
321 (k.clone(), self.mask(v))
322 } else {
323 (k.clone(), v.clone())
324 }
325 })
326 .collect()
327 }
328}
329
330#[derive(Debug, Clone)]
338pub struct PatternMasker {
339 patterns: Vec<(String, crate::MaskingRule)>,
340}
341
342impl Default for PatternMasker {
343 fn default() -> Self {
344 Self {
345 patterns: Vec::new(),
346 }
347 }
348}
349
350impl PatternMasker {
351 pub fn new() -> Self {
353 Self::default()
354 }
355
356 pub fn add_pattern(mut self, pattern: &str, rule: crate::MaskingRule) -> Self {
358 self.patterns.push((pattern.to_string(), rule));
359 self
360 }
361
362 pub fn pattern_count(&self) -> usize {
364 self.patterns.len()
365 }
366
367 pub fn clear(&mut self) {
369 self.patterns.clear();
370 }
371
372 pub fn match_rule(&self, field: &str) -> Option<&crate::MaskingRule> {
374 self.patterns
375 .iter()
376 .find(|(pat, _)| wildcard_match(pat, field))
377 .map(|(_, rule)| rule)
378 }
379
380 pub fn mask_map(&self, data: &HashMap<String, String>) -> HashMap<String, String> {
382 data.iter()
383 .map(|(k, v)| match self.match_rule(k) {
384 Some(rule) => (k.clone(), crate::DataMasker::apply(rule, v)),
385 None => (k.clone(), v.clone()),
386 })
387 .collect()
388 }
389
390 pub fn mask_json(&self, json: &str) -> String {
392 let Ok(mut value) = serde_json::from_str::<serde_json::Value>(json) else {
393 return json.to_string();
394 };
395 let Some(obj) = value.as_object_mut() else {
396 return json.to_string();
397 };
398 let keys: Vec<String> = obj.keys().cloned().collect();
399 for key in keys {
400 if let Some(rule) = self.match_rule(&key) {
401 if let Some(serde_json::Value::String(s)) = obj.get_mut(&key) {
402 *s = crate::DataMasker::apply(rule, s);
403 }
404 }
405 }
406 serde_json::to_string(&value).unwrap_or_else(|_| json.to_string())
407 }
408}
409
410pub fn wildcard_match(pattern: &str, text: &str) -> bool {
414 let pat: Vec<char> = pattern.chars().collect();
415 let txt: Vec<char> = text.chars().collect();
416 let m = pat.len();
417 let n = txt.len();
418
419 let mut dp = vec![vec![false; n + 1]; m + 1];
421 dp[0][0] = true;
422
423 for i in 1..=m {
425 if pat[i - 1] == '*' {
426 dp[i][0] = dp[i - 1][0];
427 }
428 }
429
430 for i in 1..=m {
431 for j in 1..=n {
432 match pat[i - 1] {
433 '*' => dp[i][j] = dp[i - 1][j] || dp[i][j - 1],
434 '?' => dp[i][j] = dp[i - 1][j - 1],
435 c => dp[i][j] = dp[i - 1][j - 1] && c == txt[j - 1],
436 }
437 }
438 }
439
440 dp[m][n]
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
450 fn hash_algorithm_default_is_siphash() {
451 assert_eq!(HashAlgorithm::default(), HashAlgorithm::SipHash);
452 }
453
454 #[test]
455 fn hash_algorithm_as_str() {
456 assert_eq!(HashAlgorithm::SipHash.as_str(), "siphash");
457 assert_eq!(HashAlgorithm::Fnv1a32.as_str(), "fnv1a32");
458 assert_eq!(HashAlgorithm::Fnv1a64.as_str(), "fnv1a64");
459 }
460
461 #[test]
462 fn hash_algorithm_parse_name_valid() {
463 assert_eq!(
464 HashAlgorithm::parse_name("siphash"),
465 Some(HashAlgorithm::SipHash)
466 );
467 assert_eq!(
468 HashAlgorithm::parse_name("FNV1A32"),
469 Some(HashAlgorithm::Fnv1a32)
470 );
471 assert_eq!(
472 HashAlgorithm::parse_name("fnv1a64"),
473 Some(HashAlgorithm::Fnv1a64)
474 );
475 }
476
477 #[test]
478 fn hash_algorithm_parse_name_invalid() {
479 assert_eq!(HashAlgorithm::parse_name("md5"), None);
480 assert_eq!(HashAlgorithm::parse_name(""), None);
481 }
482
483 #[test]
484 fn hash_algorithm_siphash_deterministic() {
485 let a = HashAlgorithm::SipHash.hash_hex("hello");
486 let b = HashAlgorithm::SipHash.hash_hex("hello");
487 assert_eq!(a, b);
488 assert_eq!(a.len(), 16);
489 }
490
491 #[test]
492 fn hash_algorithm_fnv1a32_deterministic() {
493 let a = HashAlgorithm::Fnv1a32.hash_hex("test");
494 let b = HashAlgorithm::Fnv1a32.hash_hex("test");
495 assert_eq!(a, b);
496 assert_eq!(a.len(), 8);
497 }
498
499 #[test]
500 fn hash_algorithm_fnv1a64_deterministic() {
501 let a = HashAlgorithm::Fnv1a64.hash_hex("test");
502 let b = HashAlgorithm::Fnv1a64.hash_hex("test");
503 assert_eq!(a, b);
504 assert_eq!(a.len(), 16);
505 }
506
507 #[test]
508 fn hash_algorithm_different_inputs_different_hashes() {
509 let a = HashAlgorithm::SipHash.hash_hex("alice");
510 let b = HashAlgorithm::SipHash.hash_hex("bob");
511 assert_ne!(a, b);
512 }
513
514 #[test]
515 fn hash_algorithm_empty_input() {
516 let h = HashAlgorithm::SipHash.hash_hex("");
517 assert!(!h.is_empty());
518 }
519
520 #[test]
521 fn fnv1a_32_known_values() {
522 assert_eq!(fnv1a_32(b""), 0x811c9dc5);
524 }
525
526 #[test]
527 fn fnv1a_64_known_values() {
528 assert_eq!(fnv1a_64(b""), 0xcbf29ce484222325);
530 }
531
532 #[test]
533 fn fnv1a_32_single_byte() {
534 let h = fnv1a_32(b"a");
535 let expected = (0x811c9dc5u32 ^ 97).wrapping_mul(0x01000193);
537 assert_eq!(h, expected);
538 }
539
540 #[test]
543 fn hash_masker_default() {
544 let m = HashMasker::new();
545 assert_eq!(m.algorithm(), HashAlgorithm::SipHash);
546 assert_eq!(m.keep_prefix(), 12);
547 assert_eq!(m.suffix(), "...");
548 }
549
550 #[test]
551 fn hash_masker_mask_basic() {
552 let m = HashMasker::new();
553 let result = m.mask("13812345678");
554 assert!(result.ends_with("..."));
555 assert!(result.len() > 3);
556 }
557
558 #[test]
559 fn hash_masker_mask_empty() {
560 let m = HashMasker::new();
561 assert_eq!(m.mask(""), "...");
562 }
563
564 #[test]
565 fn hash_masker_deterministic() {
566 let m = HashMasker::new();
567 let a = m.mask("same_value");
568 let b = m.mask("same_value");
569 assert_eq!(a, b);
570 }
571
572 #[test]
573 fn hash_masker_different_values_different_output() {
574 let m = HashMasker::new();
575 let a = m.mask("alice");
576 let b = m.mask("bob");
577 assert_ne!(a, b);
578 }
579
580 #[test]
581 fn hash_masker_with_algorithm_fnv1a32() {
582 let m = HashMasker::new().with_algorithm(HashAlgorithm::Fnv1a32);
583 let result = m.mask("test");
584 assert!(result.ends_with("..."));
585 assert_eq!(m.algorithm(), HashAlgorithm::Fnv1a32);
586 }
587
588 #[test]
589 fn hash_masker_with_keep_prefix() {
590 let m = HashMasker::new().with_keep_prefix(4);
591 let result = m.mask("hello");
592 assert_eq!(result.len(), 7);
594 }
595
596 #[test]
597 fn hash_masker_with_suffix() {
598 let m = HashMasker::new().with_suffix("[hashed]");
599 let result = m.mask("value");
600 assert!(result.ends_with("[hashed]"));
601 }
602
603 #[test]
604 fn hash_masker_keep_prefix_exceeds_hash_len() {
605 let m = HashMasker::new().with_keep_prefix(100);
607 let result = m.mask("test");
608 assert_eq!(result.len(), 19);
610 }
611
612 #[test]
613 fn hash_masker_mask_fields() {
614 let m = HashMasker::new();
615 let mut data = HashMap::new();
616 data.insert("phone".to_string(), "13812345678".to_string());
617 data.insert("name".to_string(), "Alice".to_string());
618 let fields = vec!["phone".to_string()];
619 let result = m.mask_fields(&fields, &data);
620 assert_ne!(result["phone"], "13812345678");
621 assert_eq!(result["name"], "Alice");
622 }
623
624 #[test]
625 fn hash_masker_mask_json() {
626 let m = HashMasker::new();
627 let json = r#"{"phone":"13812345678","name":"Alice"}"#;
628 let fields = vec!["phone".to_string()];
629 let result = m.mask_json(&fields, json);
630 assert!(result.contains("Alice"));
631 assert!(!result.contains("13812345678"));
632 }
633
634 #[test]
635 fn hash_masker_mask_json_invalid() {
636 let m = HashMasker::new();
637 let result = m.mask_json(&["phone".to_string()], "not json");
638 assert_eq!(result, "not json");
639 }
640
641 #[test]
642 fn hash_masker_mask_batch() {
643 let m = HashMasker::new();
644 let values = vec!["a".to_string(), "b".to_string(), "c".to_string()];
645 let result = m.mask_batch(&values);
646 assert_eq!(result.len(), 3);
647 assert_ne!(result[0], result[1]);
648 }
649
650 #[test]
653 fn partial_display_default() {
654 let m = PartialDisplayMasker::new();
655 assert_eq!(m.prefix_keep(), 3);
656 assert_eq!(m.suffix_keep(), 4);
657 assert_eq!(m.mask_char(), '*');
658 }
659
660 #[test]
661 fn partial_display_mask_basic() {
662 let m = PartialDisplayMasker::new();
663 assert_eq!(m.mask("13812345678"), "138****5678");
664 }
665
666 #[test]
667 fn partial_display_mask_too_short() {
668 let m = PartialDisplayMasker::new();
669 assert_eq!(m.mask("123"), "***");
670 }
671
672 #[test]
673 fn partial_display_mask_empty() {
674 let m = PartialDisplayMasker::new();
675 assert_eq!(m.mask(""), "***");
676 }
677
678 #[test]
679 fn partial_display_custom_mask_char() {
680 let m = PartialDisplayMasker::new().with_mask_char('#');
681 assert_eq!(m.mask("13812345678"), "138####5678");
682 }
683
684 #[test]
685 fn partial_display_custom_prefix_suffix() {
686 let m = PartialDisplayMasker::new()
687 .with_prefix(2)
688 .with_suffix_keep(2);
689 assert_eq!(m.mask("abcdefgh"), "ab****gh");
690 }
691
692 #[test]
693 fn partial_display_min_mask_length() {
694 let m = PartialDisplayMasker::new()
695 .with_prefix(3)
696 .with_suffix_keep(4)
697 .with_min_mask_length(6);
698 assert_eq!(m.mask("12345678"), "123******5678");
700 }
701
702 #[test]
703 fn partial_display_custom_fallback() {
704 let m = PartialDisplayMasker::new().with_fallback("[hidden]");
705 assert_eq!(m.mask(""), "[hidden]");
706 assert_eq!(m.mask("ab"), "[hidden]");
707 }
708
709 #[test]
710 fn partial_display_unicode() {
711 let m = PartialDisplayMasker::new()
712 .with_prefix(1)
713 .with_suffix_keep(1);
714 assert_eq!(m.mask("张三李四王"), "张***王");
716 }
717
718 #[test]
719 fn partial_display_mask_fields() {
720 let m = PartialDisplayMasker::new();
721 let mut data = HashMap::new();
722 data.insert("phone".to_string(), "13812345678".to_string());
723 data.insert("name".to_string(), "Alice".to_string());
724 let fields = vec!["phone".to_string()];
725 let result = m.mask_fields(&fields, &data);
726 assert_eq!(result["phone"], "138****5678");
727 assert_eq!(result["name"], "Alice");
728 }
729
730 #[test]
731 fn partial_display_exact_boundary() {
732 let m = PartialDisplayMasker::new();
734 assert_eq!(m.mask("1234567"), "***");
735 }
736
737 #[test]
738 fn partial_display_one_past_boundary() {
739 let m = PartialDisplayMasker::new();
741 assert_eq!(m.mask("12345678"), "123***5678");
742 }
743
744 #[test]
747 fn wildcard_exact_match() {
748 assert!(wildcard_match("phone", "phone"));
749 }
750
751 #[test]
752 fn wildcard_no_match() {
753 assert!(!wildcard_match("phone", "email"));
754 }
755
756 #[test]
757 fn wildcard_star_match_prefix() {
758 assert!(wildcard_match("user_*", "user_name"));
759 assert!(wildcard_match("user_*", "user_id"));
760 }
761
762 #[test]
763 fn wildcard_star_match_suffix() {
764 assert!(wildcard_match("*_id", "user_id"));
765 assert!(wildcard_match("*_id", "order_id"));
766 }
767
768 #[test]
769 fn wildcard_star_match_entire() {
770 assert!(wildcard_match("*", "anything"));
771 assert!(wildcard_match("*", ""));
772 }
773
774 #[test]
775 fn wildcard_question_match_single() {
776 assert!(wildcard_match("user_?", "user_1"));
777 assert!(!wildcard_match("user_?", "user_12"));
778 }
779
780 #[test]
781 fn wildcard_combined_star_question() {
782 assert!(wildcard_match("u*r?", "user_"));
783 assert!(wildcard_match("?*?", "abc"));
784 }
785
786 #[test]
787 fn wildcard_empty_pattern() {
788 assert!(wildcard_match("", ""));
789 assert!(!wildcard_match("", "a"));
790 }
791
792 #[test]
793 fn wildcard_star_only() {
794 assert!(wildcard_match("***", "test"));
795 assert!(wildcard_match("***", ""));
796 }
797
798 #[test]
799 fn pattern_masker_default_empty() {
800 let m = PatternMasker::new();
801 assert_eq!(m.pattern_count(), 0);
802 }
803
804 #[test]
805 fn pattern_masker_add_pattern() {
806 let m = PatternMasker::new().add_pattern("phone_*", crate::MaskingRule::Phone);
807 assert_eq!(m.pattern_count(), 1);
808 }
809
810 #[test]
811 fn pattern_masker_match_rule() {
812 let m = PatternMasker::new()
813 .add_pattern("phone_*", crate::MaskingRule::Phone)
814 .add_pattern("*_email", crate::MaskingRule::Email);
815 assert!(m.match_rule("phone_primary").is_some());
816 assert!(m.match_rule("user_email").is_some());
817 assert!(m.match_rule("address").is_none());
818 }
819
820 #[test]
821 fn pattern_masker_clear() {
822 let mut m = PatternMasker::new().add_pattern("*", crate::MaskingRule::Phone);
823 m.clear();
824 assert_eq!(m.pattern_count(), 0);
825 }
826
827 #[test]
828 fn pattern_masker_mask_map() {
829 let m = PatternMasker::new()
830 .add_pattern("phone_*", crate::MaskingRule::Phone)
831 .add_pattern("*_email", crate::MaskingRule::Email);
832 let mut data = HashMap::new();
833 data.insert("phone_primary".to_string(), "13812345678".to_string());
834 data.insert("user_email".to_string(), "test@example.com".to_string());
835 data.insert("name".to_string(), "Alice".to_string());
836 let result = m.mask_map(&data);
837 assert_eq!(result["phone_primary"], "138****5678");
838 assert_eq!(result["user_email"], "t***@example.com");
839 assert_eq!(result["name"], "Alice");
840 }
841
842 #[test]
843 fn pattern_masker_mask_json() {
844 let m = PatternMasker::new().add_pattern("phone", crate::MaskingRule::Phone);
845 let json = r#"{"phone":"13812345678","name":"Alice"}"#;
846 let result = m.mask_json(json);
847 assert!(result.contains("138****5678"));
848 assert!(result.contains("Alice"));
849 }
850
851 #[test]
852 fn pattern_masker_mask_json_invalid() {
853 let m = PatternMasker::new().add_pattern("*", crate::MaskingRule::Phone);
854 assert_eq!(m.mask_json("not json"), "not json");
855 }
856
857 #[test]
858 fn pattern_masker_no_match_passthrough() {
859 let m = PatternMasker::new().add_pattern("secret_*", crate::MaskingRule::Password);
860 let mut data = HashMap::new();
861 data.insert("public_field".to_string(), "visible".to_string());
862 let result = m.mask_map(&data);
863 assert_eq!(result["public_field"], "visible");
864 }
865}