1use crate::error::{BatchResult, EinError, SsnError};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum TinKind {
16 Ssn,
17 Ein,
18 Itin,
19 Atin,
20 Unknown,
21}
22
23static EIN_CAMPUS_VALID: [bool; 100] = {
26 let mut table = [false; 100];
27 let valid = [
29 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 20, 21, 22, 23, 24, 25, 26, 27, 30, 32, 33,
30 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55, 56, 57,
31 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84,
32 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 98, 99,
33 ];
34 let mut i = 0;
35 while i < valid.len() {
36 table[valid[i] as usize] = true;
37 i += 1;
38 }
39 table
40};
41
42#[derive(Debug, Clone, Copy)]
44pub struct TinValidator {
45 pub allow_formatted: bool,
47 pub strict_ssn: bool,
49}
50
51impl Default for TinValidator {
52 fn default() -> Self {
53 Self {
54 allow_formatted: true,
55 strict_ssn: true,
56 }
57 }
58}
59
60impl TinValidator {
61 pub fn new() -> Self {
63 Self::default()
64 }
65
66 pub fn strict() -> Self {
68 Self {
69 allow_formatted: false,
70 strict_ssn: true,
71 }
72 }
73}
74
75#[inline]
78fn extract_digits(s: &str) -> Option<[u8; 9]> {
79 let bytes = s.as_bytes();
80 let mut digits = [0u8; 9];
81 let mut count = 0;
82
83 if bytes.len() == 9 {
85 for (i, &b) in bytes.iter().enumerate() {
86 if b.wrapping_sub(b'0') > 9 {
87 break;
89 }
90 digits[i] = b - b'0';
91 count += 1;
92 }
93 if count == 9 {
94 return Some(digits);
95 }
96 count = 0;
98 }
99
100 for &b in bytes {
102 let d = b.wrapping_sub(b'0');
103 if d <= 9 {
104 if count >= 9 {
105 return None; }
107 digits[count] = d;
108 count += 1;
109 } else if b != b'-' && b != b' ' {
110 return None; }
112 }
113
114 if count == 9 {
115 Some(digits)
116 } else {
117 None
118 }
119}
120
121#[inline]
124fn digits_to_packed(digits: &[u8; 9]) -> u32 {
125 let area = (digits[0] as u32) * 100 + (digits[1] as u32) * 10 + (digits[2] as u32);
126 let group = (digits[3] as u32) * 10 + (digits[4] as u32);
127 let serial = (digits[5] as u32) * 1000
128 + (digits[6] as u32) * 100
129 + (digits[7] as u32) * 10
130 + (digits[8] as u32);
131
132 (area << 21) | (group << 14) | serial
133}
134
135#[inline]
137fn packed_area(packed: u32) -> u32 {
138 packed >> 21
139}
140
141#[inline]
143fn packed_group(packed: u32) -> u32 {
144 (packed >> 14) & 0x7F
145}
146
147#[inline]
149fn packed_serial(packed: u32) -> u32 {
150 packed & 0x3FFF
151}
152
153#[inline]
155pub fn validate_any(tin: &str) -> bool {
156 let Some(digits) = extract_digits(tin) else {
157 return false;
158 };
159
160 if digits.iter().all(|&d| d == 0) {
162 return false;
163 }
164
165 validate_ssn_digits(&digits).is_ok()
167 || validate_ein_digits(&digits).is_ok()
168 || validate_itin_digits(&digits)
169 || validate_atin_digits(&digits)
170}
171
172#[inline]
174pub fn validate_ssn(ssn: &str) -> bool {
175 extract_digits(ssn)
176 .map(|d| validate_ssn_digits(&d).is_ok())
177 .unwrap_or(false)
178}
179
180#[inline]
182pub fn validate_ssn_digits(digits: &[u8; 9]) -> Result<(), SsnError> {
183 let packed = digits_to_packed(digits);
184 let area = packed_area(packed);
185 let group = packed_group(packed);
186 let serial = packed_serial(packed);
187
188 if area == 0 {
190 return Err(SsnError::AreaZero);
191 }
192 if area == 666 {
193 return Err(SsnError::Area666);
194 }
195 if area >= 900 {
196 return Err(SsnError::AreaReserved);
197 }
198
199 if group == 0 {
201 return Err(SsnError::GroupZero);
202 }
203 if serial == 0 {
204 return Err(SsnError::SerialZero);
205 }
206
207 let first = digits[0];
209 if digits.iter().all(|&d| d == first) {
210 return Err(SsnError::AllSameDigits);
211 }
212
213 Ok(())
214}
215
216#[inline]
218pub fn validate_ein(ein: &str) -> bool {
219 extract_digits(ein)
220 .map(|d| validate_ein_digits(&d).is_ok())
221 .unwrap_or(false)
222}
223
224#[inline]
226pub fn validate_ein_digits(digits: &[u8; 9]) -> Result<(), EinError> {
227 if digits.iter().all(|&d| d == 0) {
229 return Err(EinError::AllZeros);
230 }
231
232 let campus = (digits[0] as usize) * 10 + (digits[1] as usize);
234
235 if !EIN_CAMPUS_VALID[campus] {
236 return Err(EinError::InvalidCampus);
237 }
238
239 Ok(())
240}
241
242#[inline]
244pub fn validate_itin(itin: &str) -> bool {
245 extract_digits(itin)
246 .map(|d| validate_itin_digits(&d))
247 .unwrap_or(false)
248}
249
250#[inline]
253pub fn validate_itin_digits(digits: &[u8; 9]) -> bool {
254 digits[0] == 9 && (digits[3] == 7 || digits[3] == 8)
255}
256
257#[inline]
259pub fn validate_atin(atin: &str) -> bool {
260 extract_digits(atin)
261 .map(|d| validate_atin_digits(&d))
262 .unwrap_or(false)
263}
264
265#[inline]
268pub fn validate_atin_digits(digits: &[u8; 9]) -> bool {
269 digits[0] == 9 && digits[3] == 9 && digits[4] == 3
270}
271
272#[inline]
274pub fn detect_tin_kind(digits: &[u8; 9]) -> TinKind {
275 if digits[0] == 9 {
277 if digits[3] == 9 && digits[4] == 3 {
278 return TinKind::Atin;
279 }
280 if digits[3] == 7 || digits[3] == 8 {
281 return TinKind::Itin;
282 }
283 }
284
285 if validate_ssn_digits(digits).is_ok() {
287 return TinKind::Ssn;
288 }
289
290 if validate_ein_digits(digits).is_ok() {
292 return TinKind::Ein;
293 }
294
295 TinKind::Unknown
296}
297
298pub fn validate_batch(tins: &[&str]) -> BatchResult {
307 let mut result = BatchResult::with_capacity(tins.len());
308
309 const CHUNK_SIZE: usize = 64;
311
312 for (chunk_idx, chunk) in tins.chunks(CHUNK_SIZE).enumerate() {
313 let base_idx = chunk_idx * CHUNK_SIZE;
314
315 let mut digits_buf: [[u8; 9]; CHUNK_SIZE] = [[0; 9]; CHUNK_SIZE];
317 let mut valid_format: [bool; CHUNK_SIZE] = [false; CHUNK_SIZE];
318
319 for (i, tin) in chunk.iter().enumerate() {
320 if let Some(d) = extract_digits(tin) {
321 digits_buf[i] = d;
322 valid_format[i] = true;
323 }
324 }
325
326 for (i, digits) in digits_buf.iter().enumerate().take(chunk.len()) {
330 if valid_format[i] {
331 let is_valid = !digits.iter().all(|&d| d == 0)
332 && (validate_ssn_digits(digits).is_ok()
333 || validate_ein_digits(digits).is_ok()
334 || validate_itin_digits(digits)
335 || validate_atin_digits(digits));
336 result.set(base_idx + i, is_valid);
337 }
338 }
339 }
340
341 result
342}
343
344pub fn validate_ssn_batch(ssns: &[&str]) -> BatchResult {
346 let mut result = BatchResult::with_capacity(ssns.len());
347
348 for (i, ssn) in ssns.iter().enumerate() {
349 if let Some(digits) = extract_digits(ssn) {
350 result.set(i, validate_ssn_digits(&digits).is_ok());
351 }
352 }
353
354 result
355}
356
357pub fn validate_ein_batch(eins: &[&str]) -> BatchResult {
359 let mut result = BatchResult::with_capacity(eins.len());
360
361 for (i, ein) in eins.iter().enumerate() {
362 if let Some(digits) = extract_digits(ein) {
363 result.set(i, validate_ein_digits(&digits).is_ok());
364 }
365 }
366
367 result
368}
369
370#[cfg(all(target_arch = "x86_64", feature = "simd"))]
375mod simd_x86 {
376 use super::*;
377
378 #[inline]
380 pub fn has_avx2() -> bool {
381 is_x86_feature_detected!("avx2")
382 }
383
384 #[cfg(target_feature = "avx2")]
389 #[inline]
390 pub unsafe fn validate_ssn_avx2(packed: &[u32; 8]) -> u8 {
391 use std::arch::x86_64::*;
392
393 let tins = _mm256_loadu_si256(packed.as_ptr() as *const __m256i);
394
395 let areas = _mm256_srli_epi32(tins, 21);
397
398 let zero = _mm256_setzero_si256();
400 let area_not_zero = _mm256_cmpgt_epi32(areas, zero);
401
402 let v666 = _mm256_set1_epi32(666);
404 let area_not_666 = _mm256_xor_si256(_mm256_cmpeq_epi32(areas, v666), _mm256_set1_epi32(-1));
405
406 let v900 = _mm256_set1_epi32(900);
408 let area_lt_900 = _mm256_cmpgt_epi32(v900, areas);
409
410 let groups = _mm256_and_si256(_mm256_srli_epi32(tins, 14), _mm256_set1_epi32(0x7F));
412 let group_not_zero = _mm256_cmpgt_epi32(groups, zero);
413
414 let serials = _mm256_and_si256(tins, _mm256_set1_epi32(0x3FFF));
416 let serial_not_zero = _mm256_cmpgt_epi32(serials, zero);
417
418 let valid = _mm256_and_si256(
420 _mm256_and_si256(_mm256_and_si256(area_not_zero, area_not_666), area_lt_900),
421 _mm256_and_si256(group_not_zero, serial_not_zero),
422 );
423
424 _mm256_movemask_ps(_mm256_castsi256_ps(valid)) as u8
426 }
427}
428
429#[cfg(all(target_arch = "aarch64", feature = "simd"))]
430#[allow(dead_code, unused_imports)]
431mod simd_arm {
432 use super::*;
433
434 #[cfg(target_feature = "neon")]
436 #[inline]
437 pub unsafe fn validate_ssn_neon(packed: &[u32; 4]) -> u8 {
438 use std::arch::aarch64::*;
439
440 let tins = vld1q_u32(packed.as_ptr());
441
442 let areas = vshrq_n_u32(tins, 21);
444
445 let zero = vdupq_n_u32(0);
447 let area_not_zero = vcgtq_u32(areas, zero);
448
449 let v666 = vdupq_n_u32(666);
451 let area_not_666 = vmvnq_u32(vceqq_u32(areas, v666));
452
453 let v900 = vdupq_n_u32(900);
455 let area_lt_900 = vcltq_u32(areas, v900);
456
457 let groups = vandq_u32(vshrq_n_u32(tins, 14), vdupq_n_u32(0x7F));
459 let group_not_zero = vcgtq_u32(groups, zero);
460
461 let serials = vandq_u32(tins, vdupq_n_u32(0x3FFF));
463 let serial_not_zero = vcgtq_u32(serials, zero);
464
465 let valid = vandq_u32(
467 vandq_u32(vandq_u32(area_not_zero, area_not_666), area_lt_900),
468 vandq_u32(group_not_zero, serial_not_zero),
469 );
470
471 let narrowed = vmovn_u32(valid);
473 let bytes = vreinterpret_u8_u16(narrowed);
474
475 let mut mask = 0u8;
477 let arr: [u8; 8] = std::mem::transmute(bytes);
478 if arr[0] != 0 {
479 mask |= 1;
480 }
481 if arr[2] != 0 {
482 mask |= 2;
483 }
484 if arr[4] != 0 {
485 mask |= 4;
486 }
487 if arr[6] != 0 {
488 mask |= 8;
489 }
490 mask
491 }
492}
493
494#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn test_extract_digits() {
504 assert_eq!(
505 extract_digits("123456789"),
506 Some([1, 2, 3, 4, 5, 6, 7, 8, 9])
507 );
508 assert_eq!(
509 extract_digits("123-45-6789"),
510 Some([1, 2, 3, 4, 5, 6, 7, 8, 9])
511 );
512 assert_eq!(
513 extract_digits("12-3456789"),
514 Some([1, 2, 3, 4, 5, 6, 7, 8, 9])
515 );
516 assert_eq!(extract_digits("12345678"), None); assert_eq!(extract_digits("1234567890"), None); assert_eq!(extract_digits("12345678a"), None); }
520
521 #[test]
522 fn test_ssn_validation() {
523 assert!(validate_ssn("123-45-6789"));
525 assert!(validate_ssn("123456789"));
526 assert!(validate_ssn("078-05-1120")); assert!(!validate_ssn("000-12-3456")); assert!(!validate_ssn("666-12-3456")); assert!(!validate_ssn("900-12-3456")); assert!(!validate_ssn("123-00-4567")); assert!(!validate_ssn("123-45-0000")); assert!(!validate_ssn("111-11-1111")); assert!(!validate_ssn("000-00-0000")); }
537
538 #[test]
539 fn test_ein_validation() {
540 assert!(validate_ein("12-3456789"));
542 assert!(validate_ein("123456789"));
543
544 assert!(!validate_ein("00-0000000")); assert!(!validate_ein("07-1234567")); assert!(!validate_ein("08-1234567")); assert!(!validate_ein("09-1234567")); }
550
551 #[test]
552 fn test_itin_validation() {
553 assert!(validate_itin("912-78-1234"));
555 assert!(validate_itin("900-70-1234"));
556
557 assert!(!validate_itin("123-45-6789")); assert!(!validate_itin("900-60-1234")); }
561
562 #[test]
563 fn test_atin_validation() {
564 assert!(validate_atin("900-93-1234"));
566
567 assert!(!validate_atin("123-93-1234")); assert!(!validate_atin("900-94-1234")); }
571
572 #[test]
573 fn test_detect_tin_kind() {
574 assert_eq!(detect_tin_kind(&[1, 2, 3, 4, 5, 6, 7, 8, 9]), TinKind::Ssn);
575 assert_eq!(detect_tin_kind(&[1, 2, 3, 4, 5, 6, 7, 8, 9]), TinKind::Ssn);
576 assert_eq!(detect_tin_kind(&[9, 0, 0, 7, 0, 1, 2, 3, 4]), TinKind::Itin);
577 assert_eq!(detect_tin_kind(&[9, 0, 0, 9, 3, 1, 2, 3, 4]), TinKind::Atin);
578 }
579
580 #[test]
581 fn test_batch_validation() {
582 let tins = vec![
583 "123-45-6789", "000-00-0000", "12-3456789", "invalid", "912-78-1234", ];
589
590 let result = validate_batch(&tins);
591
592 assert!(result.is_valid(0));
593 assert!(!result.is_valid(1));
594 assert!(result.is_valid(2));
595 assert!(!result.is_valid(3));
596 assert!(result.is_valid(4));
597
598 assert_eq!(result.valid_count(), 3);
599 assert_eq!(result.invalid_count(), 2);
600 }
601
602 #[test]
603 fn test_packed_representation() {
604 let digits = [1, 2, 3, 4, 5, 6, 7, 8, 9];
605 let packed = digits_to_packed(&digits);
606
607 assert_eq!(packed_area(packed), 123);
608 assert_eq!(packed_group(packed), 45);
609 assert_eq!(packed_serial(packed), 6789);
610 }
611}