use crate::error::PfcpError;
use crate::ie::f_teid::Fteid;
use crate::ie::recovery_time_stamp::RecoveryTimeStamp;
use crate::ie::ue_ip_address::UeIpAddress;
use crate::ie::IeType;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SemanticMatch {
Match,
Mismatch { details: String },
}
impl SemanticMatch {
pub fn is_match(&self) -> bool {
matches!(self, SemanticMatch::Match)
}
pub fn details(&self) -> Option<&str> {
match self {
SemanticMatch::Match => None,
SemanticMatch::Mismatch { details } => Some(details),
}
}
}
pub fn compare_semantically(
ie_type: IeType,
left_payload: &[u8],
right_payload: &[u8],
) -> Result<Option<SemanticMatch>, PfcpError> {
compare_semantically_with_tolerance(ie_type, left_payload, right_payload, None)
}
pub fn compare_semantically_with_tolerance(
ie_type: IeType,
left_payload: &[u8],
right_payload: &[u8],
timestamp_tolerance_secs: Option<u32>,
) -> Result<Option<SemanticMatch>, PfcpError> {
match ie_type {
IeType::Fteid => {
let left = Fteid::unmarshal(left_payload)?;
let right = Fteid::unmarshal(right_payload)?;
Ok(Some(compare_fteid(&left, &right)))
}
IeType::UeIpAddress => {
let left = UeIpAddress::unmarshal(left_payload)?;
let right = UeIpAddress::unmarshal(right_payload)?;
Ok(Some(compare_ue_ip_address(&left, &right)))
}
IeType::RecoveryTimeStamp
| IeType::StartTime
| IeType::EndTime
| IeType::TimeOfFirstPacket
| IeType::TimeOfLastPacket
| IeType::ActivationTime
| IeType::DeactivationTime
| IeType::MonitoringTime => {
if let Some(tolerance_secs) = timestamp_tolerance_secs {
let left = RecoveryTimeStamp::unmarshal(left_payload)?;
let right = RecoveryTimeStamp::unmarshal(right_payload)?;
Ok(Some(compare_timestamp(&left, &right, tolerance_secs)))
} else {
Ok(None)
}
}
_ => Ok(None), }
}
fn compare_fteid(left: &Fteid, right: &Fteid) -> SemanticMatch {
if left.teid != right.teid {
return SemanticMatch::Mismatch {
details: format!("TEID differs: {} vs {}", left.teid, right.teid),
};
}
if left.ipv4_address != right.ipv4_address {
return SemanticMatch::Mismatch {
details: format!(
"IPv4 address differs: {:?} vs {:?}",
left.ipv4_address, right.ipv4_address
),
};
}
if left.ipv6_address != right.ipv6_address {
return SemanticMatch::Mismatch {
details: format!(
"IPv6 address differs: {:?} vs {:?}",
left.ipv6_address, right.ipv6_address
),
};
}
if left.ch != right.ch {
return SemanticMatch::Mismatch {
details: format!("CHOOSE flag differs: {} vs {}", left.ch, right.ch),
};
}
if left.chid != right.chid {
return SemanticMatch::Mismatch {
details: format!("CHOOSE ID flag differs: {} vs {}", left.chid, right.chid),
};
}
if left.chid && left.choose_id != right.choose_id {
return SemanticMatch::Mismatch {
details: format!(
"Choose ID differs: {} vs {}",
left.choose_id, right.choose_id
),
};
}
SemanticMatch::Match
}
fn compare_ue_ip_address(left: &UeIpAddress, right: &UeIpAddress) -> SemanticMatch {
if left.ipv4_address != right.ipv4_address {
return SemanticMatch::Mismatch {
details: format!(
"IPv4 address differs: {:?} vs {:?}",
left.ipv4_address, right.ipv4_address
),
};
}
if left.ipv6_address != right.ipv6_address {
return SemanticMatch::Mismatch {
details: format!(
"IPv6 address differs: {:?} vs {:?}",
left.ipv6_address, right.ipv6_address
),
};
}
SemanticMatch::Match
}
fn compare_timestamp(
left: &RecoveryTimeStamp,
right: &RecoveryTimeStamp,
tolerance_secs: u32,
) -> SemanticMatch {
let diff = match left.timestamp.duration_since(right.timestamp) {
Ok(duration) => duration,
Err(e) => e.duration(), };
let tolerance = Duration::from_secs(tolerance_secs as u64);
if diff <= tolerance {
SemanticMatch::Match
} else {
SemanticMatch::Mismatch {
details: format!(
"timestamps differ by {} seconds (tolerance: {} seconds)",
diff.as_secs(),
tolerance_secs
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
fn test_fteid_semantic_match_identical() {
let fteid1 = Fteid::new(
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let fteid2 = Fteid::new(
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let result = compare_fteid(&fteid1, &fteid2);
assert!(result.is_match());
}
#[test]
fn test_fteid_semantic_match_different_v4_flag_same_address() {
let fteid1 = Fteid::new(
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let mut fteid2 = Fteid::new(
false, false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)), None,
0,
);
fteid2.v4 = false;
let result = compare_fteid(&fteid1, &fteid2);
assert!(result.is_match());
}
#[test]
fn test_fteid_semantic_mismatch_different_teid() {
let fteid1 = Fteid::new(
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let fteid2 = Fteid::new(
true,
false,
0x87654321, Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let result = compare_fteid(&fteid1, &fteid2);
assert!(!result.is_match());
assert!(result.details().unwrap().contains("TEID differs"));
}
#[test]
fn test_fteid_semantic_mismatch_different_ipv4() {
let fteid1 = Fteid::new(
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let fteid2 = Fteid::new(
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 2)), None,
0,
);
let result = compare_fteid(&fteid1, &fteid2);
assert!(!result.is_match());
assert!(result.details().unwrap().contains("IPv4 address differs"));
}
#[test]
fn test_fteid_semantic_mismatch_different_choose_flag() {
let fteid1 = Fteid::new_with_choose(
true,
false,
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let fteid2 = Fteid::new_with_choose(
true,
false,
false, false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let result = compare_fteid(&fteid1, &fteid2);
assert!(!result.is_match());
assert!(result.details().unwrap().contains("CHOOSE flag differs"));
}
#[test]
fn test_ue_ip_address_semantic_match_identical() {
let ue1 = UeIpAddress::new(Some(Ipv4Addr::new(10, 0, 0, 1)), None);
let ue2 = UeIpAddress::new(Some(Ipv4Addr::new(10, 0, 0, 1)), None);
let result = compare_ue_ip_address(&ue1, &ue2);
assert!(result.is_match());
}
#[test]
fn test_ue_ip_address_semantic_match_dual_stack() {
let ue1 = UeIpAddress::new(
Some(Ipv4Addr::new(10, 0, 0, 1)),
Some(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
);
let ue2 = UeIpAddress::new(
Some(Ipv4Addr::new(10, 0, 0, 1)),
Some(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
);
let result = compare_ue_ip_address(&ue1, &ue2);
assert!(result.is_match());
}
#[test]
fn test_ue_ip_address_semantic_mismatch_different_ipv4() {
let ue1 = UeIpAddress::new(Some(Ipv4Addr::new(10, 0, 0, 1)), None);
let ue2 = UeIpAddress::new(Some(Ipv4Addr::new(10, 0, 0, 2)), None);
let result = compare_ue_ip_address(&ue1, &ue2);
assert!(!result.is_match());
assert!(result.details().unwrap().contains("IPv4 address differs"));
}
#[test]
fn test_ue_ip_address_semantic_mismatch_different_ipv6() {
let ue1 = UeIpAddress::new(None, Some(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)));
let ue2 = UeIpAddress::new(None, Some(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2)));
let result = compare_ue_ip_address(&ue1, &ue2);
assert!(!result.is_match());
assert!(result.details().unwrap().contains("IPv6 address differs"));
}
#[test]
fn test_compare_semantically_fteid() {
let fteid = Fteid::new(
true,
false,
0x12345678,
Some(Ipv4Addr::new(192, 168, 1, 1)),
None,
0,
);
let payload = fteid.marshal();
let result = compare_semantically(IeType::Fteid, &payload, &payload).unwrap();
assert!(result.is_some());
assert!(result.unwrap().is_match());
}
#[test]
fn test_compare_semantically_ue_ip_address() {
let ue = UeIpAddress::new(Some(Ipv4Addr::new(10, 0, 0, 1)), None);
let payload = ue.marshal();
let result = compare_semantically(IeType::UeIpAddress, &payload, &payload).unwrap();
assert!(result.is_some());
assert!(result.unwrap().is_match());
}
#[test]
fn test_compare_semantically_unsupported_type() {
let result = compare_semantically(IeType::Cause, &[0x01], &[0x01]).unwrap();
assert!(result.is_none());
}
#[test]
fn test_timestamp_tolerance_within_window() {
use std::time::{Duration, SystemTime};
let time1 = RecoveryTimeStamp::new(SystemTime::now());
let time2 = RecoveryTimeStamp::new(time1.timestamp + Duration::from_secs(3));
let result = compare_timestamp(&time1, &time2, 5);
assert!(result.is_match());
}
#[test]
fn test_timestamp_tolerance_outside_window() {
use std::time::{Duration, SystemTime};
let time1 = RecoveryTimeStamp::new(SystemTime::now());
let time2 = RecoveryTimeStamp::new(time1.timestamp + Duration::from_secs(10));
let result = compare_timestamp(&time1, &time2, 5);
assert!(!result.is_match());
assert!(result.details().unwrap().contains("differ by"));
}
#[test]
fn test_timestamp_tolerance_exact_match() {
use std::time::SystemTime;
let time = SystemTime::now();
let time1 = RecoveryTimeStamp::new(time);
let time2 = RecoveryTimeStamp::new(time);
let result = compare_timestamp(&time1, &time2, 0);
assert!(result.is_match());
}
#[test]
fn test_timestamp_tolerance_reverse_order() {
use std::time::{Duration, SystemTime};
let time1 = RecoveryTimeStamp::new(SystemTime::now());
let time2 = RecoveryTimeStamp::new(time1.timestamp - Duration::from_secs(3));
let result = compare_timestamp(&time1, &time2, 5);
assert!(result.is_match());
}
#[test]
fn test_compare_semantically_with_tolerance() {
use std::time::{Duration, SystemTime};
let time1 = RecoveryTimeStamp::new(SystemTime::now());
let time2 = RecoveryTimeStamp::new(time1.timestamp + Duration::from_secs(2));
let payload1 = time1.marshal();
let payload2 = time2.marshal();
let result = compare_semantically_with_tolerance(
IeType::RecoveryTimeStamp,
&payload1,
&payload2,
Some(5),
)
.unwrap();
assert!(result.is_some());
assert!(result.unwrap().is_match());
let result = compare_semantically_with_tolerance(
IeType::RecoveryTimeStamp,
&payload1,
&payload2,
None,
)
.unwrap();
assert!(result.is_none()); }
}