kennitolur/
lib.rs

1//! # Kennitölur
2//! A kennitala (plural form: kennitölur) is a unique national identification
3//! number assigned by the Icleandic government, assigned to individuals (and
4//! organizations) in Iceland.
5//!
6//! ## Number specification
7//! Kennitalas are composed of 10 digits. The first six of these are the
8//! individual's date of birth in DDMMYY format. The seventh and eight digits
9//! are randomly chosen when the kennitala is allocated, ranging from 22 to 99.
10//! The ninth digit is the checksum digit, and the tenth indicates the century
11//! of the individual's birth.
12//!
13//! ### Checksum digit
14//! The dot product of the vector containing the first 8 digits of the kennitala
15//! is taken with the vector `[3, 2, 7, 6, 5, 4, 3, 2]`. Take the modulo 11 of
16//! that computation. If the result `r` is 0, the checksum digit is 0, otherwise it
17//! is `11 - r`.
18#![deny(
19    missing_docs,
20    future_incompatible,
21    nonstandard_style,
22    rust_2018_idioms,
23    missing_copy_implementations,
24    trivial_casts,
25    trivial_numeric_casts,
26    unsafe_code,
27    unused_qualifications
28)]
29mod dates;
30mod error;
31
32#[cfg(feature = "chrono")]
33use chrono::naive::NaiveDate;
34use std::convert::TryFrom;
35use std::fmt;
36
37use dates::days_in_month;
38pub use error::KennitalaError;
39
40const VALIDATION_DIGITS: [u8; 8] = [3, 2, 7, 6, 5, 4, 3, 2];
41
42const DAY_MASK: u32 = 0b00000000_00000000_00000000_00011111;
43const DAY_OFFSET: u32 = 0;
44const MONTH_MASK: u32 = 0b00000000_00000000_00000001_11100000;
45const MONTH_OFFSET: u32 = DAY_OFFSET + 5;
46const YEAR_MASK: u32 = 0b00000000_00000000_11111110_00000000;
47const YEAR_OFFSET: u32 = MONTH_OFFSET + 4;
48const REST_MASK: u32 = 0b00000011_11111111_00000000_00000000;
49const REST_OFFSET: u32 = YEAR_OFFSET + 7;
50const CENTURY_MASK: u32 = 0b00000100_00000000_00000000_00000000;
51const CENTURY_OFFSET: u32 = REST_OFFSET + 10;
52
53/// Struct that represents the kennitala of an Icelandic citizen or resident.
54#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
55pub struct Kennitala {
56    internal: u32,
57}
58
59impl Kennitala {
60    /// Create new kennitala object from the given string. Validation is done
61    /// beforehand.
62    pub fn new(kennitala: &str) -> Result<Self, KennitalaError> {
63        let only_numbers = kennitala
64            .chars()
65            .all(|c| ((c as u32) >= 48) && ((c as u32) <= 57));
66        if !only_numbers {
67            return Err(KennitalaError::InvalidNumber);
68        }
69
70        if kennitala.len() != 10 {
71            // A valid kennitala string consists of 10 ASCII bytes.
72            return Err(KennitalaError::InvalidLength(kennitala.len()));
73        }
74
75        let mut kennitala_array = <[u8; 10]>::try_from(kennitala.as_bytes()).unwrap();
76        for d in &mut kennitala_array {
77            // The ASCII codes for the arabic numerals share a contiguous range
78            // from 48 to 57.
79            *d -= 48;
80        }
81        Kennitala::from_slice(&kennitala_array)
82    }
83
84    // Create new kennitala object from the given u32. Validation is done
85    /// beforehand.
86    pub fn from_u32(kennitala_u32: u32) -> Result<Self, KennitalaError> {
87        let mut kennitala = [0; 10];
88        kt_to_array(kennitala_u32, &mut kennitala)?;
89        Kennitala::from_slice(&kennitala)
90    }
91
92    /// Create new kennitala object from the given slice. Validation is done
93    /// beforehand. Each element in the slice must be equal or less than 9.
94    fn from_slice(kennitala: &[u8; 10]) -> Result<Self, KennitalaError> {
95        debug_assert!(kennitala.iter().all(|d| *d <= 9));
96
97        let checksum_digit = kennitala[8];
98        let calculated_checksum_digit = calculate_checksum_digit(&kennitala);
99        if checksum_digit != calculated_checksum_digit {
100            return Err(KennitalaError::InvalidChecksum);
101        }
102
103        if ((kennitala[6] * 10) + kennitala[7]) < 20 {
104            return Err(KennitalaError::InvalidRandomDigits);
105        }
106
107        let century_digit = kennitala[9] as u32;
108        if !((century_digit == 0) || (century_digit == 9)) {
109            return Err(KennitalaError::InvalidCentury);
110        }
111        let year_offset = if century_digit == 0 { 2000 } else { 1900 };
112
113        let dob_month = (kennitala[2] * 10) as u32 + kennitala[3] as u32;
114        if (dob_month > 12) || (dob_month <= 0) {
115            return Err(KennitalaError::InvalidMonth);
116        }
117
118        let dob_year = (kennitala[4] * 10) as u32 + kennitala[5] as u32;
119
120        let dob_day = (kennitala[0] * 10) as u32 + kennitala[1] as u32;
121        if (dob_day > days_in_month(dob_month, dob_year + year_offset)) || (dob_day <= 0) {
122            return Err(KennitalaError::InvalidDay);
123        }
124
125        let rest = (kennitala[6] as u32) * 100 + (kennitala[7] * 10) as u32 + kennitala[8] as u32;
126
127        let mut value = dob_day << DAY_OFFSET;
128        value += dob_month << MONTH_OFFSET;
129        value += dob_year << YEAR_OFFSET;
130        value += rest << REST_OFFSET;
131        value += ((century_digit == 0) as u32) << CENTURY_OFFSET;
132
133        Ok(Self { internal: value })
134    }
135
136    /// Get day in the range [1, 31]
137    #[inline]
138    pub fn get_day(&self) -> u32 {
139        let day = (self.internal & DAY_MASK) >> DAY_OFFSET;
140        debug_assert!((day >= 1) && (day <= 31));
141        day
142    }
143
144    /// Get month in the range [1, 12]
145    #[inline]
146    pub fn get_month(&self) -> u32 {
147        let month = (self.internal & MONTH_MASK) >> MONTH_OFFSET;
148        debug_assert!((month >= 1) && (month <= 12));
149        month
150    }
151
152    /// Get year in the range [0, 99]
153    #[inline]
154    pub fn get_short_year(&self) -> u32 {
155        let short_year = (self.internal & YEAR_MASK) >> YEAR_OFFSET;
156        debug_assert!(short_year <= 99);
157        short_year
158    }
159
160    /// Get year in the range [1900, 2099]
161    #[inline]
162    pub fn get_year(&self) -> u32 {
163        let offset = if self.get_century_bit() == 0 {
164            1900
165        } else {
166            2000
167        };
168        self.get_short_year() + offset
169    }
170
171    /// Get the value of the bit storing which century this Kennitala's holder
172    /// was born in.
173    #[inline]
174    fn get_century_bit(&self) -> u32 {
175        let bit = (self.internal & CENTURY_MASK) >> CENTURY_OFFSET;
176        debug_assert!((bit == 0) || (bit == 1));
177        bit
178    }
179
180    /// Get century digit in the set {0, 9}
181    #[inline]
182    pub fn get_short_century(&self) -> u32 {
183        if self.get_century_bit() == 0 {
184            9
185        } else {
186            0
187        }
188    }
189
190    /// Get the two random digits plus the checksum digit, these are in the
191    /// range [20, 999]
192    #[inline]
193    pub fn get_randoms(&self) -> u32 {
194        let randoms = (self.internal & REST_MASK) >> REST_OFFSET;
195        debug_assert!((randoms >= 20) && (randoms <= 999));
196        randoms
197    }
198
199    /// Get the birthday of this kennitala's holder.
200    #[cfg(feature = "chrono")]
201    pub fn get_birthday(&self) -> NaiveDate {
202        NaiveDate::from_ymd(self.get_year() as i32, self.get_month(), self.get_day())
203    }
204}
205
206impl fmt::Display for Kennitala {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        write!(
209            f,
210            "{:02}{:02}{:02}{:03}{}",
211            self.get_day(),
212            self.get_month(),
213            self.get_short_year(),
214            self.get_randoms(),
215            self.get_short_century()
216        )
217    }
218}
219
220fn kt_to_array(kt_integer: u32, array: &mut [u8; 10]) -> Result<(), KennitalaError> {
221    let mut n = kt_integer;
222    let mut i = 0;
223    while n > 0 {
224        let digit = n % 10;
225        debug_assert!(digit <= 9);
226        array[9 - i] = digit as u8;
227        n /= 10;
228        i += 1
229    }
230    if i < 9 {
231        Err(KennitalaError::InvalidLength(i))
232    } else {
233        Ok(())
234    }
235}
236
237// This function can return the number 10, which is not a valid digit in the
238// range [0, 9]. That's okay, since the number 10 will not match the checksum
239// digit in the given kennitala, so an error will be raised.
240fn calculate_checksum_digit(kennitala: &[u8; 10]) -> u8 {
241    let mut sum: u32 = 0;
242    for i in 0..8 {
243        sum += (kennitala[i] * VALIDATION_DIGITS[i]) as u32;
244    }
245    let sum_mod_11 = sum % 11;
246    let digit = if sum_mod_11 == 0 { 0 } else { 11 - sum_mod_11 };
247    debug_assert!(digit <= 10);
248    digit as u8
249}
250
251#[cfg(test)]
252mod tests {
253
254    use super::*;
255    use std::string::ToString;
256
257    #[test]
258    fn my_own_kennitala() {
259        let my_kennitala = Kennitala::new("3110002920").unwrap();
260        assert_eq!(my_kennitala.get_day(), 31);
261        assert_eq!(my_kennitala.get_month(), 10);
262        assert_eq!(my_kennitala.get_short_year(), 0);
263        assert_eq!(my_kennitala.get_short_century(), 0);
264        assert_eq!(my_kennitala.get_randoms(), 292);
265        assert_eq!(my_kennitala.get_year(), 2000);
266        #[cfg(feature = "chrono")]
267        {
268            let my_birthday = NaiveDate::from_ymd(2000, 10, 31);
269            assert_eq!(my_kennitala.get_birthday(), my_birthday);
270        }
271        assert_eq!(my_kennitala.to_string(), "3110002920");
272    }
273
274    #[test]
275    fn my_moms_kennitala() {
276        let my_moms_kennitala = Kennitala::new("1703715939").unwrap();
277        assert_eq!(my_moms_kennitala.get_day(), 17);
278        assert_eq!(my_moms_kennitala.get_month(), 03);
279        assert_eq!(my_moms_kennitala.get_short_year(), 71);
280        assert_eq!(my_moms_kennitala.get_short_century(), 9);
281        assert_eq!(my_moms_kennitala.get_randoms(), 593);
282        assert_eq!(my_moms_kennitala.get_year(), 1971);
283        #[cfg(feature = "chrono")]
284        {
285            let my_moms_birthday = NaiveDate::from_ymd(1971, 03, 17);
286            assert_eq!(my_moms_kennitala.get_birthday(), my_moms_birthday);
287        }
288        assert_eq!(my_moms_kennitala.to_string(), "1703715939");
289    }
290
291    #[test]
292    fn made_up_kennitala() {
293        let kt = Kennitala::new("0311203149").unwrap();
294        assert_eq!(kt.get_day(), 3);
295        assert_eq!(kt.get_month(), 11);
296        assert_eq!(kt.get_short_year(), 20);
297        assert_eq!(kt.get_short_century(), 9);
298        assert_eq!(kt.get_randoms(), 314);
299        assert_eq!(kt.get_year(), 1920);
300        #[cfg(feature = "chrono")]
301        {
302            let my_moms_birthday = NaiveDate::from_ymd(1920, 11, 3);
303            assert_eq!(kt.get_birthday(), my_moms_birthday);
304        }
305        assert_eq!(kt.to_string(), "0311203149");
306    }
307
308    #[test]
309    fn max_u32() {
310        let kt = Kennitala::new(&std::u32::MAX.to_string());
311        assert!(kt.is_err());
312    }
313
314    #[test]
315    fn failed_fuzz_1() {
316        let kt = Kennitala::new("3999999999");
317        assert!(kt.is_err());
318    }
319
320    #[test]
321    fn failed_fuzz_2() {
322        let kt = Kennitala::new("9999");
323        assert!(kt.is_err());
324    }
325
326    #[test]
327    fn failed_fuzz_3() {
328        let kt = Kennitala::new("01011413300");
329        assert!(kt.is_err());
330    }
331}