use bytes::{BufMut, Bytes, BytesMut};
use monocoque_core::options::SocketOptions;
use monocoque_core::rt::TcpStream;
use std::io;
use tracing::debug;
pub const FLAG_LONG: u8 = 0x02;
pub const FLAG_COMMAND: u8 = 0x04;
pub const ACCEPT_FD_EXHAUSTION_BACKOFF: std::time::Duration = std::time::Duration::from_millis(10);
#[cfg(unix)]
#[must_use]
pub fn is_fd_exhaustion(err: &io::Error) -> bool {
matches!(err.raw_os_error(), Some(23 | 24))
}
#[cfg(not(unix))]
#[must_use]
pub fn is_fd_exhaustion(_err: &io::Error) -> bool {
false
}
pub async fn backoff_on_fd_exhaustion(err: &io::Error) {
if is_fd_exhaustion(err) {
monocoque_core::rt::sleep(ACCEPT_FD_EXHAUSTION_BACKOFF).await;
}
}
pub fn encode_frame(flags: u8, body: &Bytes) -> Bytes {
let len = body.len();
let header_len = if len <= 255 { 2 } else { 9 };
let mut out = BytesMut::with_capacity(header_len + len);
if len <= 255 {
out.put_u8(flags & !FLAG_LONG);
out.put_u8(len as u8);
} else {
out.put_u8(flags | FLAG_LONG);
out.put_u64(len as u64);
}
out.extend_from_slice(body);
out.freeze()
}
pub fn build_ready(socket_type: &str, identity: Option<&[u8]>) -> Bytes {
let mut body = BytesMut::new();
body.put_u8(5);
body.extend_from_slice(b"READY");
put_property(&mut body, "Socket-Type", socket_type.as_bytes());
if let Some(id) = identity {
put_property(&mut body, "Identity", id);
}
body.freeze()
}
#[inline]
fn put_property(dst: &mut BytesMut, name: &str, value: &[u8]) {
let name_bytes = name.as_bytes();
dst.put_u8(name_bytes.len() as u8);
dst.extend_from_slice(name_bytes);
dst.put_u32(value.len() as u32);
dst.extend_from_slice(value);
}
pub fn configure_tcp_stream(
stream: &TcpStream,
options: &SocketOptions,
socket_name: &str,
) -> io::Result<()> {
monocoque_core::tcp::enable_tcp_nodelay(stream)?;
debug!("[{}] TCP_NODELAY enabled", socket_name);
if options.sndbuf > 0 || options.rcvbuf > 0 {
monocoque_core::tcp::configure_socket_buffers(stream, options.sndbuf, options.rcvbuf)?;
debug!(
"[{}] socket buffers set (sndbuf={}, rcvbuf={})",
socket_name, options.sndbuf, options.rcvbuf
);
}
monocoque_core::tcp::configure_tcp_keepalive(
stream,
options.tcp_keepalive,
options.tcp_keepalive_cnt,
options.tcp_keepalive_idle,
options.tcp_keepalive_intvl,
)?;
if options.tcp_keepalive == 1 {
debug!(
"[{}] TCP keepalive enabled (cnt={}, idle={}s, intvl={}s)",
socket_name,
options.tcp_keepalive_cnt,
options.tcp_keepalive_idle,
options.tcp_keepalive_intvl
);
}
Ok(())
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn fd_exhaustion_errnos_are_recognized() {
assert!(is_fd_exhaustion(&io::Error::from_raw_os_error(24)));
assert!(is_fd_exhaustion(&io::Error::from_raw_os_error(23)));
}
#[test]
fn other_errors_are_not_fd_exhaustion() {
assert!(!is_fd_exhaustion(&io::Error::from_raw_os_error(104))); assert!(!is_fd_exhaustion(&io::Error::new(
io::ErrorKind::WouldBlock,
"would block"
)));
assert!(!is_fd_exhaustion(&io::Error::other("synthetic")));
}
}