use core::ffi::{c_int, c_uint, c_void};
use core::mem::size_of;
use core::ptr::NonNull;
use bun_core::{Fd, ZStr};
#[cfg(windows)]
use crate::WindowsNamedPipe;
use crate::{
CloseCode, ConnectResult, ConnectingSocket, LIBUS_SOCKET_ALLOW_HALF_OPEN,
LIBUS_SOCKET_DESCRIPTOR, SocketGroup, SocketKind, SslCtx, UpgradedDuplex,
us_bun_verify_error_t, us_socket_t,
};
bun_core::declare_scope!(uws, visible);
#[allow(non_upper_case_globals)]
impl CloseCode {
pub const Normal: CloseCode = CloseCode::normal;
pub const Failure: CloseCode = CloseCode::failure;
pub const FastShutdown: CloseCode = CloseCode::fast_shutdown;
}
#[derive(Copy, Clone)]
pub enum InternalSocket {
Connected(*mut us_socket_t),
Connecting(*mut ConnectingSocket),
Detached,
UpgradedDuplex(*mut UpgradedDuplex),
#[cfg(windows)]
Pipe(*mut WindowsNamedPipe),
#[cfg(not(windows))]
Pipe,
}
impl PartialEq for InternalSocket {
fn eq(&self, other: &Self) -> bool {
match (*self, *other) {
(InternalSocket::Connected(a), InternalSocket::Connected(b)) => core::ptr::eq(a, b),
(InternalSocket::Connecting(a), InternalSocket::Connecting(b)) => core::ptr::eq(a, b),
(InternalSocket::Detached, InternalSocket::Detached) => true,
(InternalSocket::UpgradedDuplex(a), InternalSocket::UpgradedDuplex(b)) => {
core::ptr::eq(a, b)
}
#[cfg(windows)]
(InternalSocket::Pipe(a), InternalSocket::Pipe(b)) => core::ptr::eq(a, b),
#[cfg(not(windows))]
(InternalSocket::Pipe, InternalSocket::Pipe) => false,
_ => false,
}
}
}
impl InternalSocket {
#[inline]
pub fn get(&self) -> Option<*mut us_socket_t> {
match *self {
InternalSocket::Connected(s) => Some(s),
_ => None,
}
}
#[inline]
pub fn is_detached(&self) -> bool {
matches!(self, InternalSocket::Detached)
}
#[inline]
pub fn is_named_pipe(&self) -> bool {
#[cfg(windows)]
return matches!(self, InternalSocket::Pipe(_));
#[cfg(not(windows))]
return matches!(self, InternalSocket::Pipe);
}
}
#[inline(always)]
fn sock<'a>(p: *mut us_socket_t) -> &'a mut us_socket_t {
bun_opaque::opaque_deref_mut(p)
}
#[inline(always)]
fn conn<'a>(p: *mut ConnectingSocket) -> &'a mut ConnectingSocket {
bun_opaque::opaque_deref_mut(p)
}
#[inline(always)]
fn duplex<'a>(p: *mut UpgradedDuplex) -> &'a mut UpgradedDuplex {
bun_opaque::opaque_deref_mut(p)
}
#[cfg(windows)]
#[inline(always)]
fn pipe<'a>(p: *mut WindowsNamedPipe) -> &'a mut WindowsNamedPipe {
bun_opaque::opaque_deref_mut(p)
}
macro_rules! on_socket {
(
$sock:expr;
connected $s:ident => $conn:expr,
connecting $c:ident => $cing:expr,
detached => $det:expr,
duplex $d:ident => $dup:expr,
pipe $p:ident => $pip:expr $(,)?
) => {
match $sock {
InternalSocket::Connected(__s) => { let $s = sock(__s); $conn }
InternalSocket::Connecting(__c) => { let $c = conn(__c); $cing }
InternalSocket::Detached => $det,
InternalSocket::UpgradedDuplex(__d) => { let $d = duplex(__d); $dup }
#[cfg(windows)]
InternalSocket::Pipe(__p) => { let $p = pipe(__p); $pip }
#[cfg(not(windows))]
InternalSocket::Pipe => $det,
}
};
(
$sock:expr;
connected $s:ident => $conn:expr,
duplex $d:ident => $dup:expr,
pipe $p:ident => $pip:expr,
else => $det:expr $(,)?
) => {
on_socket!($sock;
connected $s => $conn,
connecting _c => { let _ = _c; $det },
detached => $det,
duplex $d => $dup,
pipe $p => $pip,
)
};
}
#[derive(Copy, Clone)]
pub struct NewSocketHandler<const IS_SSL: bool> {
pub socket: InternalSocket,
}
pub type SocketTCP = NewSocketHandler<false>;
pub type SocketTLS = NewSocketHandler<true>;
pub type SocketTcp = NewSocketHandler<false>;
pub type SocketTls = NewSocketHandler<true>;
pub type SocketHandler<const SSL: bool> = NewSocketHandler<SSL>;
impl<const IS_SSL: bool> NewSocketHandler<IS_SSL> {
pub const DETACHED: Self = Self {
socket: InternalSocket::Detached,
};
#[inline]
pub const fn detached() -> Self {
Self::DETACHED
}
#[inline]
pub const fn assume_ssl(self) -> NewSocketHandler<true> {
debug_assert!(IS_SSL);
NewSocketHandler {
socket: self.socket,
}
}
#[inline]
pub const fn assume_tcp(self) -> NewSocketHandler<false> {
debug_assert!(!IS_SSL);
NewSocketHandler {
socket: self.socket,
}
}
#[inline]
pub const fn cast_ssl<const NEW_SSL: bool>(self) -> NewSocketHandler<NEW_SSL> {
debug_assert!(IS_SSL == NEW_SSL);
NewSocketHandler {
socket: self.socket,
}
}
#[inline]
pub fn detach(&mut self) {
self.socket = InternalSocket::Detached;
}
#[inline]
pub fn is_detached(&self) -> bool {
self.socket.is_detached()
}
#[inline]
pub fn is_named_pipe(&self) -> bool {
self.socket.is_named_pipe()
}
pub fn is_closed(&self) -> bool {
on_socket!(self.socket;
connected s => s.is_closed(),
connecting c => c.is_closed(),
detached => true,
duplex d => d.is_closed(),
pipe p => p.is_closed(),
)
}
pub fn is_shutdown(&self) -> bool {
on_socket!(self.socket;
connected s => s.is_shutdown(),
connecting c => c.is_shutdown(),
detached => true,
duplex d => d.is_shutdown(),
pipe p => p.is_shutdown(),
)
}
pub fn is_established(&self) -> bool {
on_socket!(self.socket;
connected s => s.is_established(),
duplex d => d.is_established(),
pipe p => p.is_established(),
else => false,
)
}
#[inline]
pub fn is_closed_or_has_error(&self) -> bool {
self.is_closed() || self.is_shutdown() || self.get_error() != 0
}
pub fn get_verify_error(&self) -> us_bun_verify_error_t {
on_socket!(self.socket;
connected s => s.get_verify_error(),
duplex d => d.ssl_error(),
pipe p => p.ssl_error(),
else => us_bun_verify_error_t::default(),
)
}
pub fn get_error(&self) -> i32 {
on_socket!(self.socket;
connected s => s.get_error(),
connecting c => c.get_error(),
detached => 0,
duplex d => d.ssl_error().error_no,
pipe p => p.ssl_error().error_no,
)
}
pub fn close(&self, code: CloseCode) {
on_socket!(self.socket;
connected s => s.close(code),
connecting c => c.close(),
detached => {},
duplex d => d.close(),
pipe p => p.close(),
)
}
pub fn shutdown(&self) {
on_socket!(self.socket;
connected s => s.shutdown(),
connecting c => c.shutdown(),
detached => {},
duplex d => d.shutdown(),
pipe p => p.shutdown(),
)
}
pub fn shutdown_read(&self) {
on_socket!(self.socket;
connected s => s.shutdown_read(),
connecting c => c.shutdown_read(),
detached => {},
duplex d => d.shutdown_read(),
pipe p => p.shutdown_read(),
)
}
pub fn write(&self, data: &[u8]) -> i32 {
on_socket!(self.socket;
connected s => s.write(data),
duplex d => d.encode_and_write(data),
pipe p => p.encode_and_write(data),
else => 0,
)
}
#[cfg(not(windows))]
pub fn write_fd(&self, data: &[u8], file_descriptor: c_int) -> i32 {
match self.socket {
InternalSocket::Connected(s) => {
sock(s).write_fd(data, Fd::from_native(file_descriptor))
}
InternalSocket::UpgradedDuplex(_) | InternalSocket::Pipe => self.write(data),
InternalSocket::Connecting(_) | InternalSocket::Detached => 0,
}
}
pub fn raw_write(&self, data: &[u8]) -> i32 {
on_socket!(self.socket;
connected s => s.raw_write(data),
duplex d => d.raw_write(data),
pipe p => p.raw_write(data),
else => 0,
)
}
pub fn flush(&self) {
on_socket!(self.socket;
connected s => s.flush(),
duplex d => d.flush(),
pipe p => p.flush(),
else => {},
)
}
pub fn timeout(&self, seconds: c_uint) {
on_socket!(self.socket;
connected s => s.set_timeout(seconds),
connecting c => c.timeout(seconds),
detached => {},
duplex d => d.set_timeout(seconds),
pipe p => p.set_timeout(seconds),
)
}
pub fn set_timeout(&self, seconds: c_uint) {
on_socket!(self.socket;
connected s => if seconds > 240 {
s.set_timeout(0);
s.set_long_timeout(seconds / 60);
} else {
s.set_timeout(seconds);
s.set_long_timeout(0);
},
connecting c => if seconds > 240 {
c.timeout(0);
c.long_timeout(seconds / 60);
} else {
c.timeout(seconds);
c.long_timeout(0);
},
detached => {},
duplex d => d.set_timeout(seconds),
pipe p => p.set_timeout(seconds),
)
}
pub fn set_timeout_minutes(&self, minutes: c_uint) {
on_socket!(self.socket;
connected s => { s.set_timeout(0); s.set_long_timeout(minutes); },
connecting c => { c.timeout(0); c.long_timeout(minutes); },
detached => {},
duplex d => d.set_timeout(minutes * 60),
pipe p => p.set_timeout(minutes * 60),
)
}
pub fn pause_stream(&self) -> bool {
on_socket!(self.socket;
connected s => { s.pause(); true },
connecting _c => false,
detached => true,
duplex _d => false, pipe p => p.pause_stream(),
)
}
pub fn resume_stream(&self) -> bool {
on_socket!(self.socket;
connected s => { s.resume(); true },
connecting _c => false,
detached => true,
duplex _d => false, pipe p => p.resume_stream(),
)
}
pub fn set_no_delay(&self, enabled: bool) -> bool {
match self.socket {
InternalSocket::Connected(s) => {
sock(s).set_nodelay(enabled);
true
}
_ => false,
}
}
pub fn set_keep_alive(&self, enabled: bool, delay: u32) -> bool {
match self.socket {
InternalSocket::Connected(s) => sock(s).set_keepalive(enabled, delay) == 0,
_ => false,
}
}
pub fn start_tls(&self, is_client: bool) {
if let InternalSocket::Connected(s) = self.socket {
sock(s).open(is_client, None);
}
}
#[inline]
pub fn ssl(&self) -> Option<*mut bun_boringssl_sys::SSL> {
if !IS_SSL {
return None;
}
self.get_native_handle().map(|h| h.cast())
}
pub fn get_native_handle(&self) -> Option<*mut c_void> {
match self.socket {
InternalSocket::Connected(s) => sock(s).get_native_handle(),
InternalSocket::Connecting(s) => {
let h = conn(s).get_native_handle();
if h.is_null() { None } else { Some(h) }
}
InternalSocket::UpgradedDuplex(s) if IS_SSL => duplex(s).ssl().map(|p| p.cast()),
InternalSocket::UpgradedDuplex(_) => None,
#[cfg(windows)]
InternalSocket::Pipe(s) if IS_SSL => pipe(s).ssl().map(|p| p.cast()),
#[cfg(windows)]
InternalSocket::Pipe(_) => None,
#[cfg(not(windows))]
InternalSocket::Pipe => None,
InternalSocket::Detached => None,
}
}
pub fn ext<T>(&self) -> Option<*mut T> {
match self.socket {
InternalSocket::Connected(s) => Some(sock(s).ext_ptr().cast::<T>()),
InternalSocket::Connecting(s) => {
Some(crate::connecting_socket::us_connecting_socket_ext(conn(s)).cast::<T>())
}
_ => None,
}
}
pub fn group(&self) -> Option<*mut SocketGroup> {
match self.socket {
InternalSocket::Connected(s) => {
Some(std::ptr::from_mut::<SocketGroup>(sock(s).group()))
}
InternalSocket::Connecting(s) => Some(conn(s).group()),
_ => None,
}
}
#[inline]
pub fn fd(&self) -> Fd {
match self.socket {
InternalSocket::Connected(s) => sock(s).get_fd(),
_ => Fd::INVALID,
}
}
pub fn local_port(&self) -> i32 {
match self.socket {
InternalSocket::Connected(s) => sock(s).local_port(),
_ => 0,
}
}
pub fn remote_port(&self) -> i32 {
match self.socket {
InternalSocket::Connected(s) => sock(s).remote_port(),
_ => 0,
}
}
pub fn local_address<'b>(&self, buf: &'b mut [u8]) -> Option<&'b [u8]> {
match self.socket {
InternalSocket::Connected(s) => match sock(s).local_address(buf) {
Ok(v) => Some(v),
Err(e) => bun_core::Output::panic(format_args!(
"Failed to get socket's local address: {}",
e.name()
)),
},
_ => None,
}
}
pub fn remote_address<'b>(&self, buf: &'b mut [u8]) -> Option<&'b [u8]> {
match self.socket {
InternalSocket::Connected(s) => match sock(s).remote_address(buf) {
Ok(v) => Some(v),
Err(e) => bun_core::Output::panic(format_args!(
"Failed to get socket's remote address: {}",
e.name()
)),
},
_ => None,
}
}
pub fn mark_needs_more_for_sendfile(&self) {
const { assert!(!IS_SSL, "SSL sockets do not support sendfile yet") };
if let InternalSocket::Connected(s) = self.socket {
sock(s).send_file_needs_more();
}
}
#[inline]
pub fn from(socket: *mut us_socket_t) -> Self {
Self {
socket: InternalSocket::Connected(socket),
}
}
#[inline]
pub fn from_connecting(connecting: *mut ConnectingSocket) -> Self {
Self {
socket: InternalSocket::Connecting(connecting),
}
}
#[inline]
pub fn from_any(socket: InternalSocket) -> Self {
Self { socket }
}
#[inline]
pub fn from_duplex(d: *mut UpgradedDuplex) -> Self {
Self {
socket: InternalSocket::UpgradedDuplex(d),
}
}
#[cfg(windows)]
#[inline]
pub fn from_named_pipe(p: *mut WindowsNamedPipe) -> Self {
Self {
socket: InternalSocket::Pipe(p),
}
}
pub fn from_fd<This>(
g: &mut SocketGroup,
k: SocketKind,
handle: Fd,
this: *mut This,
is_ipc: bool,
) -> Option<Self> {
let ext_size = size_of::<Option<NonNull<This>>>() as c_int;
let raw = g.from_fd(
k,
None,
ext_size,
handle.native() as LIBUS_SOCKET_DESCRIPTOR,
is_ipc,
);
if raw.is_null() {
return None;
}
*sock(raw).ext::<Option<NonNull<This>>>() = NonNull::new(this);
Some(Self {
socket: InternalSocket::Connected(raw),
})
}
pub fn connect_group<Owner>(
g: &mut SocketGroup,
kind: SocketKind,
ssl_ctx: Option<*mut SslCtx>,
raw_host: &[u8],
port: c_int,
owner: *mut Owner,
allow_half_open: bool,
) -> Result<Self, ConnectError> {
let opts: c_int = if allow_half_open {
LIBUS_SOCKET_ALLOW_HALF_OPEN
} else {
0
};
let host =
if raw_host.len() > 1 && raw_host[0] == b'[' && raw_host[raw_host.len() - 1] == b']' {
&raw_host[1..raw_host.len() - 1]
} else {
raw_host
};
let mut stack = [0u8; 256];
let heap: Vec<u8>;
let host_z: &core::ffi::CStr = if host.len() < stack.len() {
stack[..host.len()].copy_from_slice(host);
stack[host.len()] = 0;
ZStr::from_buf(&stack, host.len()).as_cstr()
} else {
heap = {
let mut v = Vec::with_capacity(host.len() + 1);
v.extend_from_slice(host);
v.push(0);
v
};
ZStr::from_slice_with_nul(&heap).as_cstr()
};
let ext_size = size_of::<Option<NonNull<Owner>>>() as c_int;
match g.connect(kind, ssl_ctx, host_z, port, opts, ext_size) {
ConnectResult::Failed => Err(ConnectError::FailedToOpenSocket),
ConnectResult::Socket(s) => {
*sock(s).ext::<Option<NonNull<Owner>>>() = NonNull::new(owner);
Ok(Self {
socket: InternalSocket::Connected(s),
})
}
ConnectResult::Connecting(cs) => {
*conn(cs).ext::<Option<NonNull<Owner>>>() = NonNull::new(owner);
Ok(Self {
socket: InternalSocket::Connecting(cs),
})
}
}
}
pub fn connect_unix_group<Owner>(
g: &mut SocketGroup,
kind: SocketKind,
ssl_ctx: Option<*mut SslCtx>,
path: &[u8],
owner: *mut Owner,
allow_half_open: bool,
) -> Result<Self, ConnectError> {
let opts: c_int = if allow_half_open {
LIBUS_SOCKET_ALLOW_HALF_OPEN
} else {
0
};
let ext_size = size_of::<Option<NonNull<Owner>>>() as c_int;
let s = g.connect_unix(kind, ssl_ctx, path, opts, ext_size);
if s.is_null() {
return Err(ConnectError::FailedToOpenSocket);
}
*sock(s).ext::<Option<NonNull<Owner>>>() = NonNull::new(owner);
Ok(Self {
socket: InternalSocket::Connected(s),
})
}
pub fn adopt_group<Owner>(
tcp: *mut us_socket_t,
g: *mut SocketGroup,
kind: SocketKind,
owner: *mut Owner,
set_socket_field: impl FnOnce(*mut Owner, Self),
) -> bool {
let new_s = unsafe {
sock_c::us_socket_adopt(
tcp,
g,
kind as u8,
size_of::<*mut c_void>() as i32,
size_of::<*mut c_void>() as i32,
)
};
if new_s.is_null() {
return false;
}
*sock(new_s).ext::<*mut c_void>() = owner.cast::<c_void>();
set_socket_field(
owner,
Self {
socket: InternalSocket::Connected(new_s),
},
);
true
}
}
mod sock_c {
use super::{SocketGroup, us_socket_t};
unsafe extern "C" {
pub(super) fn us_socket_adopt(
s: *mut us_socket_t,
group: *mut SocketGroup,
kind: u8,
old_ext_size: i32,
ext_size: i32,
) -> *mut us_socket_t;
}
}
#[derive(strum::IntoStaticStr, Debug)]
pub enum ConnectError {
FailedToOpenSocket,
}
impl From<ConnectError> for bun_core::Error {
fn from(_: ConnectError) -> Self {
bun_core::err!("FailedToOpenSocket")
}
}
#[derive(Copy, Clone)]
pub enum AnySocket {
SocketTcp(SocketTCP),
SocketTls(SocketTLS),
}
macro_rules! any_socket_forward {
($( fn $name:ident(&self $(, $arg:ident : $ty:ty)* ) $(-> $ret:ty)? ; )*) => {$(
#[inline]
pub fn $name(&self $(, $arg: $ty)*) $(-> $ret)? {
match self {
AnySocket::SocketTcp(s) => s.$name($($arg),*),
AnySocket::SocketTls(s) => s.$name($($arg),*),
}
}
)*};
}
impl AnySocket {
#[inline]
pub fn is_ssl(&self) -> bool {
matches!(self, AnySocket::SocketTls(_))
}
#[inline]
pub fn socket(&self) -> &InternalSocket {
match self {
AnySocket::SocketTcp(s) => &s.socket,
AnySocket::SocketTls(s) => &s.socket,
}
}
#[inline]
pub fn ext<T>(&self) -> Option<*mut T> {
match self {
AnySocket::SocketTcp(s) => s.ext::<T>(),
AnySocket::SocketTls(s) => s.ext::<T>(),
}
}
#[inline]
pub fn terminate(&self) {
self.close(CloseCode::failure)
}
#[inline]
pub fn group(&self) -> *mut SocketGroup {
match self {
AnySocket::SocketTcp(s) => s.group(),
AnySocket::SocketTls(s) => s.group(),
}
.unwrap()
}
any_socket_forward! {
fn is_closed(&self) -> bool;
fn is_shutdown(&self) -> bool;
fn is_established(&self) -> bool;
fn close(&self, code: CloseCode);
fn write(&self, data: &[u8]) -> i32;
fn set_timeout(&self, seconds: c_uint);
fn shutdown(&self);
fn shutdown_read(&self);
fn local_port(&self) -> i32;
fn get_native_handle(&self) -> Option<*mut c_void>;
}
}