use std::cell::Cell;
use std::cell::RefCell;
use std::net::ToSocketAddrs;
use std::rc::Rc;
use deno_core::CppgcInherits;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_core::ResourceId;
use deno_core::error::ResourceError;
use deno_core::op2;
use deno_core::uv_compat;
use deno_core::uv_compat::UvConnect;
use deno_core::uv_compat::UvLoop;
use deno_core::uv_compat::UvStream;
use deno_core::uv_compat::UvTcp;
use deno_core::v8;
use deno_net::io::TcpStreamResource;
use deno_permissions::PermissionsContainer;
use socket2::SockAddr as Socket2SockAddr;
use crate::ops::handle_wrap::AsyncWrap;
use crate::ops::handle_wrap::Handle;
use crate::ops::handle_wrap::HandleWrap;
use crate::ops::handle_wrap::OwnedPtr;
use crate::ops::handle_wrap::ProviderType;
use crate::ops::stream_wrap::LibUvStreamWrap;
use crate::ops::stream_wrap::clone_context_from_uv_loop;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
enum SocketType {
Socket = 0,
Server = 1,
}
macro_rules! with_js_handle {
($stream:expr, |$scope:ident, $this:ident| $body:block) => {{
let Some(handle_data_ptr) =
LibUvStreamWrap::stable_handle_data($stream)
else {
return;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
let isolate_ptr = unsafe { *handle_data.isolate.get() };
if isolate_ptr.is_null() {
return;
}
let mut isolate = unsafe { v8::Isolate::from_raw_isolate_ptr(isolate_ptr) };
let loop_ptr = unsafe { (*$stream).loop_ };
let context = unsafe { clone_context_from_uv_loop(&mut isolate, loop_ptr) };
v8::scope!(let handle_scope, &mut isolate);
let context_local = v8::Local::new(handle_scope, context);
let $scope = &mut v8::ContextScope::new(handle_scope, context_local);
let Some(js_global) =
(unsafe { (*handle_data.js_handle.get()).to_global($scope) })
else {
return;
};
let $this: v8::Local<v8::Object> = v8::Local::new($scope, js_global);
$body
}};
}
#[allow(
unused_unsafe,
clippy::undocumented_unsafe_blocks,
reason = "macro expands unsafe blocks inside unsafe fn"
)]
pub(crate) unsafe extern "C" fn server_connection_cb(
server: *mut UvStream,
status: i32,
) {
with_js_handle!(server, |scope, this| {
let key = v8::String::new(scope, "onconnection").unwrap();
if let Some(onconnection) = this.get(scope, key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(onconnection)
{
let status_val: v8::Local<v8::Value> =
v8::Integer::new(scope, status).into();
func.call(scope, this.into(), &[status_val]);
}
});
}
#[repr(C)]
struct ConnectReqData {
uv_req: UvConnect,
js_req: v8::Global<v8::Object>,
}
#[allow(
unused_unsafe,
clippy::undocumented_unsafe_blocks,
reason = "macro expands unsafe blocks inside unsafe fn"
)]
unsafe extern "C" fn connect_cb(req: *mut UvConnect, status: i32) {
let stream = unsafe { (*req).handle as *mut UvStream };
let req_data = unsafe { Box::from_raw(req as *mut ConnectReqData) };
let js_req_global = req_data.js_req;
with_js_handle!(stream, |scope, this| {
let js_req = v8::Local::new(scope, &js_req_global);
let oncomplete_key = v8::String::new(scope, "oncomplete").unwrap();
if let Some(oncomplete) = js_req.get(scope, oncomplete_key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(oncomplete)
{
let status_val: v8::Local<v8::Value> =
v8::Integer::new(scope, status).into();
let readable: v8::Local<v8::Value> =
v8::Boolean::new(scope, status == 0).into();
let writable: v8::Local<v8::Value> =
v8::Boolean::new(scope, status == 0).into();
func.call(
scope,
js_req.into(),
&[status_val, this.into(), js_req.into(), readable, writable],
);
}
});
}
#[derive(CppgcInherits)]
#[cppgc_inherits_from(LibUvStreamWrap)]
#[repr(C)]
pub struct TCPWrap {
base: LibUvStreamWrap,
handle: Cell<Option<OwnedPtr<UvTcp>>>,
socket_type: Cell<SocketType>,
net_perm_token: RefCell<Option<deno_net::ops::NetPermToken>>,
}
unsafe impl GarbageCollected for TCPWrap {
fn get_name(&self) -> &'static std::ffi::CStr {
c"TCP"
}
fn trace(&self, visitor: &mut v8::cppgc::Visitor) {
self.base.trace(visitor);
}
}
impl Drop for TCPWrap {
fn drop(&mut self) {
self.base.detach_stream();
}
}
impl TCPWrap {
fn new(socket_type: SocketType, op_state: &mut OpState) -> Self {
let loop_ =
&**op_state.borrow::<Box<UvLoop>>() as *const UvLoop as *mut UvLoop;
let tcp = OwnedPtr::from_box(Box::<UvTcp>::new_uninit());
let err = unsafe { uv_compat::uv_tcp_init(loop_, tcp.as_mut_ptr().cast()) };
if err == 0 {
let tcp = unsafe { tcp.cast::<UvTcp>() };
let provider = if socket_type == SocketType::Server {
ProviderType::TcpServerWrap
} else {
ProviderType::TcpWrap
};
let base = LibUvStreamWrap::new(
HandleWrap::create(
AsyncWrap::create(op_state, provider as i32),
Some(Handle::New(tcp.as_ptr().cast())),
),
-1, tcp.as_ptr().cast(),
);
unsafe {
(*tcp.as_mut_ptr()).data = base.handle_data_ptr();
}
Self {
base,
handle: Cell::new(Some(tcp)),
socket_type: Cell::new(socket_type),
net_perm_token: RefCell::new(None),
}
} else {
let provider = if socket_type == SocketType::Server {
ProviderType::TcpServerWrap
} else {
ProviderType::TcpWrap
};
unsafe {
let layout = std::alloc::Layout::new::<UvTcp>();
std::alloc::dealloc(tcp.as_mut_ptr() as *mut u8, layout);
std::mem::forget(tcp);
}
Self {
base: LibUvStreamWrap::new(
HandleWrap::create(
AsyncWrap::create(op_state, provider as i32),
None,
),
-1,
std::ptr::null(),
),
handle: Cell::new(None),
socket_type: Cell::new(socket_type),
net_perm_token: RefCell::new(None),
}
}
}
fn tcp_ptr(&self) -> *mut UvTcp {
match self.handle.take() {
Some(h) => {
let ptr = h.as_mut_ptr();
self.handle.set(Some(h));
ptr
}
None => std::ptr::null_mut(),
}
}
pub fn detach(&self) -> *mut UvTcp {
self.base.detach_stream();
match self.handle.take() {
Some(h) => h.into_raw(),
None => std::ptr::null_mut(),
}
}
pub fn stream_ptr(&self) -> *mut UvStream {
self.base.stream_ptr()
}
fn net_perm_check_host(&self, address: &str) -> String {
match &*self.net_perm_token.borrow() {
Some(token) => token.check_host(address).to_string(),
None => address.to_string(),
}
}
fn bind_inner(
&self,
state: &mut OpState,
address: &str,
port: i32,
flags: u32,
) -> Result<i32, deno_permissions::PermissionCheckError> {
state
.borrow_mut::<PermissionsContainer>()
.check_net(&(address, Some(port as u16)), "node:net.listen()")?;
let addr_str = format!("{}:{}", address, port);
let socket_addr = match addr_str.to_socket_addrs() {
Ok(mut addrs) => match addrs.next() {
Some(addr) => addr,
None => return Ok(-1),
},
Err(_) => return Ok(-1),
};
unsafe {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return Ok(-1);
}
let sock_addr = Socket2SockAddr::from(socket_addr);
Ok(uv_compat::uv_tcp_bind(
tcp,
sock_addr.as_ptr() as *const _,
#[allow(clippy::unnecessary_cast, reason = "depends on platform")]
{
sock_addr.len() as u32
},
flags,
))
}
}
}
#[op2(inherit = LibUvStreamWrap)]
impl TCPWrap {
#[constructor]
#[cppgc]
fn new_tcp(
#[smi] socket_type: i32,
op_state: &mut OpState,
#[this] this: v8::Global<v8::Object>,
scope: &mut v8::PinScope,
) -> TCPWrap {
let st = if socket_type == 1 {
SocketType::Server
} else {
SocketType::Socket
};
let tcp = TCPWrap::new(st, op_state);
tcp.base.set_js_handle(this, scope);
tcp
}
#[fast]
fn open(&self, state: &mut OpState, #[smi] fd: i32) -> i32 {
if fd < 0 {
return uv_compat::UV_EBADF;
}
let Some(was_inherited) =
state.borrow::<deno_io::FdTable>().begin_uv_adopt(fd)
else {
return -libc::EEXIST;
};
let tcp = self.tcp_ptr();
if tcp.is_null() {
return uv_compat::UV_EBADF;
}
let ret = unsafe {
#[cfg(unix)]
{
let flags = libc::fcntl(fd, libc::F_GETFL);
if flags != -1 {
libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
}
if self.socket_type.get() == SocketType::Server {
uv_compat::uv_tcp_open_listener(tcp, fd)
} else {
uv_compat::uv_tcp_open(tcp, fd)
}
}
#[cfg(windows)]
{
use windows_sys::Win32::Networking::WinSock::FIONBIO;
use windows_sys::Win32::Networking::WinSock::ioctlsocket;
let mut nonblocking: u32 = 1;
ioctlsocket(fd as usize, FIONBIO, &mut nonblocking);
uv_compat::uv_tcp_open(tcp, fd)
}
};
if ret == 0 {
state
.borrow_mut::<deno_io::FdTable>()
.finish_uv_adopt(fd, was_inherited);
self.base.set_fd(fd);
}
ret
}
#[reentrant]
fn close(
&self,
op_state: Rc<RefCell<OpState>>,
#[this] this: v8::Global<v8::Object>,
scope: &mut v8::PinScope<'_, '_>,
#[scoped] cb: Option<v8::Global<v8::Function>>,
) -> Result<(), ResourceError> {
let fd = self.base.get_fd();
if fd >= 0 {
op_state
.borrow_mut()
.borrow_mut::<deno_io::FdTable>()
.remove(fd);
self.base.set_fd(-1);
}
self.base.clear_js_handle();
self
.base
.handle_wrap()
.close_handle(op_state, this, scope, cb)
}
#[fast]
fn socket_type_for_ipc(&self) -> i32 {
match self.socket_type.get() {
SocketType::Server => 1,
SocketType::Socket => 0,
}
}
#[fast]
fn fd_for_ipc(&self) -> i32 {
#[cfg(unix)]
{
let tcp = self.tcp_ptr();
if tcp.is_null() {
return -1;
}
unsafe { uv_compat::uv_tcp_fd_for_ipc(tcp) }
}
#[cfg(not(unix))]
-1
}
#[fast]
fn open_from_rid(&self, state: &mut OpState, #[smi] rid: ResourceId) -> i32 {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return -1;
}
let fd = state
.resource_table
.get::<TcpStreamResource>(rid)
.ok()
.and_then(|r| r.dup_raw_fd());
match fd {
Some(fd) => unsafe { uv_compat::uv_tcp_open(tcp, fd) },
None => -1,
}
}
#[nofast]
fn bind(
&self,
state: &mut OpState,
#[string] address: &str,
#[smi] port: i32,
) -> Result<i32, deno_permissions::PermissionCheckError> {
self.bind_inner(state, address, port, 0)
}
#[nofast]
#[rename("bindWithFlags")]
fn bind_with_flags(
&self,
state: &mut OpState,
#[string] address: &str,
#[smi] port: i32,
#[smi] flags: u32,
) -> Result<i32, deno_permissions::PermissionCheckError> {
self.bind_inner(state, address, port, flags)
}
#[nofast]
fn bind6(
&self,
state: &mut OpState,
#[string] address: &str,
#[smi] port: i32,
#[smi] flags: u32,
) -> Result<i32, deno_permissions::PermissionCheckError> {
state
.borrow_mut::<PermissionsContainer>()
.check_net(&(address, Some(port as u16)), "node:net.listen()")?;
let addr_str = format!("{}:{}", address, port);
let socket_addr = match addr_str.to_socket_addrs() {
Ok(mut addrs) => match addrs.next() {
Some(addr) => addr,
None => return Ok(-1),
},
Err(_) => return Ok(-1),
};
unsafe {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return Ok(-1);
}
let sock_addr = Socket2SockAddr::from(socket_addr);
Ok(uv_compat::uv_tcp_bind(
tcp,
sock_addr.as_ptr() as *const _,
#[allow(clippy::unnecessary_cast, reason = "on some platforms")]
{
sock_addr.len() as u32
},
flags,
))
}
}
#[fast]
fn listen(&self, #[smi] backlog: i32) -> i32 {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return -1;
}
unsafe {
let stream = tcp as *mut UvStream;
uv_compat::uv_listen(stream, backlog, Some(server_connection_cb))
}
}
#[fast]
fn accept(&self, #[cppgc] client: &TCPWrap) -> i32 {
let server_tcp = self.tcp_ptr();
let client_tcp = client.tcp_ptr();
if server_tcp.is_null() || client_tcp.is_null() {
return -1;
}
unsafe {
uv_compat::uv_accept(
server_tcp as *mut UvStream,
client_tcp as *mut UvStream,
)
}
}
fn take_stream(
&self,
state: &mut OpState,
) -> Result<ResourceId, deno_error::JsErrorBox> {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return Err(deno_error::JsErrorBox::generic("TCP handle is closed"));
}
let tcp_stream = unsafe { (*tcp).take_stream() }.ok_or_else(|| {
deno_error::JsErrorBox::generic(
"TCP handle has no active stream - already taken or not connected",
)
})?;
let (read_half, write_half) = tcp_stream.into_split();
let resource = TcpStreamResource::new((read_half, write_half));
Ok(state.resource_table.add(resource))
}
#[fast]
fn set_no_delay(&self, enable: bool) -> i32 {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return -1;
}
unsafe { uv_compat::uv_tcp_nodelay(tcp, enable as i32) }
}
#[fast]
#[rename("setKeepAlive")]
fn set_keep_alive(&self, enable: bool, #[smi] delay: i32) -> i32 {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return -1;
}
let delay = delay.max(0) as u32;
unsafe { uv_compat::uv_tcp_keepalive(tcp, enable as i32, delay) }
}
#[fast]
fn reset(&self) -> i32 {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return -1;
}
unsafe { uv_compat::uv_tcp_reset(tcp) }
}
#[nofast]
fn set_net_perm_token(&self, #[cppgc] token: &deno_net::ops::NetPermToken) {
*self.net_perm_token.borrow_mut() = Some(deno_net::ops::NetPermToken {
hostname: token.hostname.clone(),
port: token.port,
resolved_ips: token.resolved_ips.clone(),
});
}
#[nofast]
fn connect(
&self,
state: &mut OpState,
js_req: v8::Local<v8::Object>,
#[string] address: &str,
#[smi] port: i32,
scope: &mut v8::PinScope,
) -> Result<i32, deno_permissions::PermissionCheckError> {
let check_host = self.net_perm_check_host(address);
state.borrow_mut::<PermissionsContainer>().check_net(
&(check_host.as_str(), Some(port as u16)),
"node:net.connect()",
)?;
let addr_str = format!("{}:{}", address, port);
let socket_addr = match addr_str.to_socket_addrs() {
Ok(mut addrs) => match addrs.next() {
Some(addr) => addr,
None => return Ok(-1),
},
Err(_) => return Ok(-1),
};
state
.borrow_mut::<PermissionsContainer>()
.check_net_resolved(
&socket_addr.ip(),
socket_addr.port(),
"node:net.connect()",
)?;
let tcp = self.tcp_ptr();
if tcp.is_null() {
return Ok(-1);
}
let sock_addr = Socket2SockAddr::from(socket_addr);
let js_req_global = v8::Global::new(scope, js_req);
let mut connect_req = Box::new(ConnectReqData {
uv_req: uv_compat::new_connect(),
js_req: js_req_global,
});
let req_ptr = &mut connect_req.uv_req as *mut UvConnect;
let _ = Box::into_raw(connect_req);
let ret = unsafe {
uv_compat::uv_tcp_connect(
req_ptr,
tcp,
sock_addr.as_ptr() as *const _,
Some(connect_cb),
)
};
if ret != 0 {
unsafe {
let _ = Box::from_raw(req_ptr as *mut ConnectReqData);
}
}
Ok(ret)
}
#[nofast]
fn connect6(
&self,
state: &mut OpState,
js_req: v8::Local<v8::Object>,
#[string] address: &str,
#[smi] port: i32,
scope: &mut v8::PinScope,
) -> Result<i32, deno_permissions::PermissionCheckError> {
let check_host = self.net_perm_check_host(address);
state.borrow_mut::<PermissionsContainer>().check_net(
&(check_host.as_str(), Some(port as u16)),
"node:net.connect()",
)?;
let addr_str = format!("{}:{}", address, port);
let socket_addr = match addr_str.to_socket_addrs() {
Ok(mut addrs) => match addrs.next() {
Some(addr) => addr,
None => return Ok(-1),
},
Err(_) => return Ok(-1),
};
state
.borrow_mut::<PermissionsContainer>()
.check_net_resolved(
&socket_addr.ip(),
socket_addr.port(),
"node:net.connect()",
)?;
let tcp = self.tcp_ptr();
if tcp.is_null() {
return Ok(-1);
}
let sock_addr = Socket2SockAddr::from(socket_addr);
let js_req_global = v8::Global::new(scope, js_req);
let mut connect_req = Box::new(ConnectReqData {
uv_req: uv_compat::new_connect(),
js_req: js_req_global,
});
let req_ptr = &mut connect_req.uv_req as *mut UvConnect;
let _ = Box::into_raw(connect_req);
let ret = unsafe {
uv_compat::uv_tcp_connect(
req_ptr,
tcp,
sock_addr.as_ptr() as *const _,
Some(connect_cb),
)
};
if ret != 0 {
unsafe {
let _ = Box::from_raw(req_ptr as *mut ConnectReqData);
}
}
Ok(ret)
}
#[nofast]
fn getpeername(
&self,
out: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return uv_compat::UV_EBADF;
}
unsafe {
let mut storage = std::mem::MaybeUninit::<socket2::SockAddr>::uninit();
let mut len = std::mem::size_of::<socket2::SockAddr>() as i32;
let ret = uv_compat::uv_tcp_getpeername(
tcp,
storage.as_mut_ptr() as *mut _,
&mut len,
);
if ret != 0 {
return ret;
}
populate_sockaddr_object(scope, out, &storage.assume_init());
0
}
}
#[nofast]
fn getsockname(
&self,
out: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
let tcp = self.tcp_ptr();
if tcp.is_null() {
return uv_compat::UV_EBADF;
}
unsafe {
let mut storage = std::mem::MaybeUninit::<socket2::SockAddr>::uninit();
let mut len = std::mem::size_of::<socket2::SockAddr>() as i32;
let ret = uv_compat::uv_tcp_getsockname(
tcp,
storage.as_mut_ptr() as *mut _,
&mut len,
);
if ret != 0 {
return ret;
}
populate_sockaddr_object(scope, out, &storage.assume_init());
0
}
}
}
fn populate_sockaddr_object(
scope: &mut v8::PinScope,
obj: v8::Local<v8::Object>,
sock_addr: &socket2::SockAddr,
) {
let (address, port, family) = if let Some(addr) = sock_addr.as_socket_ipv4() {
(addr.ip().to_string(), addr.port(), "IPv4")
} else if let Some(addr) = sock_addr.as_socket_ipv6() {
(addr.ip().to_string(), addr.port(), "IPv6")
} else {
return;
};
let addr_key = v8::String::new(scope, "address").unwrap();
let addr_val = v8::String::new(scope, &address).unwrap();
obj.set(scope, addr_key.into(), addr_val.into());
let port_key = v8::String::new(scope, "port").unwrap();
let port_val = v8::Integer::new(scope, port as i32);
obj.set(scope, port_key.into(), port_val.into());
let family_key = v8::String::new(scope, "family").unwrap();
let family_val = v8::String::new(scope, family).unwrap();
obj.set(scope, family_key.into(), family_val.into());
}