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