use crate::error::PfcpError;
use crate::ie::application_ids_pfds::ApplicationIdsPfds;
use crate::ie::node_id::NodeId;
use crate::ie::{Ie, IeType};
use crate::message::{header::Header, Message, MsgType};
use crate::types::{Seid, SequenceNumber};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PfdManagementRequest {
pub header: Header,
pub node_id: Option<NodeId>, pub application_ids_pfds: Option<Vec<ApplicationIdsPfds>>, pub ies: Vec<Ie>,
}
impl PfdManagementRequest {
pub fn new(
seq: impl Into<SequenceNumber>,
node_id: Option<NodeId>,
application_ids_pfds: Option<Vec<ApplicationIdsPfds>>,
ies: Vec<Ie>,
) -> Self {
let mut payload_len = 0;
if let Some(ref node_id) = node_id {
payload_len += node_id.to_ie().len();
}
if let Some(ref app_pfds) = application_ids_pfds {
for app_pfd in app_pfds {
payload_len += app_pfd.to_ie().len();
}
}
for ie in &ies {
payload_len += ie.len();
}
let mut header = Header::new(MsgType::PfdManagementRequest, false, 0, seq);
header.length = 4 + payload_len;
PfdManagementRequest {
header,
node_id,
application_ids_pfds,
ies,
}
}
}
impl Message for PfdManagementRequest {
fn marshal(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.marshaled_size());
self.marshal_into(&mut buf);
buf
}
fn marshal_into(&self, buf: &mut Vec<u8>) {
buf.reserve(self.marshaled_size());
self.header.marshal_into(buf);
if let Some(ref node_id) = self.node_id {
node_id.to_ie().marshal_into(buf);
}
if let Some(ref app_pfds) = self.application_ids_pfds {
for app_pfd in app_pfds {
app_pfd.to_ie().marshal_into(buf);
}
}
for ie in &self.ies {
ie.marshal_into(buf);
}
}
fn marshaled_size(&self) -> usize {
let mut size = self.header.len() as usize;
if let Some(ref node_id) = self.node_id {
size += node_id.to_ie().len() as usize;
}
if let Some(ref app_pfds) = self.application_ids_pfds {
for app_pfd in app_pfds {
size += app_pfd.to_ie().len() as usize;
}
}
for ie in &self.ies {
size += ie.len() as usize;
}
size
}
fn unmarshal(data: &[u8]) -> Result<Self, PfcpError> {
let header = Header::unmarshal(data)?;
let mut node_id = None;
let mut application_ids_pfds = None;
let mut ies = Vec::new();
let mut offset = header.len() as usize;
while offset < data.len() {
let ie = Ie::unmarshal(&data[offset..])?;
let ie_len = ie.len() as usize;
match ie.ie_type {
IeType::NodeId => {
if node_id.is_some() {
return Err(PfcpError::MessageParseError {
message_type: Some(MsgType::PfdManagementRequest),
reason: "Duplicate Node ID IE".to_string(),
});
}
node_id = Some(NodeId::unmarshal(&ie.payload)?);
}
IeType::ApplicationIdsPfds => {
let typed_ie = ApplicationIdsPfds::unmarshal(&ie.payload)?;
application_ids_pfds
.get_or_insert(Vec::new())
.push(typed_ie);
}
_ => ies.push(ie),
}
offset += ie_len;
}
Ok(PfdManagementRequest {
header,
node_id,
application_ids_pfds,
ies,
})
}
fn msg_type(&self) -> MsgType {
MsgType::PfdManagementRequest
}
fn seid(&self) -> Option<Seid> {
if self.header.has_seid {
Some(self.header.seid)
} else {
None
}
}
fn sequence(&self) -> SequenceNumber {
self.header.sequence_number
}
fn set_sequence(&mut self, seq: SequenceNumber) {
self.header.sequence_number = seq;
}
fn ies(&self, ie_type: IeType) -> crate::message::IeIter<'_> {
use crate::message::IeIter;
match ie_type {
IeType::NodeId => IeIter::single(None, ie_type), IeType::ApplicationIdsPfds => IeIter::single(None, ie_type), _ => IeIter::generic(&self.ies, ie_type),
}
}
fn all_ies(&self) -> Vec<&Ie> {
self.ies.iter().collect()
}
}
#[derive(Debug, Default)]
pub struct PfdManagementRequestBuilder {
sequence: SequenceNumber,
node_id: Option<NodeId>,
application_ids_pfds: Option<Vec<ApplicationIdsPfds>>,
ies: Vec<Ie>,
}
impl PfdManagementRequestBuilder {
pub fn new(sequence: impl Into<SequenceNumber>) -> Self {
Self {
sequence: sequence.into(),
node_id: None,
application_ids_pfds: None,
ies: Vec::new(),
}
}
pub fn node_id(mut self, node_id: NodeId) -> Self {
self.node_id = Some(node_id);
self
}
pub fn application_ids_pfds(mut self, application_ids_pfds: ApplicationIdsPfds) -> Self {
self.application_ids_pfds
.get_or_insert(Vec::new())
.push(application_ids_pfds);
self
}
pub fn application_ids_pfds_vec(
mut self,
application_ids_pfds: Vec<ApplicationIdsPfds>,
) -> Self {
if let Some(existing) = &mut self.application_ids_pfds {
existing.extend(application_ids_pfds);
} else {
self.application_ids_pfds = Some(application_ids_pfds);
}
self
}
pub fn ie(mut self, ie: Ie) -> Self {
self.ies.push(ie);
self
}
pub fn ies(mut self, mut ies: Vec<Ie>) -> Self {
self.ies.append(&mut ies);
self
}
pub fn build(self) -> PfdManagementRequest {
PfdManagementRequest::new(
self.sequence,
self.node_id,
self.application_ids_pfds,
self.ies,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ie::application_id::ApplicationId;
use crate::ie::pfd_contents::PfdContents;
use crate::ie::pfd_context::PfdContext;
use std::net::Ipv4Addr;
#[test]
fn test_pfd_management_request_builder_minimal() {
let request = PfdManagementRequestBuilder::new(12345).build();
assert_eq!(*request.sequence(), 12345);
assert_eq!(request.msg_type(), MsgType::PfdManagementRequest);
assert!(request.node_id.is_none());
assert!(request.application_ids_pfds.is_none());
assert!(request.ies.is_empty());
}
#[test]
fn test_pfd_management_request_builder_with_application_ids_pfds() {
let app_id = ApplicationId::new("test.app");
let pfd_contents1 =
PfdContents::flow_description("permit out ip from any to 192.168.1.0/24").unwrap();
let pfd_contents2 =
PfdContents::flow_and_url("permit in tcp from any to any port 443", "https://test.app")
.unwrap();
let pfd_context = PfdContext::new(vec![pfd_contents1, pfd_contents2]);
let app_ids_pfds = ApplicationIdsPfds::new(app_id, pfd_context);
let request = PfdManagementRequestBuilder::new(12345)
.application_ids_pfds(app_ids_pfds.clone())
.build();
assert_eq!(*request.sequence(), 12345);
assert!(request.node_id.is_none());
assert!(request.application_ids_pfds.is_some());
let app_pfds = request.application_ids_pfds.as_ref().unwrap();
assert_eq!(app_pfds.len(), 1);
assert_eq!(app_pfds[0], app_ids_pfds);
assert!(request.ies.is_empty());
}
#[test]
fn test_pfd_management_request_builder_with_multiple_application_ids_pfds() {
let app_id1 = ApplicationId::new("app1.test");
let app_id2 = ApplicationId::new("app2.test");
let app_id3 = ApplicationId::new("app3.test");
let pfd_context1 = PfdContext::new(vec![
PfdContents::flow_description("permit out tcp from any to any port 80").unwrap(),
PfdContents::domain_name("app1.test.com").unwrap(),
]);
let pfd_context2 = PfdContext::new(vec![
PfdContents::url("https://app2.test.com/api").unwrap(),
PfdContents::builder()
.flow_description("permit in udp from any to any port 443")
.domain_name("app2.test.com")
.build()
.unwrap(),
]);
let pfd_context3 = PfdContext::new(vec![
PfdContents::domain_and_protocol("app3.test.com", "https").unwrap(),
PfdContents::builder()
.custom_pfd_content("custom application detection rule")
.add_additional_url("https://cdn.app3.test.com")
.add_additional_url("https://api.app3.test.com")
.build()
.unwrap(),
]);
let app_pfds1 = ApplicationIdsPfds::new(app_id1, pfd_context1);
let app_pfds2 = ApplicationIdsPfds::new(app_id2, pfd_context2);
let app_pfds3 = ApplicationIdsPfds::new(app_id3, pfd_context3);
let request = PfdManagementRequestBuilder::new(98765)
.application_ids_pfds(app_pfds1.clone())
.application_ids_pfds_vec(vec![app_pfds2.clone(), app_pfds3.clone()])
.build();
assert_eq!(*request.sequence(), 98765);
assert!(request.node_id.is_none());
assert!(request.application_ids_pfds.is_some());
let app_pfds = request.application_ids_pfds.as_ref().unwrap();
assert_eq!(app_pfds.len(), 3);
assert_eq!(app_pfds[0], app_pfds1);
assert_eq!(app_pfds[1], app_pfds2);
assert_eq!(app_pfds[2], app_pfds3);
assert!(request.ies.is_empty());
}
#[test]
fn test_pfd_management_request_builder_with_other_ies() {
let ie1 = Ie::new(IeType::Unknown, vec![0xAA, 0xBB]);
let ie2 = Ie::new(IeType::Unknown, vec![0xCC, 0xDD]);
let ie3 = Ie::new(IeType::Unknown, vec![0xEE, 0xFF]);
let request = PfdManagementRequestBuilder::new(55555)
.ie(ie1.clone())
.ies(vec![ie2.clone(), ie3.clone()])
.build();
assert_eq!(*request.sequence(), 55555);
assert!(request.node_id.is_none());
assert!(request.application_ids_pfds.is_none());
assert_eq!(request.ies.len(), 3);
assert_eq!(request.ies[0], ie1);
assert_eq!(request.ies[1], ie2);
assert_eq!(request.ies[2], ie3);
}
#[test]
fn test_pfd_management_request_builder_full() {
let node_id = NodeId::new_ipv4(Ipv4Addr::new(192, 168, 1, 100));
let app_id = ApplicationId::new("full.test.app");
let pfd_context = PfdContext::new(vec![
PfdContents::builder()
.flow_description("permit out tcp from any to any port 80")
.url("https://full.test.app")
.domain_name("full.test.app")
.build()
.unwrap(),
PfdContents::builder()
.custom_pfd_content("signature: full_test_app_v1.0")
.domain_name_protocol("https")
.add_additional_flow_description("permit in tcp from any to any port 443")
.add_additional_flow_description("permit out udp from any to any port 53")
.add_additional_url("https://api.full.test.app")
.add_additional_domain_name_and_protocol("cdn.full.test.app:https")
.build()
.unwrap(),
]);
let app_ids_pfds = ApplicationIdsPfds::new(app_id, pfd_context);
let other_ie1 = Ie::new(IeType::Unknown, vec![0xAA, 0xBB]);
let other_ie2 = Ie::new(IeType::Unknown, vec![0xCC, 0xDD]);
let request = PfdManagementRequestBuilder::new(77777)
.node_id(node_id.clone())
.application_ids_pfds(app_ids_pfds.clone())
.ie(other_ie1.clone())
.ie(other_ie2.clone())
.build();
assert_eq!(*request.sequence(), 77777);
assert_eq!(request.node_id, Some(node_id));
assert!(request.application_ids_pfds.is_some());
let app_pfds = request.application_ids_pfds.as_ref().unwrap();
assert_eq!(app_pfds.len(), 1);
assert_eq!(app_pfds[0], app_ids_pfds);
assert_eq!(request.ies.len(), 2);
assert_eq!(request.ies[0], other_ie1);
assert_eq!(request.ies[1], other_ie2);
}
#[test]
fn test_pfd_management_request_builder_roundtrip() {
let node_id = NodeId::new_ipv4(Ipv4Addr::new(10, 0, 0, 1));
let app_id1 = ApplicationId::new("app1.roundtrip");
let app_id2 = ApplicationId::new("app2.roundtrip");
let pfd_context1 = PfdContext::new(vec![PfdContents::flow_and_url(
"permit out tcp from any to app1.roundtrip",
"https://app1.roundtrip",
)
.unwrap()]);
let pfd_context2 = PfdContext::new(vec![
PfdContents::domain_and_protocol("app2.roundtrip", "https").unwrap(),
PfdContents::builder()
.flow_description("permit in tcp from any to any port 8080")
.add_additional_flow_description("permit out udp from any to any port 1234")
.build()
.unwrap(),
]);
let app_pfds1 = ApplicationIdsPfds::new(app_id1, pfd_context1);
let app_pfds2 = ApplicationIdsPfds::new(app_id2, pfd_context2);
let other_ie = Ie::new(IeType::Unknown, vec![0xFF, 0xEE, 0xDD]);
let original = PfdManagementRequestBuilder::new(99999)
.node_id(node_id)
.application_ids_pfds(app_pfds1)
.application_ids_pfds(app_pfds2)
.ie(other_ie)
.build();
let marshaled = original.marshal();
let unmarshaled = PfdManagementRequest::unmarshal(&marshaled).unwrap();
assert_eq!(original, unmarshaled);
}
#[test]
fn test_pfd_management_request_with_node_id_only() {
let node_id = NodeId::new_ipv4(Ipv4Addr::new(172, 16, 0, 1));
let request = PfdManagementRequestBuilder::new(11111)
.node_id(node_id.clone())
.build();
assert_eq!(*request.sequence(), 11111);
assert_eq!(request.node_id, Some(node_id));
assert!(request.application_ids_pfds.is_none());
assert!(request.ies.is_empty());
}
#[test]
fn test_pfd_management_request_type_safe_access() {
let node_id = NodeId::new_ipv4(Ipv4Addr::new(203, 0, 113, 1));
let app_id = ApplicationId::new("type.safe.app");
let pfd_context = PfdContext::new(vec![PfdContents::builder()
.flow_description("permit out tcp from any to any port 80")
.url("https://type.safe.app")
.domain_name("type.safe.app")
.custom_pfd_content("application_signature_v2.1")
.domain_name_protocol("https")
.add_additional_flow_description("permit in tcp from any to any port 443")
.add_additional_url("https://api.type.safe.app")
.add_additional_domain_name_and_protocol("cdn.type.safe.app:https")
.build()
.unwrap()]);
let app_ids_pfds = ApplicationIdsPfds::new(app_id, pfd_context);
let other_ie = Ie::new(IeType::Unknown, vec![0x01, 0x02, 0x03]);
let request = PfdManagementRequestBuilder::new(55555)
.node_id(node_id.clone())
.application_ids_pfds(app_ids_pfds.clone())
.ie(other_ie.clone())
.build();
assert_eq!(request.node_id, Some(node_id));
assert!(request.application_ids_pfds.is_some());
let app_pfds = request.application_ids_pfds.as_ref().unwrap();
assert_eq!(app_pfds.len(), 1);
assert_eq!(app_pfds[0], app_ids_pfds);
let pfd_context = &app_pfds[0].pfd_context;
assert_eq!(pfd_context.pfd_contents.len(), 1);
let pfd_content = &pfd_context.pfd_contents[0];
assert_eq!(pfd_content.flags, 0xFF); assert!(pfd_content.flow_description.is_some());
assert!(pfd_content.url.is_some());
assert!(pfd_content.domain_name.is_some());
assert_eq!(request.ies.len(), 1);
assert_eq!(request.ies[0], other_ie);
assert_eq!(request.ies(IeType::NodeId).next(), None);
assert_eq!(request.ies(IeType::ApplicationIdsPfds).next(), None);
assert_eq!(request.ies(IeType::Unknown).next(), Some(&other_ie));
}
}