use super::{Error, Fault, Imsi};
#[cfg(feature = "alloc")]
use alloc::string::String;
use core::fmt;
#[cfg(feature = "alloc")]
use core::str::FromStr;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SupiType {
Imsi,
NetworkSpecific,
Gli,
Gci,
}
impl SupiType {
pub fn type_number(self) -> u8 {
match self {
Self::Imsi => 0,
Self::NetworkSpecific => 1,
Self::Gli => 2,
Self::Gci => 3,
}
}
pub fn from_type_number(number: u8) -> Result<Self, Error> {
match number {
0 => Ok(Self::Imsi),
1 => Ok(Self::NetworkSpecific),
2 => Ok(Self::Gli),
3 => Ok(Self::Gci),
given => Err(Error::new(
"supi-type",
Fault::Range {
given: given.into(),
max: 3,
},
)),
}
}
pub fn nas_format(self) -> u8 {
match self {
Self::Imsi => 0,
Self::NetworkSpecific => 1,
Self::Gci => 2,
Self::Gli => 3,
}
}
pub fn from_nas_format(format: u8) -> Result<Self, Error> {
match format {
0 => Ok(Self::Imsi),
1 => Ok(Self::NetworkSpecific),
2 => Ok(Self::Gci),
3 => Ok(Self::Gli),
given => Err(Error::new(
"supi-format",
Fault::Range {
given: given.into(),
max: 3,
},
)),
}
}
pub fn prefix(self) -> &'static str {
match self {
Self::Imsi => "imsi-",
Self::NetworkSpecific => "nai-",
Self::Gli => "gli-",
Self::Gci => "gci-",
}
}
}
const LONGEST_NAI: usize = 253 + 1 + 253;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Supi<'a> {
kind: SupiType,
identity: Text<'a>,
}
#[derive(Clone)]
enum Text<'a> {
Borrowed(&'a str),
#[cfg(feature = "alloc")]
Owned(String),
}
impl Text<'_> {
fn as_str(&self) -> &str {
match self {
Self::Borrowed(text) => text,
#[cfg(feature = "alloc")]
Self::Owned(text) => text,
}
}
}
impl PartialEq for Text<'_> {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for Text<'_> {}
impl PartialOrd for Text<'_> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Text<'_> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl core::hash::Hash for Text<'_> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<'a> Supi<'a> {
pub fn imsi(imsi: &'a str) -> Result<Self, Error> {
Imsi::new(imsi)?;
Ok(Self {
kind: SupiType::Imsi,
identity: Text::Borrowed(imsi),
})
}
pub fn network_specific(nai: &'a str) -> Result<Self, Error> {
Self::nai(SupiType::NetworkSpecific, nai)
}
pub fn gli(nai: &'a str) -> Result<Self, Error> {
Self::nai(SupiType::Gli, nai)
}
pub fn gci(nai: &'a str) -> Result<Self, Error> {
Self::nai(SupiType::Gci, nai)
}
fn nai(kind: SupiType, nai: &'a str) -> Result<Self, Error> {
if nai.len() > LONGEST_NAI {
return Err(Error::length("nai", "octets", nai.len(), 3, LONGEST_NAI));
}
match nai.rsplit_once('@') {
Some((_, realm)) if !realm.is_empty() => Ok(Self {
kind,
identity: Text::Borrowed(nai),
}),
_ => {
let expected = "a username, @ and a realm, as user@example.com";
Err(Error::new("nai", Fault::Form { expected }))
}
}
}
pub fn parse(text: &'a str) -> Result<Self, Error> {
let kinds = [
SupiType::Imsi,
SupiType::NetworkSpecific,
SupiType::Gli,
SupiType::Gci,
];
for kind in kinds {
if let Some(identity) = text.strip_prefix(kind.prefix()) {
return match kind {
SupiType::Imsi => Self::imsi(identity),
kind => Self::nai(kind, identity),
};
}
}
let expected = "imsi-, nai-, gli- or gci-, then the identity, as imsi-208930000000001";
Err(Error::new("supi", Fault::Form { expected }))
}
pub fn supi_type(&self) -> SupiType {
self.kind
}
pub fn identity(&self) -> &str {
self.identity.as_str()
}
pub fn as_imsi(&self) -> Option<Imsi> {
match self.kind {
SupiType::Imsi => Imsi::new(self.identity()).ok(),
_ => None,
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn into_owned(self) -> Supi<'static> {
Supi {
kind: self.kind,
identity: Text::Owned(String::from(self.identity())),
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<Imsi> for Supi<'static> {
fn from(imsi: Imsi) -> Self {
Self {
kind: SupiType::Imsi,
identity: Text::Owned(String::from(imsi.as_str())),
}
}
}
impl fmt::Display for Supi<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.kind.prefix(), self.identity())
}
}
impl fmt::Debug for Supi<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Supi({self})")
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl FromStr for Supi<'static> {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Error> {
Supi::parse(text).map(Supi::into_owned)
}
}