use crate::error::PfcpError;
use crate::ie::{Ie, IeType};
use std::net::{Ipv4Addr, Ipv6Addr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalIngressTunnel {
pub choose: bool,
pub port: Option<u16>,
pub ipv4: Option<Ipv4Addr>,
pub ipv6: Option<Ipv6Addr>,
}
impl LocalIngressTunnel {
pub fn choose() -> Self {
Self {
choose: true,
port: None,
ipv4: None,
ipv6: None,
}
}
pub fn new_ipv4(port: u16, addr: Ipv4Addr) -> Self {
Self {
choose: false,
port: Some(port),
ipv4: Some(addr),
ipv6: None,
}
}
pub fn new_ipv6(port: u16, addr: Ipv6Addr) -> Self {
Self {
choose: false,
port: Some(port),
ipv4: None,
ipv6: Some(addr),
}
}
pub fn new_dual_stack(port: u16, ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
Self {
choose: false,
port: Some(port),
ipv4: Some(ipv4),
ipv6: Some(ipv6),
}
}
pub fn marshal(&self) -> Vec<u8> {
let mut flags = 0u8;
if self.ipv4.is_some() {
flags |= 0x01;
}
if self.ipv6.is_some() {
flags |= 0x02;
}
if self.choose {
flags |= 0x04;
}
let mut data = vec![flags];
if !self.choose {
if let Some(port) = self.port {
data.extend_from_slice(&port.to_be_bytes());
}
if let Some(ip) = self.ipv4 {
data.extend_from_slice(&ip.octets());
}
if let Some(ip) = self.ipv6 {
data.extend_from_slice(&ip.octets());
}
}
data
}
pub fn unmarshal(data: &[u8]) -> Result<Self, PfcpError> {
if data.is_empty() {
return Err(PfcpError::invalid_length(
"Local Ingress Tunnel",
IeType::LocalIngressTunnel,
1,
0,
));
}
let flags = data[0];
let v4 = (flags & 0x01) != 0;
let v6 = (flags & 0x02) != 0;
let ch = (flags & 0x04) != 0;
if ch {
return Ok(Self {
choose: true,
port: None,
ipv4: None,
ipv6: None,
});
}
let expected = 1 + 2 + (v4 as usize * 4) + (v6 as usize * 16);
if data.len() < expected {
return Err(PfcpError::invalid_length(
"Local Ingress Tunnel",
IeType::LocalIngressTunnel,
expected,
data.len(),
));
}
let port = u16::from_be_bytes(data[1..3].try_into().unwrap());
let mut offset = 3;
let ipv4 = if v4 {
let mut octets = [0u8; 4];
octets.copy_from_slice(&data[offset..offset + 4]);
offset += 4;
Some(Ipv4Addr::from(octets))
} else {
None
};
let ipv6 = if v6 {
let mut octets = [0u8; 16];
octets.copy_from_slice(&data[offset..offset + 16]);
Some(Ipv6Addr::from(octets))
} else {
None
};
Ok(Self {
choose: false,
port: Some(port),
ipv4,
ipv6,
})
}
pub fn to_ie(&self) -> Ie {
Ie::new(IeType::LocalIngressTunnel, self.marshal())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_marshal_unmarshal_choose() {
let ie = LocalIngressTunnel::choose();
let parsed = LocalIngressTunnel::unmarshal(&ie.marshal()).unwrap();
assert_eq!(parsed, ie);
assert!(parsed.choose);
}
#[test]
fn test_marshal_unmarshal_ipv4() {
let ie = LocalIngressTunnel::new_ipv4(5004, Ipv4Addr::new(10, 0, 0, 1));
let parsed = LocalIngressTunnel::unmarshal(&ie.marshal()).unwrap();
assert_eq!(parsed, ie);
}
#[test]
fn test_marshal_unmarshal_ipv6() {
let ie = LocalIngressTunnel::new_ipv6(5004, Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
let parsed = LocalIngressTunnel::unmarshal(&ie.marshal()).unwrap();
assert_eq!(parsed, ie);
}
#[test]
fn test_marshal_unmarshal_dual_stack() {
let ie = LocalIngressTunnel::new_dual_stack(
8080,
Ipv4Addr::new(192, 168, 1, 1),
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
);
let parsed = LocalIngressTunnel::unmarshal(&ie.marshal()).unwrap();
assert_eq!(parsed, ie);
}
#[test]
fn test_choose_flag_byte() {
let ie = LocalIngressTunnel::choose();
assert_eq!(ie.marshal(), [0x04]); }
#[test]
fn test_unmarshal_empty() {
assert!(matches!(
LocalIngressTunnel::unmarshal(&[]),
Err(PfcpError::InvalidLength { .. })
));
}
#[test]
fn test_unmarshal_short_explicit() {
assert!(matches!(
LocalIngressTunnel::unmarshal(&[0x01]),
Err(PfcpError::InvalidLength { .. })
));
}
#[test]
fn test_to_ie() {
let ie = LocalIngressTunnel::new_ipv4(1234, Ipv4Addr::LOCALHOST).to_ie();
assert_eq!(ie.ie_type, IeType::LocalIngressTunnel);
assert_eq!(ie.payload.len(), 7); }
}