use crate::error::PfcpError;
use crate::ie::IeType;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const NTP_EPOCH_OFFSET: u64 = 2208988800;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct RecoveryTimeStamp {
pub timestamp: SystemTime,
}
impl RecoveryTimeStamp {
pub fn new(timestamp: SystemTime) -> Self {
RecoveryTimeStamp { timestamp }
}
pub fn marshal(&self) -> [u8; 4] {
let unix_timestamp = self
.timestamp
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let ntp_timestamp = unix_timestamp + NTP_EPOCH_OFFSET;
(ntp_timestamp as u32).to_be_bytes()
}
pub fn to_ie(&self) -> crate::ie::Ie {
crate::ie::Ie::new(IeType::RecoveryTimeStamp, self.marshal().to_vec())
}
pub fn unmarshal(data: &[u8]) -> Result<Self, PfcpError> {
if data.len() < 4 {
return Err(PfcpError::invalid_length(
"Recovery Time Stamp",
IeType::RecoveryTimeStamp,
4,
data.len(),
));
}
let ntp_timestamp = u32::from_be_bytes(data[0..4].try_into().unwrap()) as u64;
if ntp_timestamp < NTP_EPOCH_OFFSET {
return Err(PfcpError::invalid_value(
"Recovery Time Stamp",
ntp_timestamp.to_string(),
"NTP timestamp is before Unix epoch",
));
}
let unix_timestamp = ntp_timestamp - NTP_EPOCH_OFFSET;
let timestamp = UNIX_EPOCH + Duration::from_secs(unix_timestamp);
Ok(RecoveryTimeStamp { timestamp })
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn test_recovery_time_stamp_marshal_unmarshal() {
let now = SystemTime::now();
let rts = RecoveryTimeStamp::new(now);
let marshaled = rts.marshal();
let unmarshaled = RecoveryTimeStamp::unmarshal(&marshaled).unwrap();
let original_secs = rts.timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs();
let unmarshaled_secs = unmarshaled
.timestamp
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
assert_eq!(original_secs, unmarshaled_secs);
}
#[test]
fn test_recovery_time_stamp_unmarshal_empty() {
let result = RecoveryTimeStamp::unmarshal(&[]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, PfcpError::InvalidLength { .. }));
assert!(err.to_string().contains("Recovery Time Stamp"));
}
#[test]
fn test_recovery_time_stamp_unmarshal_too_short() {
let result = RecoveryTimeStamp::unmarshal(&[0x01, 0x02]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, PfcpError::InvalidLength { .. }));
}
}