use super::digits::decimal;
#[cfg(feature = "alloc")]
use super::nai::{MAC_TAG, PROFILE_A_KEY, PROFILE_B_KEY};
use super::plmn::FILLER;
use super::{Error, Fault};
#[cfg(feature = "alloc")]
use super::{Faults, Imsi, Plmn, Tolerated, tbcd};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
use core::fmt;
use core::str::FromStr;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProtectionScheme(u8);
impl ProtectionScheme {
pub const NULL: Self = Self(0);
pub const PROFILE_A: Self = Self(1);
pub const PROFILE_B: Self = Self(2);
pub fn new(identifier: u8) -> Result<Self, Error> {
let member = "protection-scheme";
match identifier {
0..=2 | 12..=15 => Ok(Self(identifier)),
3..=11 => {
let expected = "0, 1, 2 or 12 to 15: 3 to 11 are reserved";
let given = identifier.into();
Err(Error::new(member, Fault::Value { given, expected }))
}
given => {
let (given, max) = (given.into(), 15);
Err(Error::new(member, Fault::Range { given, max }))
}
}
}
pub fn identifier(self) -> u8 {
self.0
}
pub fn is_null(self) -> bool {
self.0 == 0
}
pub fn is_of_the_operator(self) -> bool {
self.0 >= 12
}
}
impl fmt::Debug for ProtectionScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self.0 {
0 => "null",
1 => "profile A",
2 => "profile B",
_ => "of the operator",
};
write!(f, "ProtectionScheme({}: {name})", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RoutingIndicator {
digits: [u8; 4],
length: u8,
}
impl RoutingIndicator {
pub fn new(digits: &str) -> Result<Self, Error> {
decimal("routing-indicator", digits, 1, 4)?;
let mut all = [b'0'; 4];
all[..digits.len()].copy_from_slice(digits.as_bytes());
Ok(Self {
digits: all,
length: digits.len() as u8,
})
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.digits[..usize::from(self.length)]).unwrap_or_default()
}
pub fn to_nas_octets(&self) -> [u8; 2] {
let half = |index: usize| match index < usize::from(self.length) {
true => self.digits[index] - b'0',
false => FILLER,
};
[half(1) << 4 | half(0), half(3) << 4 | half(2)]
}
pub fn from_nas_octets(octets: [u8; 2]) -> Result<Self, Error> {
let member = "routing-indicator";
let halves = [
octets[0] & 0x0f,
octets[0] >> 4,
octets[1] & 0x0f,
octets[1] >> 4,
];
let length = halves.iter().take_while(|half| **half <= 9).count();
if length == 0 {
return Err(Error::new(member, Fault::Digit { given: halves[0] }));
}
if let Some(given) = halves[length..].iter().find(|half| **half != FILLER) {
return Err(Error::new(member, Fault::EndMark { given: *given }));
}
let mut digits = [b'0'; 4];
for (digit, half) in digits.iter_mut().zip(&halves[..length]) {
*digit = b'0' + half;
}
Ok(Self {
digits,
length: length as u8,
})
}
}
impl Default for RoutingIndicator {
fn default() -> Self {
Self {
digits: [b'0'; 4],
length: 1,
}
}
}
impl fmt::Display for RoutingIndicator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for RoutingIndicator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RoutingIndicator({self})")
}
}
impl FromStr for RoutingIndicator {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Error> {
Self::new(text)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ImsiSuci {
plmn: Plmn,
routing_indicator: RoutingIndicator,
scheme: ProtectionScheme,
key_id: u8,
output: Vec<u8>,
}
#[cfg(feature = "alloc")]
impl ImsiSuci {
pub fn null(
plmn: Plmn,
routing_indicator: RoutingIndicator,
msin: &str,
) -> Result<Self, Error> {
decimal("msin", msin, 1, 15 - plmn.digits().len())?;
Ok(Self {
plmn,
routing_indicator,
scheme: ProtectionScheme::NULL,
key_id: 0,
output: msin.as_bytes().to_vec(),
})
}
pub fn concealed(
plmn: Plmn,
routing_indicator: RoutingIndicator,
scheme: ProtectionScheme,
key_id: u8,
output: Vec<u8>,
) -> Result<Self, Error> {
let suci = Self::of_any_output(plmn, routing_indicator, scheme, key_id, output)?;
match suci.output_length() {
Some(fault) => Err(fault),
None => Ok(suci),
}
}
fn of_any_output(
plmn: Plmn,
routing_indicator: RoutingIndicator,
scheme: ProtectionScheme,
key_id: u8,
output: Vec<u8>,
) -> Result<Self, Error> {
if scheme.is_null() {
let expected = "a scheme that conceals: 1, 2 or 12 to 15";
return Err(Error::new(
"protection-scheme",
Fault::Value { given: 0, expected },
));
}
if matches!(key_id, 0 | 255) {
let expected = "1 to 254 for a scheme that conceals";
let given = key_id.into();
let fault = Fault::Value { given, expected };
return Err(Error::new("home-network-public-key-identifier", fault));
}
Ok(Self {
plmn,
routing_indicator,
scheme,
key_id,
output,
})
}
fn output_length(&self) -> Option<Error> {
let input = (15 - self.plmn.digits().len()).div_ceil(2);
let (shortest, longest) = match self.scheme {
ProtectionScheme::PROFILE_A => {
(PROFILE_A_KEY + 1 + MAC_TAG, PROFILE_A_KEY + input + MAC_TAG)
}
ProtectionScheme::PROFILE_B => {
(PROFILE_B_KEY + 1 + MAC_TAG, PROFILE_B_KEY + input + MAC_TAG)
}
_ => (1, 3000 + input),
};
let length = self.output.len();
let unit = "octets";
(!(shortest..=longest).contains(&length))
.then(|| Error::length("scheme-output", unit, length, shortest, longest))
}
pub fn of(imsi: Imsi, mnc_digits: usize) -> Result<Self, Error> {
Self::null(
imsi.plmn(mnc_digits)?,
RoutingIndicator::default(),
imsi.msin(mnc_digits)?,
)
}
pub fn plmn(&self) -> Plmn {
self.plmn
}
pub fn routing_indicator(&self) -> RoutingIndicator {
self.routing_indicator
}
pub fn protection_scheme(&self) -> ProtectionScheme {
self.scheme
}
pub fn home_network_public_key_identifier(&self) -> u8 {
self.key_id
}
pub fn msin(&self) -> Option<&str> {
match self.scheme.is_null() {
true => core::str::from_utf8(&self.output).ok(),
false => None,
}
}
pub fn concealed_output(&self) -> Option<&[u8]> {
(!self.scheme.is_null()).then_some(&self.output[..])
}
pub fn imsi(&self) -> Option<Imsi> {
let mut digits = String::from(self.plmn.digits());
digits.push_str(self.msin()?);
Imsi::new(&digits).ok()
}
pub fn to_nas_octets(&self) -> Vec<u8> {
let mut octets = Vec::with_capacity(8 + self.output.len());
octets.push(0x01);
octets.extend_from_slice(&self.plmn.to_nas_octets());
octets.extend_from_slice(&self.routing_indicator.to_nas_octets());
octets.extend_from_slice(&[self.scheme.0, self.key_id]);
match self.msin() {
Some(msin) => octets.extend(tbcd::encode(msin).unwrap_or_default()),
None => octets.extend_from_slice(&self.output),
}
octets
}
pub fn from_nas_octets(octets: &[u8]) -> Result<Self, Error> {
let (suci, faults) = Self::from_nas_octets_tolerant(octets)?;
match faults.first() {
Some(fault) => Err(*fault),
None => Ok(suci),
}
}
pub fn from_nas_octets_tolerant(octets: &[u8]) -> Result<Tolerated<Self>, Error> {
let member = "suci";
let [first, p1, p2, p3, r1, r2, scheme, key_id, output @ ..] = octets else {
return Err(Error::length(member, "octets", octets.len(), 9, usize::MAX));
};
if output.is_empty() {
return Err(Error::length(member, "octets", octets.len(), 9, usize::MAX));
}
let mut faults = Faults::new();
let (format, kind) = ((first >> 4) & 0x07, first & 0x07);
if kind != 1 || matches!(format, 1..=3) {
let fault = Fault::Octet {
given: *first,
expected: 0x01,
};
return Err(Error::new(member, fault));
}
if format != 0 {
let expected = "0, the SUPI format of an IMSI";
let given = format.into();
faults.push(Error::new("supi-format", Fault::Value { given, expected }));
}
if first & 0x88 != 0 {
faults.push(Error::new(member, Fault::Spare { given: *first }));
}
if scheme & 0xf0 != 0 {
faults.push(Error::new(
"protection-scheme",
Fault::Spare { given: *scheme },
));
}
let plmn = Plmn::from_nas_octets([*p1, *p2, *p3])?;
let routing_indicator = RoutingIndicator::from_nas_octets([*r1, *r2])?;
let scheme = ProtectionScheme::new(scheme & 0x0f)?;
let suci = match scheme.is_null() {
true => {
if *key_id != 0 {
let expected = "0 for the null scheme";
let given = (*key_id).into();
let fault = Fault::Value { given, expected };
return Err(Error::new("home-network-public-key-identifier", fault));
}
let msin = tbcd::decode(output).map_err(|error| error.of("msin"))?;
Self::null(plmn, routing_indicator, &msin)?
}
false => {
let output = output.to_vec();
let suci = Self::of_any_output(plmn, routing_indicator, scheme, *key_id, output)?;
if let Some(fault) = suci.output_length() {
faults.push(fault);
}
suci
}
};
Ok((suci, faults))
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for ImsiSuci {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (plmn, routing, scheme, key) = (
self.plmn,
self.routing_indicator,
self.scheme.0,
self.key_id,
);
write!(f, "suci-0-{plmn}-{routing}-{scheme:x}-{key}-")?;
match self.msin() {
Some(msin) => f.write_str(msin),
None => self
.output
.iter()
.try_for_each(|octet| write!(f, "{octet:02x}")),
}
}
}
#[cfg(feature = "alloc")]
impl fmt::Debug for ImsiSuci {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ImsiSuci({self})")
}
}
#[cfg(feature = "alloc")]
impl FromStr for ImsiSuci {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Error> {
let expected = "suci-0-, the MCC, the MNC, the routing indicator, the scheme as one \
hexadecimal digit, the key identifier in decimal and the scheme output, \
as suci-0-208-93-0-0-0-0000000001";
let form = Error::new("suci", Fault::Form { expected });
let rest = text.strip_prefix("suci-0-").ok_or(form)?;
let mut parts = rest.splitn(6, '-');
let mut part = || parts.next().ok_or(form);
let plmn = Plmn::new(part()?, part()?)?;
let routing_indicator = RoutingIndicator::new(part()?)?;
let scheme = match part()?.as_bytes() {
[digit] => (*digit as char).to_digit(16).ok_or(form)?,
_ => return Err(form),
};
let scheme = ProtectionScheme::new(scheme as u8)?;
let key_id = part()?;
let decimal = key_id.bytes().all(|digit| digit.is_ascii_digit())
&& (key_id == "0" || !key_id.starts_with('0'));
let key_id = match decimal {
true => key_id.parse::<u8>().map_err(|_| form)?,
false => return Err(form),
};
let output = part()?;
if scheme.is_null() {
if key_id != 0 {
let expected = "0 for the null scheme";
let given = key_id.into();
let fault = Fault::Value { given, expected };
return Err(Error::new("home-network-public-key-identifier", fault));
}
return Self::null(plmn, routing_indicator, output);
}
let hexadecimal =
output.len() % 2 == 0 && output.bytes().all(|digit| digit.is_ascii_hexdigit());
if !hexadecimal {
return Err(form);
}
let octets = (0..output.len()).step_by(2);
let octets = octets.map(|at| u8::from_str_radix(&output[at..at + 2], 16).map_err(|_| form));
Self::concealed(
plmn,
routing_indicator,
scheme,
key_id,
octets.collect::<Result<_, _>>()?,
)
}
}