#![warn(unreachable_pub)]
#![warn(clippy::use_self)]
use core::time::Duration;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
#[cfg(unix)]
use std::os::unix::io::AsFd;
#[cfg(windows)]
use std::os::windows::io::AsSocket;
#[cfg(not(wasm_browser))]
use std::{sync::Mutex, time::Instant};
#[cfg(apple_fast)]
mod apple_fast;
#[cfg(any(all(unix, not(posix_minimal)), windows))]
mod cmsg;
#[cfg(all(unix, not(posix_minimal)))]
#[path = "unix.rs"]
mod imp;
#[cfg(all(any(target_os = "linux", target_os = "android"), not(posix_minimal)))]
mod linux;
#[cfg(windows)]
#[path = "windows.rs"]
mod imp;
#[cfg(posix_minimal)]
#[path = "posix_minimal.rs"]
mod imp;
#[allow(unused_imports, unused_macros)]
mod log {
#[cfg(all(feature = "log", not(feature = "tracing-log")))]
pub(crate) use log::{debug, error, info, trace, warn};
#[cfg(feature = "tracing-log")]
pub(crate) use tracing::{debug, error, info, trace, warn};
#[cfg(not(any(feature = "log", feature = "tracing-log")))]
mod no_op {
macro_rules! trace ( ($($tt:tt)*) => {{}} );
macro_rules! debug ( ($($tt:tt)*) => {{}} );
macro_rules! info ( ($($tt:tt)*) => {{}} );
macro_rules! log_warn ( ($($tt:tt)*) => {{}} );
macro_rules! error ( ($($tt:tt)*) => {{}} );
pub(crate) use {debug, error, info, log_warn as warn, trace};
}
#[cfg(not(any(feature = "log", feature = "tracing-log")))]
pub(crate) use no_op::*;
}
#[cfg(not(wasm_browser))]
pub use imp::UdpSocketState;
#[cfg(not(wasm_browser))]
pub const BATCH_SIZE: usize = imp::BATCH_SIZE;
#[cfg(wasm_browser)]
pub const BATCH_SIZE: usize = 1;
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub struct RecvMeta {
pub addr: SocketAddr,
pub len: usize,
pub stride: usize,
pub ecn: Option<EcnCodepoint>,
pub dst_ip: Option<IpAddr>,
pub interface_index: Option<u32>,
pub timestamp: Option<Duration>,
}
impl Default for RecvMeta {
fn default() -> Self {
Self {
addr: SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
len: 0,
stride: 0,
ecn: None,
dst_ip: None,
interface_index: None,
timestamp: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Transmit<'a> {
pub destination: SocketAddr,
pub ecn: Option<EcnCodepoint>,
pub contents: &'a [u8],
pub segment_size: Option<usize>,
pub src_ip: Option<IpAddr>,
}
#[cfg(not(posix_minimal))]
impl Transmit<'_> {
#[cfg_attr(apple_fast, allow(dead_code))] fn effective_segment_size(&self) -> Option<usize> {
match self.segment_size? {
size if size >= self.contents.len() => None,
size => Some(size),
}
}
}
#[cfg(not(wasm_browser))]
const IO_ERROR_LOG_INTERVAL: Duration = Duration::from_secs(60);
#[cfg(all(not(wasm_browser), any(feature = "tracing-log", feature = "log")))]
fn log_sendmsg_error(
last_send_error: &Mutex<Instant>,
err: impl core::fmt::Debug,
transmit: &Transmit<'_>,
) {
let now = Instant::now();
let last_send_error = &mut *last_send_error.lock().expect("poisend lock");
if now.saturating_duration_since(*last_send_error) > IO_ERROR_LOG_INTERVAL {
*last_send_error = now;
log::debug!(
"sendmsg error: {:?}, Transmit: {{ destination: {:?}, src_ip: {:?}, ecn: {:?}, len: {:?}, segment_size: {:?} }}",
err,
transmit.destination,
transmit.src_ip,
transmit.ecn,
transmit.contents.len(),
transmit.segment_size
);
}
}
#[cfg(not(any(wasm_browser, feature = "tracing-log", feature = "log")))]
fn log_sendmsg_error(_: &Mutex<Instant>, _: impl core::fmt::Debug, _: &Transmit<'_>) {}
#[cfg(not(wasm_browser))]
pub struct UdpSockRef<'a>(socket2::SockRef<'a>);
#[cfg(unix)]
impl<'s, S> From<&'s S> for UdpSockRef<'s>
where
S: AsFd,
{
fn from(socket: &'s S) -> Self {
Self(socket.into())
}
}
#[cfg(windows)]
impl<'s, S> From<&'s S> for UdpSockRef<'s>
where
S: AsSocket,
{
fn from(socket: &'s S) -> Self {
Self(socket.into())
}
}
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum EcnCodepoint {
Ect0 = 0b10,
Ect1 = 0b01,
Ce = 0b11,
}
impl EcnCodepoint {
pub fn from_bits(x: u8) -> Option<Self> {
use EcnCodepoint::*;
Some(match x & 0b11 {
0b10 => Ect0,
0b01 => Ect1,
0b11 => Ce,
_ => {
return None;
}
})
}
}
#[cfg(all(test, not(posix_minimal)))]
mod tests {
use std::net::Ipv4Addr;
use super::*;
#[test]
fn effective_segment_size() {
assert_eq!(
make_transmit(&[0u8; 10], Some(15)).effective_segment_size(),
None,
"segment_size > content_len should yield no effective segment_size"
);
assert_eq!(
make_transmit(&[0u8; 10], Some(10)).effective_segment_size(),
None,
"segment_size == content_len should yield no effective segment_size"
);
assert_eq!(
make_transmit(&[0u8; 10], None).effective_segment_size(),
None,
"no segment_size should yield no effective segment_size"
);
assert_eq!(
make_transmit(&[0u8; 10], Some(5)).effective_segment_size(),
Some(5),
"segment_size < content_len should yield effective segment_size"
);
}
fn make_transmit(contents: &[u8], segment_size: Option<usize>) -> Transmit<'_> {
Transmit {
destination: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 1)),
ecn: None,
contents,
segment_size,
src_ip: None,
}
}
}