use crate::error::PfcpError;
use crate::ie::{Ie, IeType};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApnDnn {
pub name: String,
}
impl ApnDnn {
pub fn new(name: String) -> Self {
ApnDnn { name }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn len(&self) -> usize {
self.encoded_name().len()
}
pub fn is_empty(&self) -> bool {
self.name.is_empty()
}
fn encoded_name(&self) -> Vec<u8> {
if self.name.is_empty() {
return vec![0]; }
let mut encoded = Vec::new();
let labels: Vec<&str> = self.name.split('.').collect();
for label in labels {
if label.len() > 63 {
let truncated = &label[..63];
encoded.push(truncated.len() as u8);
encoded.extend_from_slice(truncated.as_bytes());
} else {
encoded.push(label.len() as u8);
encoded.extend_from_slice(label.as_bytes());
}
}
encoded.push(0);
encoded
}
fn decode_name(encoded: &[u8]) -> Result<String, PfcpError> {
if encoded.is_empty() {
return Ok(String::new());
}
let mut labels = Vec::new();
let mut offset = 0;
while offset < encoded.len() {
let label_len = encoded[offset] as usize;
offset += 1;
if label_len == 0 {
break;
}
if offset + label_len > encoded.len() {
return Err(PfcpError::invalid_value(
"APN/DNN",
"label_length",
"label length exceeds available data",
));
}
let label = String::from_utf8(encoded[offset..offset + label_len].to_vec())
.map_err(|_| PfcpError::invalid_value("APN/DNN", "label", "invalid UTF-8"))?;
labels.push(label);
offset += label_len;
}
Ok(labels.join("."))
}
pub fn marshal(&self) -> Vec<u8> {
self.encoded_name()
}
pub fn unmarshal(payload: &[u8]) -> Result<Self, PfcpError> {
let name = Self::decode_name(payload)?;
Ok(ApnDnn { name })
}
pub fn to_ie(&self) -> Ie {
Ie::new(IeType::ApnDnn, self.marshal())
}
}
impl FromStr for ApnDnn {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ApnDnn::new(s.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apn_dnn_marshal_unmarshal_simple() {
let apn_dnn = ApnDnn::new("internet".to_string());
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(apn_dnn, unmarshaled);
assert_eq!(unmarshaled.name(), "internet");
assert_eq!(
marshaled,
vec![8, b'i', b'n', b't', b'e', b'r', b'n', b'e', b't', 0]
);
assert_eq!(apn_dnn.len(), 10);
assert!(!apn_dnn.is_empty());
}
#[test]
fn test_apn_dnn_marshal_unmarshal_complex() {
let apn_dnn = ApnDnn::new("internet.mnc001.mcc001.gprs".to_string());
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(apn_dnn, unmarshaled);
assert_eq!(unmarshaled.name(), "internet.mnc001.mcc001.gprs");
let expected = vec![
8, b'i', b'n', b't', b'e', b'r', b'n', b'e', b't', 6, b'm', b'n', b'c', b'0', b'0', b'1', 6, b'm', b'c', b'c', b'0', b'0', b'1', 4, b'g', b'p', b'r', b's', 0, ];
assert_eq!(marshaled, expected);
}
#[test]
fn test_apn_dnn_marshal_unmarshal_empty() {
let apn_dnn = ApnDnn::new("".to_string());
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(apn_dnn, unmarshaled);
assert_eq!(unmarshaled.name(), "");
assert_eq!(marshaled, vec![0]);
assert!(apn_dnn.is_empty());
}
#[test]
fn test_apn_dnn_from_str() {
let apn_dnn = "ims".parse::<ApnDnn>().unwrap();
assert_eq!(apn_dnn.name(), "ims");
let marshaled = apn_dnn.marshal();
let expected = vec![3, b'i', b'm', b's', 0];
assert_eq!(marshaled, expected);
}
#[test]
fn test_apn_dnn_5g_dnn_examples() {
let test_cases = vec![
"internet",
"ims",
"xcap",
"mms",
"supl",
"internet.mnc123.mcc456.gprs",
"ims.mnc001.mcc001.gprs",
];
for name in test_cases {
let apn_dnn = name.parse::<ApnDnn>().unwrap();
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(apn_dnn, unmarshaled);
assert_eq!(unmarshaled.name(), name);
}
}
#[test]
fn test_apn_dnn_long_label() {
let long_label = "a".repeat(63);
let apn_dnn = ApnDnn::new(long_label.clone());
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(apn_dnn, unmarshaled);
assert_eq!(unmarshaled.name(), long_label);
}
#[test]
fn test_apn_dnn_very_long_label_truncation() {
let very_long_label = "a".repeat(100);
let apn_dnn = ApnDnn::new(very_long_label.clone());
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(unmarshaled.name(), "a".repeat(63));
}
#[test]
fn test_apn_dnn_to_ie() {
let apn_dnn = "test.network".parse::<ApnDnn>().unwrap();
let ie = apn_dnn.to_ie();
assert_eq!(ie.ie_type, IeType::ApnDnn);
let unmarshaled = ApnDnn::unmarshal(&ie.payload).unwrap();
assert_eq!(apn_dnn, unmarshaled);
}
#[test]
fn test_apn_dnn_unmarshal_invalid_label_length() {
let malformed = vec![10, b'a', b'b', b'c']; let result = ApnDnn::unmarshal(&malformed);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, PfcpError::InvalidValue { .. }));
}
#[test]
fn test_apn_dnn_unmarshal_invalid_utf8() {
let invalid_utf8 = vec![3, 0xFF, 0xFE, 0xFD, 0]; let result = ApnDnn::unmarshal(&invalid_utf8);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, PfcpError::InvalidValue { .. }));
}
#[test]
fn test_apn_dnn_single_label() {
let apn_dnn = "x".parse::<ApnDnn>().unwrap();
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(apn_dnn, unmarshaled);
assert_eq!(unmarshaled.name(), "x");
assert_eq!(marshaled, vec![1, b'x', 0]);
}
#[test]
fn test_apn_dnn_multiple_short_labels() {
let apn_dnn = "a.b.c.d.e".parse::<ApnDnn>().unwrap();
let marshaled = apn_dnn.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(apn_dnn, unmarshaled);
assert_eq!(unmarshaled.name(), "a.b.c.d.e");
let expected = vec![1, b'a', 1, b'b', 1, b'c', 1, b'd', 1, b'e', 0];
assert_eq!(marshaled, expected);
}
#[test]
fn test_apn_dnn_decode_name_empty() {
let result = ApnDnn::decode_name(&[]).unwrap();
assert_eq!(result, "");
}
#[test]
fn test_apn_dnn_decode_name_zero_only() {
let result = ApnDnn::decode_name(&[0]).unwrap();
assert_eq!(result, "");
}
#[test]
fn test_apn_dnn_round_trip_various_names() {
let test_cases = vec![
"",
"a",
"internet",
"ims.network",
"test.mnc123.mcc456.gprs",
"very.long.domain.name.with.many.labels.for.testing",
];
for name in test_cases {
let original = name.parse::<ApnDnn>().unwrap();
let marshaled = original.marshal();
let unmarshaled = ApnDnn::unmarshal(&marshaled).unwrap();
assert_eq!(original, unmarshaled);
}
}
}