#![cfg(target_os = "linux")]
use std::{
collections::HashMap,
sync::{Arc, RwLock, atomic::AtomicUsize},
task::Wake,
time::Duration,
};
use timestamped_socket::{
interface::{InterfaceName, lookup_phc},
socket::{SendTimestampToken, TimestampData},
};
use std::io::Result;
use crate::wake::ListWaker;
mod addresses;
mod interface;
mod socket;
mod wake;
use addresses::BoundInterface;
pub use addresses::PtpAddressFamily;
pub use interface::Interface;
pub use socket::{ConnectedSocket, OpenSocket, RecvResult};
pub const MAX_PACKET_SIZE: usize = 4096;
pub const PACKET_BUFFER_SIZE: usize = 16;
pub const CACHED_TIMESTAMPS: usize = 32;
pub const TIMESTAMP_FETCH_TIMEOUT: Duration = Duration::from_millis(1000);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum TimestampingClock {
System,
Hardware {
phc: Option<u32>,
},
}
#[derive(Clone)]
pub struct NetworkManager<A: PtpAddressFamily>(Arc<NetworkManagerData<A>>);
struct InterfaceData<BoundInterface> {
interface: BoundInterface,
hardware_clock: Option<u32>,
refcount: usize,
send_wakers: Arc<ListWaker>,
ts_wakers: Arc<ListWaker>,
previous_timestamps: [RwLock<Option<(SendTimestampToken, TimestampData)>>; CACHED_TIMESTAMPS],
timestamp_counter: RwLock<usize>,
}
struct SocketData<A: PtpAddressFamily> {
remote_filter: Option<A>,
local_filter: Option<A>,
interface_filter: Option<InterfaceName>,
timestamp_source: TimestampSource,
message_channel: tokio::sync::mpsc::Sender<RecvResult<A>>,
}
struct NetworkManagerData<A: PtpAddressFamily> {
interfaces: RwLock<HashMap<Option<InterfaceName>, InterfaceData<A::BoundInterface>>>,
read_wakers: Arc<ListWaker>,
sockets: RwLock<HashMap<usize, SocketData<A>>>,
next_socket_id: AtomicUsize,
}
impl<A: PtpAddressFamily> NetworkManager<A> {
pub fn new() -> Result<Self> {
let mut interfaces = HashMap::new();
interfaces.insert(
None,
InterfaceData {
interface: A::BoundInterface::open(None, None)?,
hardware_clock: None,
refcount: 1,
send_wakers: Arc::default(),
ts_wakers: Arc::default(),
previous_timestamps: Default::default(),
timestamp_counter: RwLock::new(0),
},
);
Ok(NetworkManager(Arc::new(NetworkManagerData {
interfaces: RwLock::new(interfaces),
read_wakers: Arc::default(),
next_socket_id: AtomicUsize::new(0),
sockets: RwLock::default(),
})))
}
}
impl<A: PtpAddressFamily> NetworkManager<A> {
#[must_use]
#[expect(
clippy::missing_panics_doc,
reason = "Function will only panic if there is an implementation bug in this crate."
)]
pub fn open_general(&self) -> Interface<A> {
let mut interfaces = self.0.interfaces.write().unwrap();
let entry = interfaces.get_mut(&None).unwrap();
entry.refcount = entry.refcount.checked_add(1).unwrap();
Interface {
state: self.0.clone(),
name: None,
timestamp_source: TimestampSource::System,
}
}
#[expect(
clippy::missing_panics_doc,
reason = "Function will only panic if there is an implementation bug in this crate."
)]
pub fn open_interface(
&self,
interface: InterfaceName,
clock: TimestampingClock,
) -> Result<Interface<A>> {
let clock_idx = match clock {
TimestampingClock::System => None,
TimestampingClock::Hardware { phc: None } => {
Some(lookup_phc(interface).ok_or(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Hardware timestamping requested, but not available on interface.",
))?)
}
TimestampingClock::Hardware { phc: Some(phc) } => Some(phc),
};
let mut interfaces = self.0.interfaces.write().unwrap();
if let Some(entry) = interfaces.get_mut(&Some(interface)) {
if clock_idx.is_some() && entry.hardware_clock != clock_idx {
return Err(std::io::Error::other(
"Interface already in use with different hardware clock.",
));
}
entry.refcount = entry.refcount.checked_add(1).unwrap();
} else {
interfaces.insert(
Some(interface),
InterfaceData {
interface: A::BoundInterface::open(Some(interface), clock_idx)?,
hardware_clock: clock_idx,
refcount: 1,
send_wakers: Arc::default(),
ts_wakers: Arc::default(),
previous_timestamps: Default::default(),
timestamp_counter: RwLock::new(0),
},
);
self.0.read_wakers.wake_by_ref();
}
Ok(Interface {
state: self.0.clone(),
name: Some(interface),
timestamp_source: match clock {
TimestampingClock::System => TimestampSource::System,
TimestampingClock::Hardware { .. } => TimestampSource::Hardware,
},
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TimestampSource {
System,
Hardware,
}
#[cfg(all(test, feature = "privileged_tests"))]
mod test;