Validation

Phone and SIM numbers

The numbers that identify phones and SIM cards: IMEI and IMEISV read and made, CDMA MEIDs converted to decimal with their pseudo-ESN, SIM ICCIDs checked with their country, and IMSIs split into country and operator codes.

Version 1.0.0 · 5 functions · 2 crates

#phone#devices#mobile#validation

Functions

What use imei.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use imei_parse, imei_make from imei (Quillo 0.6).

imei_parse(text: str) → any

An IMEI (15 digits) or IMEISV (16) read: { valid, kind, imei, tac, serial, check_digit, svn, formatted, error }. tac, the type allocation code, names the phone model in the GSMA's database; an IMEISV has a software version (svn) in place of the check digit.

imei_make(tac: str, serial: str) → stror an error

An IMEI from a type allocation code (8 digits) and a serial (6), with its check digit: for test data and device labels.

meid_parse(text: str) → any

A CDMA MEID read, hex (14 digits, maybe with its check digit) or decimal (18 digits): { valid, hex, decimal, manufacturer, serial, check_digit, pesn }. pesn is the pseudo-ESN made from it (80 and the last 24 bits of its SHA-1), which older systems ask for.

iccid_parse(text: str) → any

A SIM card's ICCID (18 to 20 digits, starting 89) read: { valid, iccid, country_code, country, issuer_and_account, check_digit, error }, checked by its Luhn digit when it has one (19 or 20 digits). The country is the calling code's (+63 the Philippines).

imsi_parse(text: str) → any

An IMSI (the subscriber number on a SIM, 15 digits) split: { valid, mcc, mnc, msin, country }. The network code has three digits in North America and a few other countries, two elsewhere.

Get it

$ quillo install imei               # imei.qwer into this folder$ quillo install imei --example     # and its example, imei.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/imei.qwer$ curl -O https://quillolang.org/plugins/files/imei.qwe

Then, in your program, all of it or only what you call:

main.qwe
use imei.qweruse imei_parse, imei_make from imei      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
sha1 = "0.10"serde_json = { version = "1", features = ["preserve_order"] }

Example

imei.qwe
// A phone repair shop's intake: the IMEI from the label checked (a typo means the wrong// customer's phone), the SIM's ICCID read, and an old CDMA phone's MEID converted.////   quillo run imei.qwe      then open http://localhost:8080/intakeuse "imei.qwer"route GET /imei/:n = imei_parse(n)route GET /intake = {    phone: imei_parse("35-209900-176148-1"),    typo: imei_parse("352099001761482").error,    imeisv: imei_parse("3520990017614823"),    made: imei_make("35209900", "176148")?,    sim: iccid_parse("8963 0123 4567 8901 238"),    subscriber: imsi_parse("515031234567890"),    us_subscriber: imsi_parse("310260123456789"),    cdma: meid_parse("A10000009296F2"),}
$ quillo run imei.qwe$ curl 'localhost:8080/intake'

Source

imei.qwer
//! The numbers that identify phones and SIM cards: IMEI and IMEISV read (type allocation code, serial, check digit) and made, CDMA MEIDs converted to decimal and their pseudo-ESN worked out, SIM ICCIDs checked with their country and issuer, and IMSIs split into the network's country and operator codes.//! version: 1.0.0//!//! [dependencies]//! sha1 = "0.10"//! serde_json = { version = "1", features = ["preserve_order"] }use serde_json::{json, Value};use sha1::{Digest, Sha1};fn clean(s: &str) -> String {    s.chars().filter(|c| c.is_ascii_alphanumeric()).map(|c| c.to_ascii_uppercase()).collect()}/// The Luhn check digit in `base` (10 for IMEI and ICCID, 16 for MEID) for `body`.fn luhn_digit(body: &str, base: u32) -> Option<u32> {    let mut sum = 0;    for (i, c) in body.chars().rev().enumerate() {        let v = c.to_digit(base)?;        let x = if i % 2 == 0 { v * 2 } else { v };        sum += x / base + x % base;    }    Some((base - sum % base) % base)}/// Mobile country codes and their countries (ITU E.212), the ones most networks use.const MCC: &[(&str, &str)] = &[    ("202", "Greece"), ("204", "Netherlands"), ("206", "Belgium"), ("208", "France"), ("214", "Spain"), ("216", "Hungary"), ("222", "Italy"), ("226", "Romania"),    ("228", "Switzerland"), ("230", "Czech Republic"), ("231", "Slovakia"), ("232", "Austria"), ("234", "United Kingdom"), ("235", "United Kingdom"), ("238", "Denmark"),    ("240", "Sweden"), ("242", "Norway"), ("244", "Finland"), ("246", "Lithuania"), ("247", "Latvia"), ("248", "Estonia"), ("250", "Russia"), ("255", "Ukraine"),    ("260", "Poland"), ("262", "Germany"), ("268", "Portugal"), ("270", "Luxembourg"), ("272", "Ireland"), ("274", "Iceland"), ("280", "Cyprus"), ("284", "Bulgaria"),    ("286", "Turkey"), ("293", "Slovenia"), ("297", "Montenegro"), ("219", "Croatia"), ("220", "Serbia"), ("302", "Canada"), ("310", "United States"), ("311", "United States"),    ("312", "United States"), ("313", "United States"), ("314", "United States"), ("315", "United States"), ("316", "United States"), ("334", "Mexico"), ("338", "Jamaica"),    ("344", "Antigua and Barbuda"), ("350", "Bermuda"), ("370", "Dominican Republic"), ("372", "Haiti"), ("374", "Trinidad and Tobago"), ("404", "India"), ("405", "India"),    ("410", "Pakistan"), ("413", "Sri Lanka"), ("414", "Myanmar"), ("415", "Lebanon"), ("416", "Jordan"), ("418", "Iraq"), ("419", "Kuwait"), ("420", "Saudi Arabia"),    ("424", "United Arab Emirates"), ("425", "Israel"), ("426", "Bahrain"), ("427", "Qatar"), ("432", "Iran"), ("440", "Japan"), ("441", "Japan"), ("450", "South Korea"),    ("452", "Vietnam"), ("454", "Hong Kong"), ("455", "Macau"), ("456", "Cambodia"), ("457", "Laos"), ("460", "China"), ("466", "Taiwan"), ("470", "Bangladesh"),    ("502", "Malaysia"), ("505", "Australia"), ("510", "Indonesia"), ("515", "Philippines"), ("520", "Thailand"), ("525", "Singapore"), ("528", "Brunei"), ("530", "New Zealand"),    ("602", "Egypt"), ("603", "Algeria"), ("604", "Morocco"), ("605", "Tunisia"), ("620", "Ghana"), ("621", "Nigeria"), ("630", "DR Congo"), ("639", "Kenya"), ("640", "Tanzania"),    ("641", "Uganda"), ("655", "South Africa"), ("704", "Guatemala"), ("706", "El Salvador"), ("708", "Honduras"), ("710", "Nicaragua"), ("712", "Costa Rica"), ("714", "Panama"),    ("716", "Peru"), ("722", "Argentina"), ("724", "Brazil"), ("730", "Chile"), ("732", "Colombia"), ("734", "Venezuela"), ("736", "Bolivia"), ("740", "Ecuador"),    ("744", "Paraguay"), ("748", "Uruguay"), ("901", "International"),];/// Countries whose operators have three-digit network codes.const THREE_DIGIT_MNC: &[&str] = &["302", "310", "311", "312", "313", "314", "315", "316", "334", "338", "344", "350", "365", "405", "708", "722", "732"];/// Country calling codes for ICCIDs (ITU E.164), longest first when matching.const CALLING: &[(&str, &str)] = &[    ("1", "United States or Canada"), ("7", "Russia or Kazakhstan"), ("20", "Egypt"), ("27", "South Africa"), ("30", "Greece"), ("31", "Netherlands"), ("32", "Belgium"),    ("33", "France"), ("34", "Spain"), ("36", "Hungary"), ("39", "Italy"), ("40", "Romania"), ("41", "Switzerland"), ("43", "Austria"), ("44", "United Kingdom"),    ("45", "Denmark"), ("46", "Sweden"), ("47", "Norway"), ("48", "Poland"), ("49", "Germany"), ("51", "Peru"), ("52", "Mexico"), ("54", "Argentina"), ("55", "Brazil"),    ("56", "Chile"), ("57", "Colombia"), ("60", "Malaysia"), ("61", "Australia"), ("62", "Indonesia"), ("63", "Philippines"), ("64", "New Zealand"), ("65", "Singapore"),    ("66", "Thailand"), ("81", "Japan"), ("82", "South Korea"), ("84", "Vietnam"), ("86", "China"), ("90", "Turkey"), ("91", "India"), ("92", "Pakistan"), ("234", "Nigeria"),    ("254", "Kenya"), ("351", "Portugal"), ("353", "Ireland"), ("358", "Finland"), ("380", "Ukraine"), ("852", "Hong Kong"), ("886", "Taiwan"), ("966", "Saudi Arabia"),    ("971", "United Arab Emirates"), ("972", "Israel"), ("882", "International networks"), ("883", "International networks"),];/// An IMEI (15 digits) or IMEISV (16) read: `{ valid, kind, imei, tac, serial, check_digit, svn, formatted, error }`. `tac`, the type/// allocation code, names the phone model in the GSMA's database; an IMEISV has a software version (`svn`) in place of the check digit.pub fn imei_parse(text: &str) -> Value {    let s = clean(text);    if !s.chars().all(|c| c.is_ascii_digit()) {        return json!({ "valid": false, "error": "an IMEI has only digits" });    }    match s.len() {        15 => {            let check = luhn_digit(&s[..14], 10);            if check != s[14..].parse().ok() {                return json!({ "valid": false, "kind": "IMEI", "error": "the check digit does not match: a typo" });            }            json!({ "valid": true, "kind": "IMEI", "imei": s, "tac": &s[..8], "serial": &s[8..14], "check_digit": &s[14..], "svn": null, "formatted": format!("{}-{}-{}-{}", &s[..2], &s[2..8], &s[8..14], &s[14..]), "error": null })        }        16 => {            let imei = format!("{}{}", &s[..14], luhn_digit(&s[..14], 10).unwrap_or(0));            json!({ "valid": true, "kind": "IMEISV", "imei": imei, "tac": &s[..8], "serial": &s[8..14], "check_digit": null, "svn": &s[14..], "formatted": format!("{}-{}-{}-{}", &s[..2], &s[2..8], &s[8..14], &s[14..]), "error": null })        }        14 => json!({ "valid": false, "kind": "IMEI", "error": format!("14 digits: the check digit is missing; with it, {}", format!("{}{}", s, luhn_digit(&s, 10).unwrap_or(0))) }),        n => json!({ "valid": false, "error": format!("an IMEI has 15 digits (16 for an IMEISV), not {}", n) }),    }}/// An IMEI from a type allocation code (8 digits) and a serial (6), with its check digit: for test data and device labels.pub fn imei_make(tac: &str, serial: &str) -> Result<String, String> {    let body = format!("{}{}", clean(tac), clean(serial));    if body.len() != 14 || !body.chars().all(|c| c.is_ascii_digit()) {        return Err("a TAC is 8 digits and a serial 6".into());    }    Ok(format!("{}{}", body, luhn_digit(&body, 10).unwrap_or(0)))}/// A CDMA MEID read, hex (14 digits, maybe with its check digit) or decimal (18 digits): `{ valid, hex, decimal, manufacturer,/// serial, check_digit, pesn }`. `pesn` is the pseudo-ESN made from it (80 and the last 24 bits of its SHA-1), which older systems ask for.pub fn meid_parse(text: &str) -> Value {    let s = clean(text);    let hex = match s.len() {        14 | 15 if s.chars().all(|c| c.is_ascii_hexdigit()) => s[..14].to_string(),        18 if s.chars().all(|c| c.is_ascii_digit()) => {            let (a, b) = (s[..10].parse::<u64>().unwrap_or(u64::MAX), s[10..].parse::<u64>().unwrap_or(u64::MAX));            if a > 0xFFFF_FFFF || b > 0xFF_FFFF {                return json!({ "valid": false, "error": "the decimal MEID's parts are out of range" });            }            format!("{:08X}{:06X}", a, b)        }        _ => return json!({ "valid": false, "error": "a MEID is 14 hex digits (15 with its check digit) or 18 decimal digits" }),    };    let check = luhn_digit(&hex, 16).map(|c| format!("{:X}", c)).unwrap_or_default();    if s.len() == 15 && s[14..] != check {        return json!({ "valid": false, "error": "the check digit does not match: a typo" });    }    let bytes: Vec<u8> = (0..7).map(|i| u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap_or(0)).collect();    let h = Sha1::digest(&bytes);    let pesn = format!("80{:02X}{:02X}{:02X}", h[17], h[18], h[19]);    let decimal = format!("{:010}{:08}", u64::from_str_radix(&hex[..8], 16).unwrap_or(0), u64::from_str_radix(&hex[8..], 16).unwrap_or(0));    json!({ "valid": true, "hex": hex, "decimal": decimal, "manufacturer": &hex[..8], "serial": &hex[8..], "check_digit": check, "pesn": pesn, "error": null })}/// A SIM card's ICCID (18 to 20 digits, starting 89) read: `{ valid, iccid, country_code, country, issuer_and_account, check_digit, error }`,/// checked by its Luhn digit when it has one (19 or 20 digits). The country is the calling code's (+63 the Philippines).pub fn iccid_parse(text: &str) -> Value {    let s = clean(text.trim_end_matches(['F', 'f']));    if !s.chars().all(|c| c.is_ascii_digit()) || !(18..=20).contains(&s.len()) {        return json!({ "valid": false, "error": "an ICCID has 18 to 20 digits" });    }    if !s.starts_with("89") {        return json!({ "valid": false, "error": "an ICCID starts with 89 (telecommunications)" });    }    if s.len() >= 19 {        let (body, last) = s.split_at(s.len() - 1);        if luhn_digit(body, 10) != last.parse().ok() {            return json!({ "valid": false, "error": "the check digit does not match: a typo" });        }    }    let rest = &s[2..];    let (cc, country) = [3usize, 2, 1].iter().find_map(|n| CALLING.iter().find(|(c, _)| c.len() == *n && rest.starts_with(c)).map(|(c, name)| (c.to_string(), name.to_string()))).unwrap_or_default();    json!({        "valid": true,        "iccid": s,        "country_code": if cc.is_empty() { Value::Null } else { json!(format!("+{}", cc)) },        "country": if country.is_empty() { Value::Null } else { json!(country) },        "issuer_and_account": &rest[cc.len()..s.len() - 2 - if s.len() >= 19 { 1 } else { 0 }],        "check_digit": if s.len() >= 19 { json!(&s[s.len() - 1..]) } else { Value::Null },        "error": null,    })}/// An IMSI (the subscriber number on a SIM, 15 digits) split: `{ valid, mcc, mnc, msin, country }`. The network code has three/// digits in North America and a few other countries, two elsewhere.pub fn imsi_parse(text: &str) -> Value {    let s = clean(text);    if !s.chars().all(|c| c.is_ascii_digit()) || !(14..=15).contains(&s.len()) {        return json!({ "valid": false, "error": "an IMSI has 15 digits (14 on some old networks)" });    }    let mcc = &s[..3];    let Some((_, country)) = MCC.iter().find(|(c, _)| *c == mcc) else {        return json!({ "valid": false, "error": format!("{} is not a mobile country code in use", mcc) });    };    let mnc_len = if THREE_DIGIT_MNC.contains(&mcc) { 3 } else { 2 };    json!({ "valid": true, "mcc": mcc, "mnc": &s[3..3 + mnc_len], "msin": &s[3 + mnc_len..], "country": country, "error": null })}
Card numbers cards.qwer · 1.0.0Payment card numbers checked before they reach a payment provider: Luhn check digit, brand and length, expiry, and the number formatted or masked for receipts.paymentsformscheckoutvalidation7 views · 7 downloadsCheck digits check_digits.qwer · 1.0.0Check digits made and checked: ISBN-10 and ISBN-13 with conversion, EAN-13, EAN-8 and UPC-A barcodes, IMEI, Luhn, ISO 7064 mod 97-10 references and Verhoeff.validationbarcodesbooksimport6 views · 7 downloadsCountries countries.qwer · 1.0.0ISO 3166 countries for forms and addresses: alpha-2, alpha-3 and numeric codes, names in several languages, calling codes, flags, continents, currencies and EU membership.formsaddressesphonei18n9 views · 7 downloadsISBN isbn.qwer · 1.0.0ISBNs checked, converted between 10 and 13 digits, hyphenated by the ISBN Agency's ranges into group, publisher and title parts with the group's language or country named, and every ISBN in a text found.booksvalidationcodesebooks6 views · 6 downloadsNational ID numbers national_ids.qwer · 1.0.0National identity numbers of 29 countries checked by their own rules (US SSN, UK NINO, CPF, Aadhaar, PH TIN, DNI and NIE, codice fiscale, PESEL...), with the birth date and sex read out where the number holds them.validationidentitycomplianceforms7 views · 7 downloadsPassport MRZ passport_mrz.qwer · 1.0.0Machine-readable zones of passports, ID cards and visas (ICAO 9303 TD1, TD2, TD3, MRV) read into names, numbers and dates with every check digit verified, MRZ lines found in OCR text, and MRZs made for test documents.identitytravelocrvalidation6 views · 9 downloads
Thread
711Useful? Like it, or add to its thread below.

Thread 0 posts

On Phone and SIM numbers: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.