use crate::error::PfcpError;
use crate::ie::{Ie, IeType};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GroupId {
pub value: Vec<u8>,
}
impl GroupId {
pub fn new(value: Vec<u8>) -> Self {
GroupId { value }
}
pub fn new_uuid(uuid_bytes: [u8; 16]) -> Self {
GroupId {
value: uuid_bytes.to_vec(),
}
}
pub fn new_from_hex(hex_str: &str) -> Result<Self, PfcpError> {
let hex_clean = hex_str.replace("-", "").replace(" ", "");
if !hex_clean.len().is_multiple_of(2) {
return Err(PfcpError::invalid_value(
"Group ID",
hex_str.to_string(),
"Hex string must have even length",
));
}
let mut bytes = Vec::new();
for i in (0..hex_clean.len()).step_by(2) {
let byte_str = &hex_clean[i..i + 2];
let byte = u8::from_str_radix(byte_str, 16).map_err(|_| {
PfcpError::invalid_value("Group ID", hex_str.to_string(), "Invalid hex string")
})?;
bytes.push(byte);
}
Ok(GroupId { value: bytes })
}
pub fn value(&self) -> &[u8] {
&self.value
}
pub fn to_hex(&self) -> String {
self.value
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>()
}
pub fn to_uuid_string(&self) -> Option<String> {
if self.value.len() == 16 {
Some(format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
self.value[0], self.value[1], self.value[2], self.value[3],
self.value[4], self.value[5],
self.value[6], self.value[7],
self.value[8], self.value[9],
self.value[10], self.value[11], self.value[12], self.value[13], self.value[14], self.value[15]
))
} else {
None
}
}
pub fn marshal(&self) -> Vec<u8> {
self.value.clone()
}
pub fn unmarshal(data: &[u8]) -> Result<Self, PfcpError> {
if data.is_empty() {
return Err(PfcpError::invalid_length("Group ID", IeType::GroupId, 1, 0));
}
Ok(GroupId {
value: data.to_vec(),
})
}
pub fn to_ie(&self) -> Ie {
Ie::new(IeType::GroupId, self.marshal())
}
pub fn len(&self) -> usize {
self.value.len()
}
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
pub fn is_uuid(&self) -> bool {
self.value.len() == 16
}
}
impl From<Vec<u8>> for GroupId {
fn from(value: Vec<u8>) -> Self {
GroupId::new(value)
}
}
impl From<[u8; 16]> for GroupId {
fn from(uuid: [u8; 16]) -> Self {
GroupId::new_uuid(uuid)
}
}
impl From<GroupId> for Vec<u8> {
fn from(group_id: GroupId) -> Self {
group_id.value
}
}
impl AsRef<[u8]> for GroupId {
fn as_ref(&self) -> &[u8] {
&self.value
}
}
impl std::fmt::Display for GroupId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(uuid_str) = self.to_uuid_string() {
write!(f, "{}", uuid_str)
} else {
write!(f, "{}", self.to_hex())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_group_id_basic() {
let data = vec![0x01, 0x02, 0x03, 0x04];
let group_id = GroupId::new(data.clone());
assert_eq!(group_id.value(), &data);
assert_eq!(group_id.len(), 4);
assert!(!group_id.is_empty());
assert!(!group_id.is_uuid());
}
#[test]
fn test_group_id_uuid() {
let uuid_bytes = [
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
];
let group_id = GroupId::new_uuid(uuid_bytes);
assert_eq!(group_id.len(), 16);
assert!(group_id.is_uuid());
let uuid_str = group_id.to_uuid_string().unwrap();
assert_eq!(uuid_str, "12345678-9abc-def0-1122-334455667788");
}
#[test]
fn test_group_id_from_hex() {
let hex_str = "123456789abcdef0";
let group_id = GroupId::new_from_hex(hex_str).unwrap();
assert_eq!(
group_id.value(),
&[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]
);
let uuid_hex = "12345678-9abc-def0-1122-334455667788";
let group_id = GroupId::new_from_hex(uuid_hex).unwrap();
assert_eq!(group_id.len(), 16);
assert!(group_id.is_uuid());
}
#[test]
fn test_group_id_from_hex_errors() {
let result = GroupId::new_from_hex("123");
assert!(result.is_err());
let result = GroupId::new_from_hex("12zz");
assert!(result.is_err());
}
#[test]
fn test_group_id_to_hex() {
let data = vec![0xde, 0xad, 0xbe, 0xef];
let group_id = GroupId::new(data);
assert_eq!(group_id.to_hex(), "deadbeef");
}
#[test]
fn test_group_id_marshal_unmarshal() {
let original_data = vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
let group_id = GroupId::new(original_data.clone());
let marshaled = group_id.marshal();
assert_eq!(marshaled, original_data);
let unmarshaled = GroupId::unmarshal(&marshaled).unwrap();
assert_eq!(unmarshaled, group_id);
}
#[test]
fn test_group_id_unmarshal_errors() {
let result = GroupId::unmarshal(&[]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, PfcpError::InvalidLength { .. }));
assert!(err.to_string().contains("Group ID"));
}
#[test]
fn test_group_id_to_ie() {
let data = vec![0xaa, 0xbb, 0xcc, 0xdd];
let group_id = GroupId::new(data.clone());
let ie = group_id.to_ie();
assert_eq!(ie.ie_type, IeType::GroupId);
assert_eq!(ie.payload, data);
}
#[test]
fn test_group_id_display() {
let uuid_bytes = [
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
];
let group_id = GroupId::new_uuid(uuid_bytes);
let display_str = format!("{}", group_id);
assert!(display_str.contains("-"));
let data = vec![0x01, 0x02, 0x03];
let group_id = GroupId::new(data);
let display_str = format!("{}", group_id);
assert_eq!(display_str, "010203"); }
#[test]
fn test_group_id_conversions() {
let data = vec![0x01, 0x02, 0x03, 0x04];
let group_id: GroupId = data.clone().into();
assert_eq!(group_id.value(), &data);
let back_to_vec: Vec<u8> = group_id.into();
assert_eq!(back_to_vec, data);
}
#[test]
fn test_group_id_round_trip() {
let test_cases = vec![
vec![0x01],
vec![0x01, 0x02, 0x03, 0x04],
vec![
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
], vec![0xff; 32], ];
for data in test_cases {
let original = GroupId::new(data.clone());
let marshaled = original.marshal();
let unmarshaled = GroupId::unmarshal(&marshaled).unwrap();
assert_eq!(original, unmarshaled);
assert_eq!(unmarshaled.value(), &data);
}
}
#[test]
fn test_group_id_uuid_string_format() {
let uuid_bytes = [
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
];
let group_id = GroupId::new_uuid(uuid_bytes);
let uuid_str = group_id.to_uuid_string().unwrap();
let parts: Vec<&str> = uuid_str.split('-').collect();
assert_eq!(parts.len(), 5);
assert_eq!(parts[0].len(), 8);
assert_eq!(parts[1].len(), 4);
assert_eq!(parts[2].len(), 4);
assert_eq!(parts[3].len(), 4);
assert_eq!(parts[4].len(), 12);
}
}