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