commit 44d4a83cfc1f306cbdb6d15d3e61913fd65885da
parent d59b23ae7725271a93afe3aba3bb0bff24512002
Author: Antoine A <>
Date: Tue, 18 Feb 2025 11:26:46 +0100
common: IBAN country & pattern check
Diffstat:
7 files changed, 1880 insertions(+), 79 deletions(-)
diff --git a/common/taler-api/tests/common/db.rs b/common/taler-api/tests/common/db.rs
@@ -46,7 +46,7 @@ pub async fn notification_listener(
pub enum TransferResult {
Success(TransferResponse),
RequestUidReuse,
- WtidReuse
+ WtidReuse,
}
pub async fn transfer(db: &PgPool, transfer: TransferRequest) -> sqlx::Result<TransferResult> {
diff --git a/common/taler-common/Cargo.toml b/common/taler-common/Cargo.toml
@@ -33,3 +33,7 @@ criterion.workspace = true
[[bench]]
name = "base32"
harness = false
+
+[[bench]]
+name = "iban"
+harness = false
+\ No newline at end of file
diff --git a/common/taler-common/benches/iban.rs b/common/taler-common/benches/iban.rs
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+use std::str::FromStr;
+
+use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
+use taler_common::types::iban::{
+ Country::{self, *},
+ IBAN,
+};
+
+const COUNTRIES: [Country; 89] = [
+ AD, AE, AL, AT, AZ, BA, BE, BG, BH, BI, BR, BY, CH, CR, CY, CZ, DE, DJ, DK, DO, EE, EG, ES, FI,
+ FK, FO, FR, GB, GE, GI, GL, GR, GT, HN, HR, HU, IE, IL, IQ, IS, IT, JO, KW, KZ, LB, LC, LI, LT,
+ LU, LV, LY, MC, MD, ME, MK, MN, MR, MT, MU, NI, NL, NO, OM, PK, PL, PS, PT, QA, RO, RS, RU, SA,
+ SC, SD, SE, SI, SK, SM, SO, ST, SV, TL, TN, TR, UA, VA, VG, XK, YE,
+];
+
+fn parser(c: &mut Criterion) {
+ c.bench_function("iban_random_all", |b| {
+ b.iter_batched(
+ || {
+ (0..fastrand::usize(0..40))
+ .map(|_| fastrand::char(..))
+ .collect::<String>()
+ },
+ |case| IBAN::from_str(&case),
+ BatchSize::SmallInput,
+ )
+ });
+ c.bench_function("iban_random_alphanumeric", |b| {
+ b.iter_batched(
+ || {
+ (0..fastrand::usize(0..40))
+ .map(|_| fastrand::alphanumeric())
+ .collect::<String>()
+ },
+ |case| IBAN::from_str(&case),
+ BatchSize::SmallInput,
+ )
+ });
+
+ c.bench_function("iban_valid", |b| {
+ b.iter_batched(
+ || IBAN::random(fastrand::choice(COUNTRIES).unwrap()).to_string(),
+ |case| IBAN::from_str(&case).unwrap(),
+ BatchSize::SmallInput,
+ )
+ });
+}
+
+criterion_group!(benches, parser);
+criterion_main!(benches);
diff --git a/common/taler-common/src/error_code.rs b/common/taler-common/src/error_code.rs
@@ -2002,4 +2002,4 @@ impl ErrorCode {
END => (0, "End of error code range."),
}
}
-}
-\ No newline at end of file
+}
diff --git a/common/taler-common/src/types/iban.rs b/common/taler-common/src/types/iban.rs
@@ -20,8 +20,13 @@ use std::{
str::FromStr,
};
+pub use registry::Country;
+use registry::{rng_pattern, IbanC, PatternErr};
+
use super::utils::InlineStr;
+mod registry;
+
const MAX_IBAN_SIZE: usize = 34;
const MAX_BIC_SIZE: usize = 11;
@@ -39,73 +44,80 @@ pub fn bic(bic: impl AsRef<str>) -> BIC {
Debug, Clone, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
)]
/// International Bank Account Number (IBAN)
-pub struct IBAN(InlineStr<MAX_IBAN_SIZE>);
+pub struct IBAN {
+ country: Country,
+ encoded: InlineStr<MAX_IBAN_SIZE>,
+}
impl IBAN {
/// Compute IBAN checksum
fn iban_checksum(s: &[u8]) -> u8 {
- (s.iter()
- .cycle()
- .skip(4)
- .take(s.len())
- .fold(0u32, |mut checksum, b| {
- if b.is_ascii_digit() {
- checksum = checksum * 10 + (b - b'0') as u32;
- } else {
- checksum = checksum * 100 + (b - b'A' + 10) as u32;
- }
- if checksum > 9_999_999 {
- checksum %= 97
- }
- checksum
- })
- % 97) as u8
+ (s.iter().cycle().skip(4).take(s.len()).fold(0u32, |sum, b| {
+ if b.is_ascii_digit() {
+ (sum * 10 + (b - b'0') as u32) % 97
+ } else {
+ (sum * 100 + (b - b'A' + 10) as u32) % 97
+ }
+ })) as u8
}
- pub fn from_parts(country: &str, bban: &str) -> Self {
- assert_eq!(country.len(), 2);
+ fn from_raw_parts(country: Country, bban: &[u8]) -> Self {
// Create a iban with an empty digit check
- let mut iban = InlineStr::try_from_iter(
+ let mut encoded = InlineStr::try_from_iter(
country
.as_bytes()
- .iter()
- .copied()
- .chain("00".as_bytes().iter().copied())
- .chain(bban.as_bytes().iter().copied()),
+ .into_iter()
+ .chain([b'0', b'0'])
+ .chain(bban.iter().copied()),
)
.unwrap();
// Compute check digit
- let check_digit = 98 - Self::iban_checksum(iban.deref());
+ let checksum = 98 - Self::iban_checksum(encoded.deref());
// And insert it
unsafe {
// SAFETY: we only insert ASCII digits
- let buf = iban.deref_mut();
- buf[3] = check_digit % 10 + b'0';
- buf[2] = check_digit / 10 + b'0';
+ let buf = encoded.deref_mut();
+ buf[3] = checksum % 10 + b'0';
+ buf[2] = checksum / 10 + b'0';
}
- Self(iban)
+
+ Self { country, encoded }
}
- pub fn country_code(&self) -> &str {
- // SAFETY len >= 4
- unsafe { self.as_ref().get_unchecked(0..2) }
+ pub fn from_parts(country: Country, bban: &str) -> Self {
+ assert_eq!(bban.len(), country.bban_len()); // TODO return Result ?
+ country.bban_check(bban.as_bytes()).unwrap(); // TODO return Result
+ Self::from_raw_parts(country, bban.as_bytes())
+ }
+
+ pub fn random(country: Country) -> Self {
+ let mut bban = [0u8; MAX_IBAN_SIZE - 4];
+ rng_pattern(&mut bban, country.bban_pattern());
+ Self::from_raw_parts(country, &bban[..country.bban_len()])
}
- pub fn check_digit(&self) -> &str {
- // SAFETY len >= 4
- unsafe { self.as_ref().get_unchecked(2..4) }
+ pub fn country(&self) -> Country {
+ self.country
}
pub fn bban(&self) -> &str {
// SAFETY len >= 5
unsafe { self.as_ref().get_unchecked(4..) }
}
+
+ pub fn bank_id(&self) -> &str {
+ &self.bban()[self.country.bank_id()]
+ }
+
+ pub fn branch_id(&self) -> &str {
+ &self.bban()[self.country.branch_id()]
+ }
}
impl AsRef<str> for IBAN {
fn as_ref(&self) -> &str {
- self.0.as_ref()
+ self.encoded.as_ref()
}
}
@@ -113,12 +125,16 @@ impl AsRef<str> for IBAN {
pub enum IbanErrorKind {
#[error("contains illegal characters (only 0-9A-Z allowed)")]
Invalid,
- #[error("contains invalid country code")]
- CountryCode,
- #[error("contains invalid check digit")]
- CheckDigit,
+ #[error("contains invalid characters")]
+ Malformed,
+ #[error("unknown country {0}")]
+ UnknownCountry(String),
#[error("too long expected max {MAX_IBAN_SIZE} chars got {0}")]
- Big(usize),
+ Overflow(usize),
+ #[error("too short expected min 4 chars got {0}")]
+ Underflow(usize),
+ #[error("wrong size expected {0} chars got {1}")]
+ Size(u8, usize),
#[error("checksum expected 1 got {0}")]
Checksum(u8),
}
@@ -140,25 +156,36 @@ impl FromStr for IBAN {
.all(|b| b.is_ascii_whitespace() || b.is_ascii_alphanumeric())
{
Err(IbanErrorKind::Invalid)
- } else if let Some(inlined) = InlineStr::try_from_iter(
+ } else if let Some(encoded) = InlineStr::try_from_iter(
bytes
.iter()
.filter_map(|b| (!b.is_ascii_whitespace()).then_some(b.to_ascii_uppercase())),
) {
- if inlined.len() < 2 || !inlined[0..2].iter().all(u8::is_ascii_uppercase) {
- Err(IbanErrorKind::CountryCode)
- } else if inlined.len() < 4 || !inlined[2..4].iter().all(u8::is_ascii_digit) {
- Err(IbanErrorKind::CheckDigit)
- } else {
- let checksum = Self::iban_checksum(&inlined);
- if checksum != 1 {
- Err(IbanErrorKind::Checksum(checksum))
+ if encoded.len() < 4 {
+ Err(IbanErrorKind::Underflow(encoded.len()))
+ } else if !IbanC::A.check(&encoded[0..2]) || !IbanC::N.check(&encoded[2..4]) {
+ Err(IbanErrorKind::Malformed)
+ } else if let Some(country) = Country::from_str(&encoded.as_ref()[..2]) {
+ if let Err(e) = country.bban_check(&encoded[4..]) {
+ Err(match e {
+ PatternErr::Len(expected, got) => IbanErrorKind::Size(expected, got),
+ PatternErr::Malformed => IbanErrorKind::Malformed,
+ })
} else {
- Ok(Self(inlined))
+ let checksum = Self::iban_checksum(&encoded);
+ if checksum != 1 {
+ Err(IbanErrorKind::Checksum(checksum))
+ } else {
+ Ok(Self { country, encoded })
+ }
}
+ } else {
+ Err(IbanErrorKind::UnknownCountry(
+ encoded.as_ref()[..2].to_owned(),
+ ))
}
} else {
- Err(IbanErrorKind::Big(bytes.len()))
+ Err(IbanErrorKind::Overflow(bytes.len()))
}
.map_err(|kind| ParseIbanError {
iban: s.to_owned(),
@@ -262,32 +289,32 @@ impl Display for BIC {
#[test]
fn parse_iban() {
- for valid in [
- "DE44500105175407324931", // Germany
- "GB82WEST12345698765432", // United Kingdom
- "FR1420041010050500013M02606", // France
- "ES9121000418450200051332", // Spain
- "NL91ABNA0417164300", // Netherlands
- "IT60X0542811101000000123456", // Italy
- "BE68539007547034", // Belgium
- "SE4550000000058398257466", // Sweden
- "PL61109010140000071219812874", // Poland
- "NO9386011117947", // Norway
- ] {
+ use registry::VALID_IBAN;
+ for (valid, bban) in VALID_IBAN {
// Parsing
- let iban = IBAN::from_str(&valid).unwrap();
+ let iban = IBAN::from_str(valid).unwrap();
assert_eq!(iban.to_string(), valid);
// Roundtrip
- let from_parts = IBAN::from_parts(iban.country_code(), iban.bban());
+ let from_parts = IBAN::from_parts(iban.country(), iban.bban());
assert_eq!(from_parts.to_string(), valid);
+
+ // BBAN
+ if let Some(bban) = bban {
+ assert_eq!(bban, iban.bban());
+ }
+
+ // Random
+ let rand = IBAN::random(iban.country());
+ let parsed = IBAN::from_str(&rand.to_string()).unwrap();
+ assert_eq!(rand, parsed);
}
for (invalid, err) in [
("FR1420041@10050500013M02606", IbanErrorKind::Invalid),
- ("", IbanErrorKind::CountryCode),
- ("12345678901234567890123456", IbanErrorKind::CountryCode),
- ("FR", IbanErrorKind::CheckDigit),
- ("FRANCE123456", IbanErrorKind::CheckDigit),
+ ("", IbanErrorKind::Underflow(0)),
+ ("12345678901234567890123456", IbanErrorKind::Malformed),
+ ("FR", IbanErrorKind::Underflow(2)),
+ ("FRANCE123456", IbanErrorKind::Malformed),
("DE44500105175407324932", IbanErrorKind::Checksum(28)),
] {
let iban = IBAN::from_str(&invalid).unwrap_err();
diff --git a/common/taler-common/src/types/iban/registry.rs b/common/taler-common/src/types/iban/registry.rs
@@ -0,0 +1,1704 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+use std::fmt::Display;
+
+use Country::*;
+use IbanC::*;
+
+/// IBAN ASCII characters rules
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum IbanC {
+ /// Digits (0-9)
+ N,
+ /// Uppercase (A-Z)
+ A,
+ /// Digits or uppercase (0-9 & A-Z)
+ C,
+}
+
+impl IbanC {
+ /// Check if a valid IBAN slice follow a specific characters rules
+ pub fn check(self, iban_ascii: &[u8]) -> bool {
+ // IBAN are made of ASCII digits and uppercase
+ debug_assert!(iban_ascii
+ .iter()
+ .all(|b| b.is_ascii_uppercase() || b.is_ascii_digit()));
+ // As all characters are ASCII digits or uppercase
+ // we can use simple masks to check the character kind
+ const MASK_IS_UPPERCASE: u8 = 0b0100_0000;
+ const MASK_IS_DIGIT: u8 = 0b0010_0000;
+
+ let mask = match self {
+ Self::N => MASK_IS_UPPERCASE,
+ Self::A => MASK_IS_DIGIT,
+ Self::C => return true,
+ };
+ iban_ascii.iter().all(|b| (*b & mask) == 0)
+ }
+}
+
+/// An IBAN pattern, an array of characters rules over a number of character
+pub type Pattern = &'static [(u8, IbanC)];
+
+#[derive(Debug)]
+pub enum PatternErr {
+ Len(u8, usize),
+ Malformed,
+}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Country {
+ AD,
+ AE,
+ AL,
+ AT,
+ AZ,
+ BA,
+ BE,
+ BG,
+ BH,
+ BI,
+ BR,
+ BY,
+ CH,
+ CR,
+ CY,
+ CZ,
+ DE,
+ DJ,
+ DK,
+ DO,
+ EE,
+ EG,
+ ES,
+ FI,
+ FK,
+ FO,
+ FR,
+ GB,
+ GE,
+ GI,
+ GL,
+ GR,
+ GT,
+ HN,
+ HR,
+ HU,
+ IE,
+ IL,
+ IQ,
+ IS,
+ IT,
+ JO,
+ KW,
+ KZ,
+ LB,
+ LC,
+ LI,
+ LT,
+ LU,
+ LV,
+ LY,
+ MC,
+ MD,
+ ME,
+ MK,
+ MN,
+ MR,
+ MT,
+ MU,
+ NI,
+ NL,
+ NO,
+ OM,
+ PK,
+ PL,
+ PS,
+ PT,
+ QA,
+ RO,
+ RS,
+ RU,
+ SA,
+ SC,
+ SD,
+ SE,
+ SI,
+ SK,
+ SM,
+ SO,
+ ST,
+ SV,
+ TL,
+ TN,
+ TR,
+ UA,
+ VA,
+ VG,
+ XK,
+ YE,
+}
+
+impl Country {
+ pub fn from_str(country: &str) -> Option<Self> {
+ match country {
+ "AD" => Some(AD),
+ "AE" => Some(AE),
+ "AL" => Some(AL),
+ "AT" => Some(AT),
+ "AZ" => Some(AZ),
+ "BA" => Some(BA),
+ "BE" => Some(BE),
+ "BG" => Some(BG),
+ "BH" => Some(BH),
+ "BI" => Some(BI),
+ "BR" => Some(BR),
+ "BY" => Some(BY),
+ "CH" => Some(CH),
+ "CR" => Some(CR),
+ "CY" => Some(CY),
+ "CZ" => Some(CZ),
+ "DE" => Some(DE),
+ "DJ" => Some(DJ),
+ "DK" => Some(DK),
+ "DO" => Some(DO),
+ "EE" => Some(EE),
+ "EG" => Some(EG),
+ "ES" => Some(ES),
+ "FI" => Some(FI),
+ "FK" => Some(FK),
+ "FO" => Some(FO),
+ "FR" => Some(FR),
+ "GB" => Some(GB),
+ "GE" => Some(GE),
+ "GI" => Some(GI),
+ "GL" => Some(GL),
+ "GR" => Some(GR),
+ "GT" => Some(GT),
+ "HN" => Some(HN),
+ "HR" => Some(HR),
+ "HU" => Some(HU),
+ "IE" => Some(IE),
+ "IL" => Some(IL),
+ "IQ" => Some(IQ),
+ "IS" => Some(IS),
+ "IT" => Some(IT),
+ "JO" => Some(JO),
+ "KW" => Some(KW),
+ "KZ" => Some(KZ),
+ "LB" => Some(LB),
+ "LC" => Some(LC),
+ "LI" => Some(LI),
+ "LT" => Some(LT),
+ "LU" => Some(LU),
+ "LV" => Some(LV),
+ "LY" => Some(LY),
+ "MC" => Some(MC),
+ "MD" => Some(MD),
+ "ME" => Some(ME),
+ "MK" => Some(MK),
+ "MN" => Some(MN),
+ "MR" => Some(MR),
+ "MT" => Some(MT),
+ "MU" => Some(MU),
+ "NI" => Some(NI),
+ "NL" => Some(NL),
+ "NO" => Some(NO),
+ "OM" => Some(OM),
+ "PK" => Some(PK),
+ "PL" => Some(PL),
+ "PS" => Some(PS),
+ "PT" => Some(PT),
+ "QA" => Some(QA),
+ "RO" => Some(RO),
+ "RS" => Some(RS),
+ "RU" => Some(RU),
+ "SA" => Some(SA),
+ "SC" => Some(SC),
+ "SD" => Some(SD),
+ "SE" => Some(SE),
+ "SI" => Some(SI),
+ "SK" => Some(SK),
+ "SM" => Some(SM),
+ "SO" => Some(SO),
+ "ST" => Some(ST),
+ "SV" => Some(SV),
+ "TL" => Some(TL),
+ "TN" => Some(TN),
+ "TR" => Some(TR),
+ "UA" => Some(UA),
+ "VA" => Some(VA),
+ "VG" => Some(VG),
+ "XK" => Some(XK),
+ "YE" => Some(YE),
+ _ => None,
+ }
+ }
+
+ pub const fn as_str(self) -> &'static str {
+ match self {
+ AD => "AD",
+ AE => "AE",
+ AL => "AL",
+ AT => "AT",
+ AZ => "AZ",
+ BA => "BA",
+ BE => "BE",
+ BG => "BG",
+ BH => "BH",
+ BI => "BI",
+ BR => "BR",
+ BY => "BY",
+ CH => "CH",
+ CR => "CR",
+ CY => "CY",
+ CZ => "CZ",
+ DE => "DE",
+ DJ => "DJ",
+ DK => "DK",
+ DO => "DO",
+ EE => "EE",
+ EG => "EG",
+ ES => "ES",
+ FI => "FI",
+ FK => "FK",
+ FO => "FO",
+ FR => "FR",
+ GB => "GB",
+ GE => "GE",
+ GI => "GI",
+ GL => "GL",
+ GR => "GR",
+ GT => "GT",
+ HN => "HN",
+ HR => "HR",
+ HU => "HU",
+ IE => "IE",
+ IL => "IL",
+ IQ => "IQ",
+ IS => "IS",
+ IT => "IT",
+ JO => "JO",
+ KW => "KW",
+ KZ => "KZ",
+ LB => "LB",
+ LC => "LC",
+ LI => "LI",
+ LT => "LT",
+ LU => "LU",
+ LV => "LV",
+ LY => "LY",
+ MC => "MC",
+ MD => "MD",
+ ME => "ME",
+ MK => "MK",
+ MN => "MN",
+ MR => "MR",
+ MT => "MT",
+ MU => "MU",
+ NI => "NI",
+ NL => "NL",
+ NO => "NO",
+ OM => "OM",
+ PK => "PK",
+ PL => "PL",
+ PS => "PS",
+ PT => "PT",
+ QA => "QA",
+ RO => "RO",
+ RS => "RS",
+ RU => "RU",
+ SA => "SA",
+ SC => "SC",
+ SD => "SD",
+ SE => "SE",
+ SI => "SI",
+ SK => "SK",
+ SM => "SM",
+ SO => "SO",
+ ST => "ST",
+ SV => "SV",
+ TL => "TL",
+ TN => "TN",
+ TR => "TR",
+ UA => "UA",
+ VA => "VA",
+ VG => "VG",
+ XK => "XK",
+ YE => "YE",
+ }
+ }
+
+ pub const fn as_bytes(self) -> [u8; 2] {
+ match self {
+ AD => [b'A', b'D'],
+ AE => [b'A', b'E'],
+ AL => [b'A', b'L'],
+ AT => [b'A', b'T'],
+ AZ => [b'A', b'Z'],
+ BA => [b'B', b'A'],
+ BE => [b'B', b'E'],
+ BG => [b'B', b'G'],
+ BH => [b'B', b'H'],
+ BI => [b'B', b'I'],
+ BR => [b'B', b'R'],
+ BY => [b'B', b'Y'],
+ CH => [b'C', b'H'],
+ CR => [b'C', b'R'],
+ CY => [b'C', b'Y'],
+ CZ => [b'C', b'Z'],
+ DE => [b'D', b'E'],
+ DJ => [b'D', b'J'],
+ DK => [b'D', b'K'],
+ DO => [b'D', b'O'],
+ EE => [b'E', b'E'],
+ EG => [b'E', b'G'],
+ ES => [b'E', b'S'],
+ FI => [b'F', b'I'],
+ FK => [b'F', b'K'],
+ FO => [b'F', b'O'],
+ FR => [b'F', b'R'],
+ GB => [b'G', b'B'],
+ GE => [b'G', b'E'],
+ GI => [b'G', b'I'],
+ GL => [b'G', b'L'],
+ GR => [b'G', b'R'],
+ GT => [b'G', b'T'],
+ HN => [b'H', b'N'],
+ HR => [b'H', b'R'],
+ HU => [b'H', b'U'],
+ IE => [b'I', b'E'],
+ IL => [b'I', b'L'],
+ IQ => [b'I', b'Q'],
+ IS => [b'I', b'S'],
+ IT => [b'I', b'T'],
+ JO => [b'J', b'O'],
+ KW => [b'K', b'W'],
+ KZ => [b'K', b'Z'],
+ LB => [b'L', b'B'],
+ LC => [b'L', b'C'],
+ LI => [b'L', b'I'],
+ LT => [b'L', b'T'],
+ LU => [b'L', b'U'],
+ LV => [b'L', b'V'],
+ LY => [b'L', b'Y'],
+ MC => [b'M', b'C'],
+ MD => [b'M', b'D'],
+ ME => [b'M', b'E'],
+ MK => [b'M', b'K'],
+ MN => [b'M', b'N'],
+ MR => [b'M', b'R'],
+ MT => [b'M', b'T'],
+ MU => [b'M', b'U'],
+ NI => [b'N', b'I'],
+ NL => [b'N', b'L'],
+ NO => [b'N', b'O'],
+ OM => [b'O', b'M'],
+ PK => [b'P', b'K'],
+ PL => [b'P', b'L'],
+ PS => [b'P', b'S'],
+ PT => [b'P', b'T'],
+ QA => [b'Q', b'A'],
+ RO => [b'R', b'O'],
+ RS => [b'R', b'S'],
+ RU => [b'R', b'U'],
+ SA => [b'S', b'A'],
+ SC => [b'S', b'C'],
+ SD => [b'S', b'D'],
+ SE => [b'S', b'E'],
+ SI => [b'S', b'I'],
+ SK => [b'S', b'K'],
+ SM => [b'S', b'M'],
+ SO => [b'S', b'O'],
+ ST => [b'S', b'T'],
+ SV => [b'S', b'V'],
+ TL => [b'T', b'L'],
+ TN => [b'T', b'N'],
+ TR => [b'T', b'R'],
+ UA => [b'U', b'A'],
+ VA => [b'V', b'A'],
+ VG => [b'V', b'G'],
+ XK => [b'X', b'K'],
+ YE => [b'Y', b'E'],
+ }
+ }
+
+ pub const fn iban_len(self) -> usize {
+ match self {
+ AD => 24,
+ AE => 23,
+ AL => 28,
+ AT => 20,
+ AZ => 28,
+ BA => 20,
+ BE => 16,
+ BG => 22,
+ BH => 22,
+ BI => 27,
+ BR => 29,
+ BY => 28,
+ CH => 21,
+ CR => 22,
+ CY => 28,
+ CZ => 24,
+ DE => 22,
+ DJ => 27,
+ DK => 18,
+ DO => 28,
+ EE => 20,
+ EG => 29,
+ ES => 24,
+ FI => 18,
+ FK => 18,
+ FO => 18,
+ FR => 27,
+ GB => 22,
+ GE => 22,
+ GI => 23,
+ GL => 18,
+ GR => 27,
+ GT => 28,
+ HN => 28,
+ HR => 21,
+ HU => 28,
+ IE => 22,
+ IL => 23,
+ IQ => 23,
+ IS => 26,
+ IT => 27,
+ JO => 30,
+ KW => 30,
+ KZ => 20,
+ LB => 28,
+ LC => 32,
+ LI => 21,
+ LT => 20,
+ LU => 20,
+ LV => 21,
+ LY => 25,
+ MC => 27,
+ MD => 24,
+ ME => 22,
+ MK => 19,
+ MN => 20,
+ MR => 27,
+ MT => 31,
+ MU => 30,
+ NI => 28,
+ NL => 18,
+ NO => 15,
+ OM => 23,
+ PK => 24,
+ PL => 28,
+ PS => 29,
+ PT => 25,
+ QA => 29,
+ RO => 24,
+ RS => 22,
+ RU => 33,
+ SA => 24,
+ SC => 31,
+ SD => 18,
+ SE => 24,
+ SI => 19,
+ SK => 24,
+ SM => 27,
+ SO => 23,
+ ST => 25,
+ SV => 28,
+ TL => 23,
+ TN => 24,
+ TR => 26,
+ UA => 29,
+ VA => 22,
+ VG => 24,
+ XK => 20,
+ YE => 30,
+ }
+ }
+
+ pub const fn bban_len(self) -> usize {
+ self.iban_len() - 4
+ }
+
+ pub const fn bank_id(self) -> core::ops::Range<usize> {
+ match self {
+ AD => 0..4,
+ AE => 0..3,
+ AL => 0..3,
+ AT => 0..5,
+ AZ => 0..4,
+ BA => 0..3,
+ BE => 0..3,
+ BG => 0..4,
+ BH => 0..4,
+ BI => 0..5,
+ BR => 0..8,
+ BY => 0..4,
+ CH => 0..5,
+ CR => 0..4,
+ CY => 0..3,
+ CZ => 0..4,
+ DE => 0..8,
+ DJ => 0..5,
+ DK => 0..4,
+ DO => 0..4,
+ EE => 0..2,
+ EG => 0..4,
+ ES => 0..4,
+ FI => 0..3,
+ FK => 0..2,
+ FO => 0..4,
+ FR => 0..5,
+ GB => 0..4,
+ GE => 0..2,
+ GI => 0..4,
+ GL => 0..4,
+ GR => 0..3,
+ GT => 0..4,
+ HN => 0..4,
+ HR => 0..7,
+ HU => 0..3,
+ IE => 0..4,
+ IL => 0..3,
+ IQ => 0..4,
+ IS => 0..2,
+ IT => 1..6,
+ JO => 4..8,
+ KW => 0..4,
+ KZ => 0..3,
+ LB => 0..4,
+ LC => 0..4,
+ LI => 0..5,
+ LT => 0..5,
+ LU => 0..3,
+ LV => 0..4,
+ LY => 0..3,
+ MC => 0..5,
+ MD => 0..2,
+ ME => 0..3,
+ MK => 0..3,
+ MN => 0..4,
+ MR => 0..5,
+ MT => 0..4,
+ MU => 0..6,
+ NI => 0..4,
+ NL => 0..4,
+ NO => 0..4,
+ OM => 0..3,
+ PK => 0..4,
+ PL => 0..0,
+ PS => 0..4,
+ PT => 0..4,
+ QA => 0..4,
+ RO => 0..4,
+ RS => 0..3,
+ RU => 0..9,
+ SA => 0..2,
+ SC => 0..6,
+ SD => 0..2,
+ SE => 0..3,
+ SI => 0..5,
+ SK => 0..4,
+ SM => 1..6,
+ SO => 0..4,
+ ST => 0..4,
+ SV => 0..4,
+ TL => 0..3,
+ TN => 0..2,
+ TR => 0..5,
+ UA => 0..6,
+ VA => 0..3,
+ VG => 0..4,
+ XK => 0..2,
+ YE => 0..4,
+ }
+ }
+
+ pub const fn branch_id(self) -> core::ops::Range<usize> {
+ match self {
+ AD => 4..8,
+ AE => 0..0,
+ AL => 3..8,
+ AT => 0..0,
+ AZ => 0..0,
+ BA => 3..6,
+ BE => 0..0,
+ BG => 4..8,
+ BH => 0..0,
+ BI => 5..10,
+ BR => 8..13,
+ BY => 0..0,
+ CH => 0..0,
+ CR => 0..0,
+ CY => 3..8,
+ CZ => 0..0,
+ DE => 0..0,
+ DJ => 5..10,
+ DK => 0..0,
+ DO => 0..0,
+ EE => 0..0,
+ EG => 4..8,
+ ES => 4..8,
+ FI => 0..0,
+ FK => 0..0,
+ FO => 0..0,
+ FR => 0..0,
+ GB => 4..10,
+ GE => 0..0,
+ GI => 0..0,
+ GL => 0..0,
+ GR => 3..7,
+ GT => 0..0,
+ HN => 0..0,
+ HR => 0..0,
+ HU => 3..7,
+ IE => 4..10,
+ IL => 3..6,
+ IQ => 4..7,
+ IS => 2..4,
+ IT => 6..11,
+ JO => 4..8,
+ KW => 0..0,
+ KZ => 0..0,
+ LB => 0..0,
+ LC => 0..0,
+ LI => 0..0,
+ LT => 0..0,
+ LU => 0..0,
+ LV => 0..0,
+ LY => 3..6,
+ MC => 5..10,
+ MD => 0..0,
+ ME => 0..0,
+ MK => 0..0,
+ MN => 0..0,
+ MR => 5..10,
+ MT => 4..9,
+ MU => 6..8,
+ NI => 0..0,
+ NL => 0..0,
+ NO => 0..0,
+ OM => 0..0,
+ PK => 0..0,
+ PL => 0..8,
+ PS => 0..0,
+ PT => 4..8,
+ QA => 0..0,
+ RO => 0..0,
+ RS => 0..0,
+ RU => 9..14,
+ SA => 0..0,
+ SC => 6..8,
+ SD => 0..0,
+ SE => 0..0,
+ SI => 0..0,
+ SK => 0..0,
+ SM => 6..11,
+ SO => 4..7,
+ ST => 4..8,
+ SV => 0..0,
+ TL => 0..0,
+ TN => 2..5,
+ TR => 0..0,
+ UA => 0..0,
+ VA => 0..0,
+ VG => 0..0,
+ XK => 2..4,
+ YE => 4..8,
+ }
+ }
+
+ pub const fn bban_pattern(self) -> Pattern {
+ match self {
+ AD => &[(8, N), (12, C)],
+ AE => &[(19, N)],
+ AL => &[(8, N), (16, C)],
+ AT => &[(16, N)],
+ AZ => &[(4, A), (20, C)],
+ BA => &[(16, N)],
+ BE => &[(12, N)],
+ BG => &[(4, A), (6, N), (8, C)],
+ BH => &[(4, A), (14, C)],
+ BI => &[(23, N)],
+ BR => &[(23, N), (1, A), (1, C)],
+ BY => &[(4, C), (4, N), (16, C)],
+ CH => &[(5, N), (12, C)],
+ CR => &[(18, N)],
+ CY => &[(8, N), (16, C)],
+ CZ => &[(20, N)],
+ DE => &[(18, N)],
+ DJ => &[(23, N)],
+ DK => &[(14, N)],
+ DO => &[(4, C), (20, N)],
+ EE => &[(16, N)],
+ EG => &[(25, N)],
+ ES => &[(20, N)],
+ FI => &[(14, N)],
+ FK => &[(2, A), (12, N)],
+ FO => &[(14, N)],
+ FR => &[(10, N), (11, C), (2, N)],
+ GB => &[(4, A), (14, N)],
+ GE => &[(2, A), (16, N)],
+ GI => &[(4, A), (15, C)],
+ GL => &[(14, N)],
+ GR => &[(7, N), (16, C)],
+ GT => &[(24, C)],
+ HN => &[(4, A), (20, N)],
+ HR => &[(17, N)],
+ HU => &[(24, N)],
+ IE => &[(4, A), (14, N)],
+ IL => &[(19, N)],
+ IQ => &[(4, A), (15, N)],
+ IS => &[(22, N)],
+ IT => &[(1, A), (10, N), (12, C)],
+ JO => &[(4, A), (4, N), (18, C)],
+ KW => &[(4, A), (22, C)],
+ KZ => &[(3, N), (13, C)],
+ LB => &[(4, N), (20, C)],
+ LC => &[(4, A), (24, C)],
+ LI => &[(5, N), (12, C)],
+ LT => &[(16, N)],
+ LU => &[(3, N), (13, C)],
+ LV => &[(4, A), (13, C)],
+ LY => &[(21, N)],
+ MC => &[(10, N), (11, C), (2, N)],
+ MD => &[(20, C)],
+ ME => &[(18, N)],
+ MK => &[(3, N), (10, C), (2, N)],
+ MN => &[(16, N)],
+ MR => &[(23, N)],
+ MT => &[(4, A), (5, N), (18, C)],
+ MU => &[(4, A), (19, N), (3, A)],
+ NI => &[(4, A), (20, N)],
+ NL => &[(4, A), (10, N)],
+ NO => &[(11, N)],
+ OM => &[(3, N), (16, C)],
+ PK => &[(4, A), (16, C)],
+ PL => &[(24, N)],
+ PS => &[(4, A), (21, C)],
+ PT => &[(21, N)],
+ QA => &[(4, A), (21, C)],
+ RO => &[(4, A), (16, C)],
+ RS => &[(18, N)],
+ RU => &[(14, N), (15, C)],
+ SA => &[(2, N), (18, C)],
+ SC => &[(4, A), (20, N), (3, A)],
+ SD => &[(14, N)],
+ SE => &[(20, N)],
+ SI => &[(15, N)],
+ SK => &[(20, N)],
+ SM => &[(1, A), (10, N), (12, C)],
+ SO => &[(19, N)],
+ ST => &[(21, N)],
+ SV => &[(4, A), (20, N)],
+ TL => &[(19, N)],
+ TN => &[(20, N)],
+ TR => &[(6, N), (16, C)],
+ UA => &[(6, N), (19, C)],
+ VA => &[(18, N)],
+ VG => &[(4, A), (16, N)],
+ XK => &[(16, N)],
+ YE => &[(4, A), (4, N), (18, C)],
+ }
+ }
+
+ pub fn bban_check(self, iban_ascii: &[u8]) -> Result<(), PatternErr> {
+ // IBAN are made of ASCII digits and uppercase
+ debug_assert!(iban_ascii
+ .iter()
+ .all(|b| b.is_ascii_uppercase() || b.is_ascii_digit()));
+ match self {
+ AD => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..8]) || !C.check(&iban_ascii[8..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ AE => {
+ if iban_ascii.len() != 19 {
+ return Err(PatternErr::Len(19, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..19]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ AL => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..8]) || !C.check(&iban_ascii[8..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ AT => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ AZ => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ BA => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ BE => {
+ if iban_ascii.len() != 12 {
+ return Err(PatternErr::Len(12, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..12]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ BG => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4])
+ || !N.check(&iban_ascii[4..10])
+ || !C.check(&iban_ascii[10..18])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ BH => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ BI => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..23]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ BR => {
+ if iban_ascii.len() != 25 {
+ return Err(PatternErr::Len(25, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..23])
+ || !A.check(&iban_ascii[23..24])
+ || !C.check(&iban_ascii[24..25])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ BY => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !C.check(&iban_ascii[0..4])
+ || !N.check(&iban_ascii[4..8])
+ || !C.check(&iban_ascii[8..24])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ CH => {
+ if iban_ascii.len() != 17 {
+ return Err(PatternErr::Len(17, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..5]) || !C.check(&iban_ascii[5..17]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ CR => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ CY => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..8]) || !C.check(&iban_ascii[8..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ CZ => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ DE => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ DJ => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..23]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ DK => {
+ if iban_ascii.len() != 14 {
+ return Err(PatternErr::Len(14, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..14]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ DO => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !C.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ EE => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ EG => {
+ if iban_ascii.len() != 25 {
+ return Err(PatternErr::Len(25, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..25]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ ES => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ FI => {
+ if iban_ascii.len() != 14 {
+ return Err(PatternErr::Len(14, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..14]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ FK => {
+ if iban_ascii.len() != 14 {
+ return Err(PatternErr::Len(14, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..2]) || !N.check(&iban_ascii[2..14]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ FO => {
+ if iban_ascii.len() != 14 {
+ return Err(PatternErr::Len(14, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..14]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ FR => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..10])
+ || !C.check(&iban_ascii[10..21])
+ || !N.check(&iban_ascii[21..23])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ GB => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ GE => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..2]) || !N.check(&iban_ascii[2..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ GI => {
+ if iban_ascii.len() != 19 {
+ return Err(PatternErr::Len(19, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..19]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ GL => {
+ if iban_ascii.len() != 14 {
+ return Err(PatternErr::Len(14, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..14]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ GR => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..7]) || !C.check(&iban_ascii[7..23]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ GT => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !C.check(&iban_ascii[0..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ HN => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ HR => {
+ if iban_ascii.len() != 17 {
+ return Err(PatternErr::Len(17, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..17]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ HU => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ IE => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ IL => {
+ if iban_ascii.len() != 19 {
+ return Err(PatternErr::Len(19, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..19]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ IQ => {
+ if iban_ascii.len() != 19 {
+ return Err(PatternErr::Len(19, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..19]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ IS => {
+ if iban_ascii.len() != 22 {
+ return Err(PatternErr::Len(22, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..22]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ IT => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..1])
+ || !N.check(&iban_ascii[1..11])
+ || !C.check(&iban_ascii[11..23])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ JO => {
+ if iban_ascii.len() != 26 {
+ return Err(PatternErr::Len(26, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4])
+ || !N.check(&iban_ascii[4..8])
+ || !C.check(&iban_ascii[8..26])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ KW => {
+ if iban_ascii.len() != 26 {
+ return Err(PatternErr::Len(26, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..26]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ KZ => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..3]) || !C.check(&iban_ascii[3..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ LB => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ LC => {
+ if iban_ascii.len() != 28 {
+ return Err(PatternErr::Len(28, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..28]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ LI => {
+ if iban_ascii.len() != 17 {
+ return Err(PatternErr::Len(17, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..5]) || !C.check(&iban_ascii[5..17]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ LT => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ LU => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..3]) || !C.check(&iban_ascii[3..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ LV => {
+ if iban_ascii.len() != 17 {
+ return Err(PatternErr::Len(17, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..17]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ LY => {
+ if iban_ascii.len() != 21 {
+ return Err(PatternErr::Len(21, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..21]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ MC => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..10])
+ || !C.check(&iban_ascii[10..21])
+ || !N.check(&iban_ascii[21..23])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ MD => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !C.check(&iban_ascii[0..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ ME => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ MK => {
+ if iban_ascii.len() != 15 {
+ return Err(PatternErr::Len(15, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..3])
+ || !C.check(&iban_ascii[3..13])
+ || !N.check(&iban_ascii[13..15])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ MN => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ MR => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..23]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ MT => {
+ if iban_ascii.len() != 27 {
+ return Err(PatternErr::Len(27, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4])
+ || !N.check(&iban_ascii[4..9])
+ || !C.check(&iban_ascii[9..27])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ MU => {
+ if iban_ascii.len() != 26 {
+ return Err(PatternErr::Len(26, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4])
+ || !N.check(&iban_ascii[4..23])
+ || !A.check(&iban_ascii[23..26])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ NI => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ NL => {
+ if iban_ascii.len() != 14 {
+ return Err(PatternErr::Len(14, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..14]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ NO => {
+ if iban_ascii.len() != 11 {
+ return Err(PatternErr::Len(11, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..11]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ OM => {
+ if iban_ascii.len() != 19 {
+ return Err(PatternErr::Len(19, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..3]) || !C.check(&iban_ascii[3..19]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ PK => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ PL => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ PS => {
+ if iban_ascii.len() != 25 {
+ return Err(PatternErr::Len(25, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..25]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ PT => {
+ if iban_ascii.len() != 21 {
+ return Err(PatternErr::Len(21, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..21]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ QA => {
+ if iban_ascii.len() != 25 {
+ return Err(PatternErr::Len(25, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..25]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ RO => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !C.check(&iban_ascii[4..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ RS => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ RU => {
+ if iban_ascii.len() != 29 {
+ return Err(PatternErr::Len(29, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..14]) || !C.check(&iban_ascii[14..29]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SA => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..2]) || !C.check(&iban_ascii[2..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SC => {
+ if iban_ascii.len() != 27 {
+ return Err(PatternErr::Len(27, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4])
+ || !N.check(&iban_ascii[4..24])
+ || !A.check(&iban_ascii[24..27])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SD => {
+ if iban_ascii.len() != 14 {
+ return Err(PatternErr::Len(14, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..14]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SE => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SI => {
+ if iban_ascii.len() != 15 {
+ return Err(PatternErr::Len(15, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..15]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SK => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SM => {
+ if iban_ascii.len() != 23 {
+ return Err(PatternErr::Len(23, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..1])
+ || !N.check(&iban_ascii[1..11])
+ || !C.check(&iban_ascii[11..23])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SO => {
+ if iban_ascii.len() != 19 {
+ return Err(PatternErr::Len(19, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..19]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ ST => {
+ if iban_ascii.len() != 21 {
+ return Err(PatternErr::Len(21, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..21]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ SV => {
+ if iban_ascii.len() != 24 {
+ return Err(PatternErr::Len(24, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..24]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ TL => {
+ if iban_ascii.len() != 19 {
+ return Err(PatternErr::Len(19, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..19]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ TN => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ TR => {
+ if iban_ascii.len() != 22 {
+ return Err(PatternErr::Len(22, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..6]) || !C.check(&iban_ascii[6..22]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ UA => {
+ if iban_ascii.len() != 25 {
+ return Err(PatternErr::Len(25, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..6]) || !C.check(&iban_ascii[6..25]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ VA => {
+ if iban_ascii.len() != 18 {
+ return Err(PatternErr::Len(18, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..18]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ VG => {
+ if iban_ascii.len() != 20 {
+ return Err(PatternErr::Len(20, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4]) || !N.check(&iban_ascii[4..20]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ XK => {
+ if iban_ascii.len() != 16 {
+ return Err(PatternErr::Len(16, iban_ascii.len()));
+ } else if !N.check(&iban_ascii[0..16]) {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ YE => {
+ if iban_ascii.len() != 26 {
+ return Err(PatternErr::Len(26, iban_ascii.len()));
+ } else if !A.check(&iban_ascii[0..4])
+ || !N.check(&iban_ascii[4..8])
+ || !C.check(&iban_ascii[8..26])
+ {
+ return Err(PatternErr::Malformed);
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl Display for Country {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(self.as_str())
+ }
+}
+
+/// Generate random ASCII string following an IBAN pattern rules
+pub fn rng_pattern(out: &mut [u8], pattern: Pattern) {
+ let mut cursor = 0;
+ for (len, rule) in pattern {
+ let alphabet = match rule {
+ IbanC::C => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+ IbanC::N => "0123456789",
+ IbanC::A => "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ };
+ for b in &mut out[cursor..cursor + *len as usize] {
+ *b = *fastrand::choice(alphabet.as_bytes()).unwrap();
+ }
+ cursor += *len as usize
+ }
+}
+
+#[cfg(test)]
+pub const VALID_IBAN: [(&str, Option<&str>); 89] = [
+ ("AD1200012030200359100100", Some("00012030200359100100")),
+ ("AE070331234567890123456", Some("0331234567890123456")),
+ (
+ "AL47212110090000000235698741",
+ Some("212110090000000235698741"),
+ ),
+ ("AT611904300234573201", Some("1904300234573201")),
+ (
+ "AZ21NABZ00000000137010001944",
+ Some("NABZ00000000137010001944"),
+ ),
+ ("BA391290079401028494", Some("1290079401028494")),
+ ("BE68539007547034", Some("539007547034")),
+ ("BG80BNBG96611020345678", Some("BNBG96611020345678")),
+ ("BH67BMAG00001299123456", Some("BMAG00001299123456")),
+ (
+ "BI4210000100010000332045181",
+ Some("10000100010000332045181"),
+ ),
+ (
+ "BR1800360305000010009795493C1",
+ Some("00360305000010009795493C1"),
+ ),
+ (
+ "BY13NBRB3600900000002Z00AB00",
+ Some("NBRB3600900000002Z00AB00"),
+ ),
+ ("CH9300762011623852957", Some("00762011623852957")),
+ ("CR05015202001026284066", Some("015202001026284066")),
+ (
+ "CY17002001280000001200527600",
+ Some("002001280000001200527600"),
+ ),
+ ("CZ6508000000192000145399", Some("08000000192000145399")),
+ ("DE89370400440532013000", Some("370400440532013000")),
+ (
+ "DJ2100010000000154000100186",
+ Some("00010000000154000100186"),
+ ),
+ ("DK5000400440116243", Some("00400440116243")),
+ (
+ "DO28BAGR00000001212453611324",
+ Some("BAGR00000001212453611324"),
+ ),
+ ("EE382200221020145685", Some("2200221020145685")),
+ (
+ "EG380019000500000000263180002",
+ Some("0019000500000000263180002"),
+ ),
+ ("ES9121000418450200051332", Some("21000418450200051332")),
+ ("FI2112345600000785", None),
+ ("FK88SC123456789012", Some("SC123456789012")),
+ ("FO6264600001631634", Some("64600001631634")),
+ (
+ "FR1420041010050500013M02606",
+ Some("20041010050500013M02606"),
+ ),
+ ("GB29NWBK60161331926819", Some("NWBK60161331926819")),
+ ("GE29NB0000000101904917", Some("NB0000000101904917")),
+ ("GI75NWBK000000007099453", Some("NWBK000000007099453")),
+ ("GL8964710001000206", Some("64710001000206")),
+ (
+ "GR1601101250000000012300695",
+ Some("01101250000000012300695"),
+ ),
+ (
+ "GT82TRAJ01020000001210029690",
+ Some("TRAJ01020000001210029690"),
+ ),
+ (
+ "HN88CABF00000000000250005469",
+ Some("CABF00000000000250005469"),
+ ),
+ ("HR1210010051863000160", Some("10010051863000160")),
+ (
+ "HU42117730161111101800000000",
+ Some("117730161111101800000000"),
+ ),
+ ("IE29AIBK93115212345678", Some("AIBK93115212345678")),
+ ("IL620108000000099999999", Some("0108000000099999999")),
+ ("IQ98NBIQ850123456789012", Some("NBIQ850123456789012")),
+ ("IS140159260076545510730339", Some("0159260076545510730339")),
+ (
+ "IT60X0542811101000000123456",
+ Some("X0542811101000000123456"),
+ ),
+ (
+ "JO94CBJO0010000000000131000302",
+ Some("CBJO0010000000000131000302"),
+ ),
+ (
+ "KW81CBKU0000000000001234560101",
+ Some("CBKU0000000000001234560101"),
+ ),
+ ("KZ86125KZT5004100100", Some("125KZT5004100100")),
+ (
+ "LB62099900000001001901229114",
+ Some("099900000001001901229114"),
+ ),
+ (
+ "LC55HEMM000100010012001200023015",
+ Some("HEMM000100010012001200023015"),
+ ),
+ ("LI21088100002324013AA", Some("088100002324013AA")),
+ ("LT121000011101001000", Some("1000011101001000")),
+ ("LU280019400644750000", Some("0019400644750000")),
+ ("LV80BANK0000435195001", Some("BANK0000435195001")),
+ ("LY83002048000020100120361", Some("002048000020100120361")),
+ (
+ "MC5811222000010123456789030",
+ Some("11222000010123456789030"),
+ ),
+ ("MD24AG000225100013104168", Some("AG000225100013104168")),
+ ("ME25505000012345678951", Some("505000012345678951")),
+ ("MK07250120000058984", Some("250120000058984")),
+ ("MN121234123456789123", Some("1234123456789123")),
+ (
+ "MR1300020001010000123456753",
+ Some("00020001010000123456753"),
+ ),
+ (
+ "MT84MALT011000012345MTLCAST001S",
+ Some("MALT011000012345MTLCAST001S"),
+ ),
+ (
+ "MU17BOMM0101101030300200000MUR",
+ Some("BOMM0101101030300200000MUR"),
+ ),
+ (
+ "NI45BAPR00000013000003558124",
+ Some("BAPR00000013000003558124"),
+ ),
+ ("NL91ABNA0417164300", Some("ABNA0417164300")),
+ ("NO9386011117947", Some("86011117947")),
+ ("OM810180000001299123456", Some("0180000001299123456")),
+ ("PK36SCBL0000001123456702", Some("SCBL0000001123456702")),
+ (
+ "PL61109010140000071219812874",
+ Some("109010140000071219812874"),
+ ),
+ (
+ "PS92PALS000000000400123456702",
+ Some("PALS000000000400123456702"),
+ ),
+ ("PT50000201231234567890154", Some("000201231234567890154")),
+ (
+ "QA58DOHB00001234567890ABCDEFG",
+ Some("DOHB00001234567890ABCDEFG"),
+ ),
+ ("RO49AAAA1B31007593840000", Some("AAAA1B31007593840000")),
+ ("RS35260005601001611379", Some("260005601001611379")),
+ (
+ "RU0304452522540817810538091310419",
+ Some("04452522540817810538091310419"),
+ ),
+ ("SA0380000000608010167519", Some("80000000608010167519")),
+ (
+ "SC18SSCB11010000000000001497USD",
+ Some("SSCB11010000000000001497USD"),
+ ),
+ ("SD2129010501234001", Some("29010501234001")),
+ ("SE4550000000058398257466", Some("50000000058398257466")),
+ ("SI56263300012039086", Some("263300012039086")),
+ ("SK3112000000198742637541", Some("12000000198742637541")),
+ (
+ "SM86U0322509800000000270100",
+ Some("U0322509800000000270100"),
+ ),
+ ("SO211000001001000100141", Some("1000001001000100141")),
+ ("ST23000100010051845310146", Some("000100010051845310146")),
+ (
+ "SV62CENR00000000000000700025",
+ Some("CENR00000000000000700025"),
+ ),
+ ("TL380080012345678910157", Some("0080012345678910157")),
+ ("TN5910006035183598478831", Some("10006035183598478831")),
+ ("TR330006100519786457841326", Some("0006100519786457841326")),
+ (
+ "UA213223130000026007233566001",
+ Some("3223130000026007233566001"),
+ ),
+ ("VA59001123000012345678", Some("001123000012345678")),
+ ("VG96VPVG0000012345678901", Some("VPVG0000012345678901")),
+ ("XK051212012345678906", Some("1212012345678906")),
+ (
+ "YE15CBYE0001018861234567891234",
+ Some("CBYE0001018861234567891234"),
+ ),
+];
diff --git a/taler-magnet-bank/src/lib.rs b/taler-magnet-bank/src/lib.rs
@@ -17,7 +17,7 @@
use std::{borrow::Cow, str::FromStr};
use taler_common::types::{
- iban::{IbanErrorKind, ParseIbanError, IBAN},
+ iban::{Country, IbanErrorKind, ParseIbanError, IBAN},
payto::{FullPayto, IbanPayto, Payto, PaytoErr, PaytoImpl, PaytoURI, TransferPayto},
};
@@ -85,7 +85,7 @@ impl HuIban {
} else {
Cow::Borrowed(bban)
};
- let iban = IBAN::from_parts("HU", &full_bban);
+ let iban = IBAN::from_parts(Country::HU, &full_bban);
Ok(Self(iban))
}
@@ -104,7 +104,7 @@ pub enum HuIbanErr {
#[error("contains illegal characters (only 0-9 allowed)")]
Invalid,
#[error("expected an hungarian IBAN starting with HU got {0}")]
- CountryCode(String),
+ Country(Country),
#[error("invalid length expected 16 or 24 chars got {0}")]
BbanSize(usize),
#[error("invalid checksum for {0} expected {1} got {2}")]
@@ -129,9 +129,9 @@ impl TryFrom<IBAN> for HuIban {
type Error = HuIbanErr;
fn try_from(iban: IBAN) -> Result<Self, Self::Error> {
- let country_code = iban.country_code();
- if country_code != "HU" {
- return Err(HuIbanErr::CountryCode(country_code.to_owned()));
+ let country = iban.country();
+ if country != Country::HU {
+ return Err(HuIbanErr::Country(country));
}
Self::check_bban(iban.bban())?;