use std::{
io::{self, IoSliceMut},
os::fd::RawFd,
time::SystemTime,
};
use nix::{
errno::Errno,
sys::socket::{
cmsg_space, recvmsg, ControlMessageOwned, MsgFlags, TimestampingFlag, Timestamps,
},
};
use super::system_time_from_timespec;
pub(crate) const TX_TIMESTAMPING_FLAGS: TimestampingFlag =
TimestampingFlag::SOF_TIMESTAMPING_RX_SOFTWARE
.union(TimestampingFlag::SOF_TIMESTAMPING_SOFTWARE)
.union(TimestampingFlag::SOF_TIMESTAMPING_TX_SOFTWARE)
.union(TimestampingFlag::SOF_TIMESTAMPING_OPT_ID)
.union(TimestampingFlag::SOF_TIMESTAMPING_OPT_TSONLY);
const SCM_TSTAMP_SND: u32 = 0;
#[repr(C)]
struct ExtendedErrWithAddr {
_err: nix::libc::sock_extended_err,
_addr: nix::libc::sockaddr_in6,
}
const ERROR_QUEUE_CONTROL_LEN: usize =
cmsg_space::<ExtendedErrWithAddr>() + cmsg_space::<Timestamps>();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ErrorQueueRecord {
TxTimestamp { id: u32, timestamp: SystemTime },
MalformedOrUnsupportedTimestamp,
SocketError { errno: i32, origin: u8 },
Ignored,
}
pub(crate) fn classify(cmsgs: impl Iterator<Item = ControlMessageOwned>) -> ErrorQueueRecord {
let mut extended_error: Option<(u32, u8, u32, u32)> = None;
let mut timestamp: Option<Timestamps> = None;
for cmsg in cmsgs {
match cmsg {
ControlMessageOwned::Ipv4RecvErr(err, _) | ControlMessageOwned::Ipv6RecvErr(err, _) => {
extended_error = Some((err.ee_errno, err.ee_origin, err.ee_info, err.ee_data));
}
ControlMessageOwned::ScmTimestampsns(observed) => {
timestamp = Some(observed);
}
_ => {}
}
}
let Some((errno, origin, info, id)) = extended_error else {
return ErrorQueueRecord::Ignored;
};
if origin != nix::libc::SO_EE_ORIGIN_TIMESTAMPING {
return ErrorQueueRecord::SocketError {
errno: errno as i32,
origin,
};
}
let is_send_completion = errno == nix::libc::ENOMSG as u32 && info == SCM_TSTAMP_SND;
if !is_send_completion {
return ErrorQueueRecord::MalformedOrUnsupportedTimestamp;
}
match timestamp.and_then(|observed| system_time_from_timespec(observed.system)) {
Some(timestamp) => ErrorQueueRecord::TxTimestamp { id, timestamp },
None => ErrorQueueRecord::MalformedOrUnsupportedTimestamp,
}
}
#[repr(align(8))]
struct ErrorQueueControlBuffer([u8; ERROR_QUEUE_CONTROL_LEN]);
pub(crate) fn try_recv_error_queue_record(fd: RawFd) -> io::Result<Option<ErrorQueueRecord>> {
let mut payload: [u8; 0] = [];
let mut iov = [IoSliceMut::new(&mut payload)];
let mut control = ErrorQueueControlBuffer([0; ERROR_QUEUE_CONTROL_LEN]);
match recvmsg::<()>(
fd,
&mut iov,
Some(&mut control.0),
MsgFlags::MSG_ERRQUEUE | MsgFlags::MSG_DONTWAIT,
) {
Ok(msg) => {
let cmsgs = msg
.cmsgs()
.map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?;
Ok(Some(classify(cmsgs)))
}
Err(Errno::EWOULDBLOCK) => Ok(None),
Err(Errno::EINTR) => Ok(Some(ErrorQueueRecord::Ignored)),
Err(errno) => Err(io::Error::from(errno)),
}
}