codes_check_digits/
iso_7064.rs

1/*!
2Provides an implementation of the
3[ISO/IEC 7064:2003](https://www.iso.org/standard/31531.html)
4*Information technology — Security techniques — Check character
5systems* standard.
6
7Also, see Code of Federal Regulations, Title 12 - Banks and Banking,
8[Appendix C to Part 1003—Procedures for Generating a Check Digit and
9Validating a ULI](https://www.govinfo.gov/content/pkg/CFR-2016-title12-vol8/xml/CFR-2016-title12-vol8-part1003-appC.xml).
10
11# Example
12
13YYYYY
14
15*/
16
17use crate::{
18    common::{
19        ascii_alphanum_to_u8, calculate_mod, is_ascii_alpha_upper, is_ascii_alphanumeric_upper,
20        is_ascii_numeric, string_to_numeric_string,
21    },
22    Calculator,
23};
24use std::fmt::Display;
25
26// ------------------------------------------------------------------------------------------------
27// Public Types
28// ------------------------------------------------------------------------------------------------
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31#[allow(non_camel_case_types)]
32pub enum IsoVariant {
33    ///
34    /// The data digits must be '0' - '9' but the check digit may
35    /// also be 'X' (representing the value 10). Usage: unknown.
36    /// This is not the ISBN check.
37    ///
38    Mod_11_2,
39    ///
40    /// The ISO 7064 standard defines a family of check digits in
41    /// the form of "Mod N+1,N".
42    ///
43    /// It catches all single digit errors but does not catch
44    /// transposition errors "01" → "10" (but not vice-versa) and
45    /// "34" → "43" (but not vice-versa).
46    ///
47    Mod_11_10,
48    ///
49    /// ISO 7064 Mod_27_26 -- same as Mod_37_36 but restricted to
50    /// 'A'-'Z'
51    ///
52    Mod_27_26,
53    Mod_37_2,
54    ///
55    /// This scheme works similar to Mod_11_10, but is defined to
56    /// use alphanumeric characters '0' -'9', 'A' - 'Z' for the
57    /// data and check digit
58    ///
59    Mod_37_36,
60    ///
61    /// While this scheme is described in the standard as taking only
62    /// a numeric alphabet it is used in a number of other standards
63    /// with alpha-numeric input.
64    ///
65    /// 1. IF alpha-numeric:
66    ///    1. replace any alphabetic character with it's numeric
67    ///       equivalent where 'A' is 10, 'B' is 11, etc. The
68    ///       input string '9A8C' therefore becomes '910812'.
69    /// 2. Convert this string into an integer value. An arbitrary
70    ///    precision integer is likely needed for longer input strings.
71    /// 3. The check value is calculated as `98 - (n % 97)`.
72    /// 4. If the value is less than 10 add a '0' pad character to the
73    ///    left to produce the two character value.
74    ///
75    Mod_97_10,
76    ///
77    /// ISO 7064 Mod_661_26 -- restricted to 'A'-'Z' and produces 2
78    /// check digits
79    ///
80    Mod_661_26,
81    ///
82    /// ISO 7064 Mod_1271_36 -- alphanumeric and produces 2 check
83    /// digits
84    ///
85    Mod_1271_36,
86}
87
88#[derive(Clone, Copy, Debug)]
89pub struct CheckDigitAlgorithm {
90    variant: IsoVariant,
91}
92
93// ------------------------------------------------------------------------------------------------
94// Public Functions
95// ------------------------------------------------------------------------------------------------
96
97pub const fn get_algorithm_instance(variant: IsoVariant) -> CheckDigitAlgorithm {
98    CheckDigitAlgorithm::new(variant)
99}
100
101// ------------------------------------------------------------------------------------------------
102// Private Types
103// ------------------------------------------------------------------------------------------------
104
105// ------------------------------------------------------------------------------------------------
106// Implementations
107// ------------------------------------------------------------------------------------------------
108
109impl Display for IsoVariant {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        write!(f, "{}", self.name())
112    }
113}
114
115impl IsoVariant {
116    const fn check_digits(&self) -> usize {
117        match self {
118            Self::Mod_11_2 => 1,
119            Self::Mod_11_10 => 1,
120            Self::Mod_27_26 => 1,
121            Self::Mod_37_2 => 1,
122            Self::Mod_37_36 => 1,
123            Self::Mod_97_10 => 2,
124            Self::Mod_661_26 => 2,
125            Self::Mod_1271_36 => 2,
126        }
127    }
128
129    const fn name(&self) -> &'static str {
130        match self {
131            Self::Mod_11_2 => "ISO 7064 - MOD 11-2",
132            Self::Mod_11_10 => "ISO 7064 - MOD 11,10",
133            Self::Mod_27_26 => "ISO 7064 - MOD 27,26",
134            Self::Mod_37_2 => "ISO 7064 - MOD 37-2",
135            Self::Mod_37_36 => "ISO 7064 - MOD 37,36",
136            Self::Mod_97_10 => "ISO 7064 - MOD 97-10",
137            Self::Mod_661_26 => "ISO 7064 - MOD 661-26",
138            Self::Mod_1271_36 => "ISO 7064 - MOD 1271-36",
139        }
140    }
141}
142
143// ------------------------------------------------------------------------------------------------
144
145impl CheckDigitAlgorithm {
146    pub const fn new(variant: IsoVariant) -> Self {
147        Self { variant }
148    }
149}
150
151impl Calculator<u8> for CheckDigitAlgorithm {
152    fn name(&self) -> &'static str {
153        self.variant.name()
154    }
155
156    fn number_of_check_digit_chars(&self) -> usize {
157        self.variant.check_digits()
158    }
159
160    fn calculate(&self, s: &str) -> Result<u8, crate::error::CheckDigitError> {
161        match self.variant {
162            IsoVariant::Mod_11_2 => is_ascii_numeric(s)?,
163            IsoVariant::Mod_27_26 => is_ascii_alpha_upper(s)?,
164            IsoVariant::Mod_97_10 => {
165                is_ascii_alphanumeric_upper(s)?;
166                let s = format!("{}00", string_to_numeric_string(s, ascii_alphanum_to_u8));
167                return Ok(98 - calculate_mod(&s, 97) as u8);
168            }
169            IsoVariant::Mod_661_26 => is_ascii_alpha_upper(s)?,
170            _ => is_ascii_alphanumeric_upper(s)?,
171        }
172        todo!()
173    }
174}
175
176// ------------------------------------------------------------------------------------------------
177// Private Functions
178// ------------------------------------------------------------------------------------------------
179
180// ------------------------------------------------------------------------------------------------
181// Modules
182// ------------------------------------------------------------------------------------------------
183
184// ------------------------------------------------------------------------------------------------
185// Unit Tests
186// ------------------------------------------------------------------------------------------------
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_string_to_string_a36() {
194        assert_eq!(
195            &string_to_numeric_string("10BX939C5543TQA1144M999143X", ascii_alphanum_to_u8),
196            "10113393912554329261011442299914333"
197        );
198    }
199}