use crate::error::{NetError, NetResult};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::time::{SystemTime, UNIX_EPOCH};
pub const PTP_VERSION: u8 = 2;
pub const PTP_DOMAIN_DEFAULT: u8 = 127;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PtpMessageType {
Sync = 0x0,
DelayReq = 0x1,
PdelayReq = 0x2,
PdelayResp = 0x3,
FollowUp = 0x8,
DelayResp = 0x9,
PdelayRespFollowUp = 0xA,
Announce = 0xB,
Signaling = 0xC,
Management = 0xD,
}
impl PtpMessageType {
pub fn from_u8(value: u8) -> NetResult<Self> {
match value {
0x0 => Ok(Self::Sync),
0x1 => Ok(Self::DelayReq),
0x2 => Ok(Self::PdelayReq),
0x3 => Ok(Self::PdelayResp),
0x8 => Ok(Self::FollowUp),
0x9 => Ok(Self::DelayResp),
0xA => Ok(Self::PdelayRespFollowUp),
0xB => Ok(Self::Announce),
0xC => Ok(Self::Signaling),
0xD => Ok(Self::Management),
_ => Err(NetError::protocol(format!(
"Invalid PTP message type: {value}"
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PtpTimestamp {
pub seconds: u64,
pub nanoseconds: u32,
}
impl PtpTimestamp {
#[must_use]
pub const fn new(seconds: u64, nanoseconds: u32) -> Self {
Self {
seconds,
nanoseconds,
}
}
#[must_use]
pub fn from_nanos(nanos: u64) -> Self {
Self {
seconds: nanos / 1_000_000_000,
nanoseconds: (nanos % 1_000_000_000) as u32,
}
}
#[must_use]
pub const fn to_nanos(&self) -> u64 {
self.seconds * 1_000_000_000 + self.nanoseconds as u64
}
#[must_use]
pub fn now() -> Self {
let system_time = SystemTime::now();
let duration = system_time
.duration_since(UNIX_EPOCH)
.expect("invariant: system time is after UNIX_EPOCH");
let tai_offset_seconds = 37;
let total_seconds = duration.as_secs() + tai_offset_seconds;
let nanos = duration.subsec_nanos();
Self::new(total_seconds, nanos)
}
pub fn parse(cursor: &mut &[u8]) -> NetResult<Self> {
if cursor.len() < 10 {
return Err(NetError::parse(0, "Not enough data for PTP timestamp"));
}
let seconds_hi = u64::from(cursor.get_u16());
let seconds_lo = u64::from(cursor.get_u32());
let seconds = (seconds_hi << 32) | seconds_lo;
let nanoseconds = cursor.get_u32();
Ok(Self::new(seconds, nanoseconds))
}
pub fn serialize(&self, buf: &mut BytesMut) {
let seconds_hi = ((self.seconds >> 32) & 0xFFFF) as u16;
let seconds_lo = (self.seconds & 0xFFFFFFFF) as u32;
buf.put_u16(seconds_hi);
buf.put_u32(seconds_lo);
buf.put_u32(self.nanoseconds);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClockIdentity([u8; 8]);
impl ClockIdentity {
#[must_use]
pub const fn new(bytes: [u8; 8]) -> Self {
Self(bytes)
}
#[must_use]
pub fn from_mac(mac: [u8; 6]) -> Self {
let mut id = [0u8; 8];
id[0..3].copy_from_slice(&mac[0..3]);
id[3..5].copy_from_slice(&[0xFF, 0xFE]);
id[5..8].copy_from_slice(&mac[3..6]);
Self(id)
}
pub fn parse(cursor: &mut &[u8]) -> NetResult<Self> {
if cursor.len() < 8 {
return Err(NetError::parse(0, "Not enough data for clock identity"));
}
let mut bytes = [0u8; 8];
cursor.copy_to_slice(&mut bytes);
Ok(Self(bytes))
}
pub fn serialize(&self, buf: &mut BytesMut) {
buf.put_slice(&self.0);
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 8] {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PortIdentity {
pub clock_identity: ClockIdentity,
pub port_number: u16,
}
impl PortIdentity {
#[must_use]
pub const fn new(clock_identity: ClockIdentity, port_number: u16) -> Self {
Self {
clock_identity,
port_number,
}
}
pub fn parse(cursor: &mut &[u8]) -> NetResult<Self> {
let clock_identity = ClockIdentity::parse(cursor)?;
if cursor.len() < 2 {
return Err(NetError::parse(0, "Not enough data for port number"));
}
let port_number = cursor.get_u16();
Ok(Self::new(clock_identity, port_number))
}
pub fn serialize(&self, buf: &mut BytesMut) {
self.clock_identity.serialize(buf);
buf.put_u16(self.port_number);
}
}
#[derive(Debug, Clone)]
pub struct PtpHeader {
pub message_type: PtpMessageType,
pub message_length: u16,
pub domain_number: u8,
pub flags: u16,
pub correction_field: i64,
pub source_port_identity: PortIdentity,
pub sequence_id: u16,
pub control: u8,
pub log_message_interval: i8,
}
impl PtpHeader {
pub const SIZE: usize = 34;
#[must_use]
#[allow(clippy::too_many_arguments)]
pub const fn new(
message_type: PtpMessageType,
message_length: u16,
domain_number: u8,
flags: u16,
correction_field: i64,
source_port_identity: PortIdentity,
sequence_id: u16,
control: u8,
log_message_interval: i8,
) -> Self {
Self {
message_type,
message_length,
domain_number,
flags,
correction_field,
source_port_identity,
sequence_id,
control,
log_message_interval,
}
}
pub fn parse(data: &[u8]) -> NetResult<Self> {
if data.len() < Self::SIZE {
return Err(NetError::parse(0, "PTP header too short"));
}
let mut cursor = &data[..];
let byte0 = cursor.get_u8();
let message_type = PtpMessageType::from_u8(byte0 & 0x0F)?;
let byte1 = cursor.get_u8();
let version = byte1 & 0x0F;
if version != PTP_VERSION {
return Err(NetError::protocol(format!(
"Invalid PTP version: {version}"
)));
}
let message_length = cursor.get_u16();
let domain_number = cursor.get_u8();
let _reserved = cursor.get_u8();
let flags = cursor.get_u16();
let correction_field = cursor.get_i64();
let _reserved2 = cursor.get_u32();
let source_port_identity = PortIdentity::parse(&mut cursor)?;
let sequence_id = cursor.get_u16();
let control = cursor.get_u8();
let log_message_interval = cursor.get_i8();
Ok(Self::new(
message_type,
message_length,
domain_number,
flags,
correction_field,
source_port_identity,
sequence_id,
control,
log_message_interval,
))
}
pub fn serialize(&self, buf: &mut BytesMut) {
buf.put_u8((self.message_type as u8) & 0x0F);
buf.put_u8(PTP_VERSION & 0x0F);
buf.put_u16(self.message_length);
buf.put_u8(self.domain_number);
buf.put_u8(0);
buf.put_u16(self.flags);
buf.put_i64(self.correction_field);
buf.put_u32(0);
self.source_port_identity.serialize(buf);
buf.put_u16(self.sequence_id);
buf.put_u8(self.control);
buf.put_i8(self.log_message_interval);
}
}
#[derive(Debug, Clone)]
pub struct PtpSync {
pub header: PtpHeader,
pub origin_timestamp: PtpTimestamp,
}
impl PtpSync {
pub const SIZE: usize = 44;
pub fn parse(data: &[u8]) -> NetResult<Self> {
if data.len() < Self::SIZE {
return Err(NetError::parse(0, "PTP Sync message too short"));
}
let header = PtpHeader::parse(data)?;
let mut cursor = &data[PtpHeader::SIZE..];
let origin_timestamp = PtpTimestamp::parse(&mut cursor)?;
Ok(Self {
header,
origin_timestamp,
})
}
pub fn serialize(&self) -> Bytes {
let mut buf = BytesMut::with_capacity(Self::SIZE);
self.header.serialize(&mut buf);
self.origin_timestamp.serialize(&mut buf);
buf.freeze()
}
}
#[derive(Debug, Clone)]
pub struct PtpFollowUp {
pub header: PtpHeader,
pub precise_origin_timestamp: PtpTimestamp,
}
impl PtpFollowUp {
pub const SIZE: usize = 44;
pub fn parse(data: &[u8]) -> NetResult<Self> {
if data.len() < Self::SIZE {
return Err(NetError::parse(0, "PTP Follow_Up message too short"));
}
let header = PtpHeader::parse(data)?;
let mut cursor = &data[PtpHeader::SIZE..];
let precise_origin_timestamp = PtpTimestamp::parse(&mut cursor)?;
Ok(Self {
header,
precise_origin_timestamp,
})
}
pub fn serialize(&self) -> Bytes {
let mut buf = BytesMut::with_capacity(Self::SIZE);
self.header.serialize(&mut buf);
self.precise_origin_timestamp.serialize(&mut buf);
buf.freeze()
}
}
#[derive(Debug, Clone)]
pub struct PtpDelayReq {
pub header: PtpHeader,
pub origin_timestamp: PtpTimestamp,
}
impl PtpDelayReq {
pub const SIZE: usize = 44;
pub fn serialize(&self) -> Bytes {
let mut buf = BytesMut::with_capacity(Self::SIZE);
self.header.serialize(&mut buf);
self.origin_timestamp.serialize(&mut buf);
buf.freeze()
}
}
#[derive(Debug, Clone)]
pub struct PtpDelayResp {
pub header: PtpHeader,
pub receive_timestamp: PtpTimestamp,
pub requesting_port_identity: PortIdentity,
}
impl PtpDelayResp {
pub const SIZE: usize = 54;
pub fn parse(data: &[u8]) -> NetResult<Self> {
if data.len() < Self::SIZE {
return Err(NetError::parse(0, "PTP Delay_Resp message too short"));
}
let header = PtpHeader::parse(data)?;
let mut cursor = &data[PtpHeader::SIZE..];
let receive_timestamp = PtpTimestamp::parse(&mut cursor)?;
let requesting_port_identity = PortIdentity::parse(&mut cursor)?;
Ok(Self {
header,
receive_timestamp,
requesting_port_identity,
})
}
}
#[derive(Debug, Clone)]
pub struct PtpClock {
pub port_identity: PortIdentity,
pub domain_number: u8,
pub offset_ns: i64,
pub delay_ns: u64,
pub sync_sequence_id: u16,
pub delay_sequence_id: u16,
pub master_identity: Option<PortIdentity>,
}
impl PtpClock {
#[must_use]
pub fn new(port_identity: PortIdentity, domain_number: u8) -> Self {
Self {
port_identity,
domain_number,
offset_ns: 0,
delay_ns: 0,
sync_sequence_id: 0,
delay_sequence_id: 0,
master_identity: None,
}
}
pub fn process_sync(&mut self, sync: &PtpSync, receive_time: PtpTimestamp) {
self.master_identity = Some(sync.header.source_port_identity);
let t1 = sync.origin_timestamp.to_nanos() as i64;
let t2 = receive_time.to_nanos() as i64;
self.offset_ns = t2 - t1;
}
pub fn process_follow_up(&mut self, follow_up: &PtpFollowUp, sync_receive_time: PtpTimestamp) {
let t1 = follow_up.precise_origin_timestamp.to_nanos() as i64;
let t2 = sync_receive_time.to_nanos() as i64;
self.offset_ns = t2 - t1 - (self.delay_ns as i64 / 2);
}
pub fn process_delay_resp(
&mut self,
delay_resp: &PtpDelayResp,
delay_req_send_time: PtpTimestamp,
) {
let t3 = delay_req_send_time.to_nanos();
let t4 = delay_resp.receive_timestamp.to_nanos();
self.delay_ns = if t4 > t3 { t4 - t3 } else { 0 };
}
#[must_use]
pub fn create_delay_req(&mut self) -> PtpDelayReq {
let sequence_id = self.delay_sequence_id;
self.delay_sequence_id = self.delay_sequence_id.wrapping_add(1);
let header = PtpHeader::new(
PtpMessageType::DelayReq,
PtpDelayReq::SIZE as u16,
self.domain_number,
0,
0,
self.port_identity,
sequence_id,
1,
0x7F, );
PtpDelayReq {
header,
origin_timestamp: PtpTimestamp::now(),
}
}
#[must_use]
pub fn synchronized_time(&self) -> PtpTimestamp {
let local_time = PtpTimestamp::now();
let adjusted_nanos = if self.offset_ns >= 0 {
local_time.to_nanos() + (self.offset_ns as u64)
} else {
local_time
.to_nanos()
.saturating_sub(self.offset_ns.unsigned_abs())
};
PtpTimestamp::from_nanos(adjusted_nanos)
}
#[must_use]
pub fn is_synchronized(&self) -> bool {
self.master_identity.is_some() && self.offset_ns.abs() < 1_000_000 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ptp_timestamp() {
let ts = PtpTimestamp::new(1000, 500_000_000);
assert_eq!(ts.to_nanos(), 1_000_500_000_000);
let ts2 = PtpTimestamp::from_nanos(1_000_500_000_000);
assert_eq!(ts2.seconds, 1000);
assert_eq!(ts2.nanoseconds, 500_000_000);
}
#[test]
fn test_clock_identity() {
let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
let clock_id = ClockIdentity::from_mac(mac);
let bytes = clock_id.as_bytes();
assert_eq!(bytes[0], 0x00);
assert_eq!(bytes[1], 0x11);
assert_eq!(bytes[2], 0x22);
assert_eq!(bytes[3], 0xFF);
assert_eq!(bytes[4], 0xFE);
assert_eq!(bytes[5], 0x33);
assert_eq!(bytes[6], 0x44);
assert_eq!(bytes[7], 0x55);
}
#[test]
fn test_ptp_header_parse() {
let mut buf = BytesMut::new();
buf.put_u8(0x00); buf.put_u8(0x02); buf.put_u16(44); buf.put_u8(127); buf.put_u8(0); buf.put_u16(0); buf.put_i64(0); buf.put_u32(0);
buf.put_slice(&[0; 8]); buf.put_u16(1);
buf.put_u16(100); buf.put_u8(0); buf.put_i8(0);
let header = PtpHeader::parse(&buf).expect("should succeed in test");
assert_eq!(header.message_type, PtpMessageType::Sync);
assert_eq!(header.domain_number, 127);
assert_eq!(header.sequence_id, 100);
}
#[test]
fn test_ptp_clock() {
let clock_id = ClockIdentity::new([0; 8]);
let port_id = PortIdentity::new(clock_id, 1);
let mut clock = PtpClock::new(port_id, 127);
assert!(!clock.is_synchronized());
let delay_req = clock.create_delay_req();
assert_eq!(delay_req.header.message_type, PtpMessageType::DelayReq);
}
}