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}