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
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) → anyAn 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 errorAn 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) → anyA 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) → anyA 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) → anyAn 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.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/imei.qwer$ curl -O https://quillolang.org/plugins/files/imei.qweThen, in your program, all of it or only what you call:
use imei.qweruse imei_parse, imei_make from imei // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
sha1 = "0.10"serde_json = { version = "1", features = ["preserve_order"] }Example
// 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
//! 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 })}Related
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.7 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.6 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.9 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.6 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.7 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.6 views · 9 downloadsThread 0 posts
On Phone and SIM numbers: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.