use super::{Error, Fault, Plmn};
#[cfg(feature = "alloc")]
use super::{Faults, Tolerated, plmn::FILLER};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
use core::fmt;
use core::str::FromStr;
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub mod tbcd {
use super::*;
const CHARACTERS: &[u8; 15] = b"0123456789*#abc";
pub fn encode(text: &str) -> Result<Vec<u8>, Error> {
let half = |character: char| {
let known = CHARACTERS
.iter()
.position(|known| char::from(*known) == character);
let expected = "digits, *, #, a, b and c";
let fault = Fault::Character {
given: character,
expected,
};
known
.map(|half| half as u8)
.ok_or(Error::new("tbcd", fault))
};
let mut octets = Vec::with_capacity(text.len().div_ceil(2));
let mut characters = text.chars();
while let Some(first) = characters.next() {
let second = match characters.next() {
Some(second) => half(second)?,
None => FILLER,
};
octets.push(second << 4 | half(first)?);
}
Ok(octets)
}
pub fn decode(octets: &[u8]) -> Result<String, Error> {
let mut text = String::with_capacity(2 * octets.len());
for (index, octet) in octets.iter().enumerate() {
let (low, high) = (octet & 0x0f, octet >> 4);
let last = index + 1 == octets.len();
if low == FILLER || (high == FILLER && !last) {
return Err(Error::new("tbcd", Fault::Digit { given: FILLER }));
}
text.push(char::from(CHARACTERS[usize::from(low)]));
if high != FILLER {
text.push(char::from(CHARACTERS[usize::from(high)]));
}
}
Ok(text)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn encode(type_of_identity: u8, digits: &str) -> Result<Vec<u8>, Error> {
if type_of_identity > 7 {
let fault = Fault::Range {
given: type_of_identity.into(),
max: 7,
};
return Err(Error::new("type-of-identity", fault));
}
decimal("identity-digits", digits, 1, usize::MAX)?;
let digits = digits.as_bytes();
let odd = (digits.len() as u8 & 1) << 3;
let mut octets = Vec::with_capacity(1 + digits.len() / 2);
octets.push((digits[0] - b'0') << 4 | odd | type_of_identity);
for pair in digits[1..].chunks(2) {
let high = pair.get(1).map_or(FILLER, |digit| digit - b'0');
octets.push(high << 4 | (pair[0] - b'0'));
}
Ok(octets)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn decode(octets: &[u8]) -> Result<(u8, String), Error> {
let (read, faults) = decode_tolerant(octets)?;
match faults.first() {
Some(fault) => Err(*fault),
None => Ok(read),
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn decode_tolerant(octets: &[u8]) -> Result<Tolerated<(u8, String)>, Error> {
let member = "identity-digits";
let Some((first, rest)) = octets.split_first() else {
return Err(Error::length(member, "octets", 0, 1, usize::MAX));
};
let digit = |half: u8| match half {
0..=9 => Ok(char::from(b'0' + half)),
given => Err(Error::new(member, Fault::Digit { given })),
};
let (odd, kind) = (first & 0x08 != 0, first & 0x07);
let mut digits = String::with_capacity(octets.len() * 2);
let mut faults = Faults::new();
digits.push(digit(first >> 4)?);
if !odd && rest.is_empty() {
return Err(Error::length(member, "octets", 1, 2, usize::MAX));
}
for (index, octet) in rest.iter().enumerate() {
let (low, high) = (octet & 0x0f, octet >> 4);
digits.push(digit(low)?);
if odd || index + 1 < rest.len() {
digits.push(digit(high)?);
} else if high != FILLER {
faults.push(Error::new(member, Fault::EndMark { given: high }));
}
}
Ok(((kind, digits), faults))
}
pub(crate) fn decimal(
member: &'static str,
text: &str,
min: usize,
max: usize,
) -> Result<(), Error> {
if let Some(given) = text.chars().find(|character| !character.is_ascii_digit()) {
let expected = "decimal digits";
return Err(Error::new(member, Fault::Character { given, expected }));
}
match (min..=max).contains(&text.len()) {
true => Ok(()),
false => Err(Error::length(member, "digits", text.len(), min, max)),
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Decimal<const N: usize> {
digits: [u8; N],
length: u8,
}
impl<const N: usize> Decimal<N> {
fn new(member: &'static str, text: &str, min: usize) -> Result<Self, Error> {
decimal(member, text, min, N)?;
let mut digits = [b'0'; N];
digits[..text.len()].copy_from_slice(text.as_bytes());
Ok(Self {
digits,
length: text.len() as u8,
})
}
fn as_str(&self) -> &str {
core::str::from_utf8(&self.digits[..usize::from(self.length)]).unwrap_or_default()
}
}
#[cfg(feature = "alloc")]
fn read<T>(
member: &'static str,
type_of_identity: u8,
octets: &[u8],
new: impl FnOnce(&str) -> Result<T, Error>,
) -> Result<Tolerated<T>, Error> {
let ((kind, digits), faults) = decode_tolerant(octets).map_err(|error| error.of(member))?;
if kind != type_of_identity & 0x07 {
let fault = Fault::Octet {
given: octets[0],
expected: octets[0] & 0xf8 | type_of_identity & 0x07,
};
return Err(Error::new(member, fault));
}
Ok((new(&digits)?, faults.of(member)))
}
#[cfg(feature = "alloc")]
fn strict<T>(read: Tolerated<T>) -> Result<T, Error> {
match read.1.first() {
Some(fault) => Err(*fault),
None => Ok(read.0),
}
}
macro_rules! digits_identity {
($name:ident, $member:literal, $min:literal, $max:literal, $what:literal) => {
impl $name {
#[doc = concat!("The digits of the ", $what, ".")]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[doc = concat!("The octets of the ", $what, " as the identity digits of a mobile")]
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_nas_octets(&self, type_of_identity: u8) -> Result<Vec<u8>, Error> {
encode(type_of_identity, self.as_str())
}
#[doc = concat!("The ", $what, " that `octets` hold as the identity digits of a")]
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn from_nas_octets(type_of_identity: u8, octets: &[u8]) -> Result<Self, Error> {
strict(Self::from_nas_octets_tolerant(type_of_identity, octets)?)
}
#[doc = concat!("Octets that are no ", $what, " however they are read.")]
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn from_nas_octets_tolerant(
type_of_identity: u8,
octets: &[u8],
) -> Result<Tolerated<Self>, Error> {
read($member, type_of_identity, octets, Self::new)
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, concat!(stringify!($name), "({})"), self.as_str())
}
}
impl FromStr for $name {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Error> {
Self::new(text)
}
}
};
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Imsi(Decimal<15>);
impl Imsi {
pub fn new(digits: &str) -> Result<Self, Error> {
Decimal::new("imsi", digits, 6).map(Self)
}
pub fn plmn(&self, mnc_digits: usize) -> Result<Plmn, Error> {
let digits = self.as_str();
let mnc = digits.get(3..3 + mnc_digits).unwrap_or_default();
Plmn::new(&digits[..3], mnc)
}
pub fn msin(&self, mnc_digits: usize) -> Result<&str, Error> {
self.plmn(mnc_digits)?;
Ok(&self.as_str()[3 + mnc_digits..])
}
}
digits_identity!(Imsi, "imsi", 6, 15, "IMSI");
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Imei(Decimal<15>);
impl Imei {
pub fn new(digits: &str) -> Result<Self, Error> {
Decimal::new("imei", digits, 15).map(Self)
}
pub fn with_spare_digit(tac_snr: &str) -> Result<Self, Error> {
Self::with_last(tac_snr, |_| 0)
}
pub fn with_check_digit(tac_snr: &str) -> Result<Self, Error> {
Self::with_last(tac_snr, check_digit)
}
fn with_last(tac_snr: &str, last: impl FnOnce(&[u8]) -> u8) -> Result<Self, Error> {
decimal("tac-snr", tac_snr, 14, 14)?;
let mut digits = [b'0'; 15];
digits[..14].copy_from_slice(tac_snr.as_bytes());
digits[14] = b'0' + last(tac_snr.as_bytes());
Ok(Self(Decimal { digits, length: 15 }))
}
pub fn tac(&self) -> &str {
&self.as_str()[..8]
}
pub fn snr(&self) -> &str {
&self.as_str()[8..14]
}
pub fn has_check_digit(&self) -> bool {
let digits = self.as_str().as_bytes();
digits[14] - b'0' == check_digit(&digits[..14])
}
}
fn check_digit(tac_snr: &[u8]) -> u8 {
let mut sum = 0;
for (index, digit) in tac_snr.iter().enumerate() {
let digit = digit - b'0';
sum += match index % 2 {
0 => digit,
_ => digit * 2 / 10 + digit * 2 % 10,
};
}
(10 - sum % 10) % 10
}
digits_identity!(Imei, "imei", 15, 15, "IMEI");
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Imeisv(Decimal<16>);
impl Imeisv {
pub fn new(digits: &str) -> Result<Self, Error> {
Decimal::new("imeisv", digits, 16).map(Self)
}
pub fn tac(&self) -> &str {
&self.as_str()[..8]
}
pub fn snr(&self) -> &str {
&self.as_str()[8..14]
}
pub fn svn(&self) -> &str {
&self.as_str()[14..]
}
}
digits_identity!(Imeisv, "imeisv", 16, 16, "IMEISV");