use std::cell::Cell;
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::ffi::c_char;
use std::ptr::NonNull;
use std::rc::Rc;
use deno_core::CppgcBase;
use deno_core::CppgcInherits;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_core::error::ResourceError;
use deno_core::op2;
use deno_core::uv_compat;
use deno_core::uv_compat::UV_EBADF;
use deno_core::uv_compat::uv_buf_t;
use deno_core::uv_compat::uv_shutdown_t;
use deno_core::uv_compat::uv_stream_t;
use deno_core::uv_compat::uv_write_t;
use deno_core::v8;
use crate::ops::handle_wrap::AsyncWrap;
use crate::ops::handle_wrap::GlobalHandle;
use crate::ops::handle_wrap::HandleWrap;
use crate::ops::handle_wrap::OwnedPtr;
use crate::ops::handle_wrap::ProviderType;
use crate::ops::stream_wrap_state::ReadCallbackKey;
use crate::ops::stream_wrap_state::ReadCallbackRegistry;
use crate::ops::stream_wrap_state::ReadCallbackState;
use crate::ops::stream_wrap_state::ReadInterceptor;
use crate::ops::stream_wrap_state::RequestCallbackRegistry;
use crate::ops::stream_wrap_state::RequestCallbackState;
use crate::ops::stream_wrap_state::ShutdownRequestCallbackState;
use crate::ops::stream_wrap_state::WriteRequestCallbackState;
pub struct StreamBaseState {
pub array: v8::Global<v8::Int32Array>,
}
pub(crate) struct StreamHandleData {
pub js_handle: UnsafeCell<GlobalHandle<v8::Object>>,
pub isolate: UnsafeCell<v8::UnsafeRawIsolatePtr>,
pub bytes_read: Rc<Cell<u64>>,
pub desired_read_interceptor: Cell<Option<ReadInterceptor>>,
pub read_callbacks: RefCell<ReadCallbackRegistry>,
pub active_read: Cell<Option<ReadCallbackKey>>,
pub request_callbacks: RefCell<RequestCallbackRegistry>,
pub user_buffer: RefCell<Option<UserBuffer>>,
}
pub(crate) struct UserBuffer {
#[allow(
dead_code,
reason = "retention only — handle keeps the backing store alive"
)]
pub(crate) handle: v8::Global<v8::Uint8Array>,
pub(crate) ptr: *mut u8,
pub(crate) len: usize,
}
impl UserBuffer {
pub(crate) fn from_view(
scope: &mut v8::PinScope,
buffer: v8::Local<v8::Uint8Array>,
) -> Option<Self> {
let byte_length = buffer.byte_length();
let byte_offset = buffer.byte_offset();
let ab = buffer.buffer(scope)?;
let data_ptr = ab.data()?;
let ptr = unsafe { (data_ptr.as_ptr() as *mut u8).add(byte_offset) };
Some(Self {
handle: v8::Global::new(scope, buffer),
ptr,
len: byte_length,
})
}
}
#[op2(fast)]
pub fn op_stream_base_register_state(
state: &mut OpState,
array: v8::Local<v8::Int32Array>,
scope: &mut v8::PinScope,
) {
state.put(StreamBaseState {
array: v8::Global::new(scope, array),
});
}
#[repr(usize)]
enum StreamBaseStateFields {
ReadBytesOrError = 0,
ArrayBufferOffset = 1,
BytesWritten = 2,
LastWriteWasAsync = 3,
}
#[derive(CppgcBase, CppgcInherits)]
#[cppgc_inherits_from(AsyncWrap)]
#[repr(C)]
pub struct WriteWrap {
base: AsyncWrap,
req: OwnedPtr<uv_write_t>,
}
unsafe impl GarbageCollected for WriteWrap {
fn get_name(&self) -> &'static std::ffi::CStr {
c"WriteWrap"
}
fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
}
impl WriteWrap {
pub fn new(op_state: &mut OpState) -> Self {
Self {
base: AsyncWrap::create(op_state, ProviderType::WriteWrap as i32),
req: OwnedPtr::from_box(Box::new(uv_compat::new_write())),
}
}
pub fn req_ptr(&mut self) -> *mut uv_write_t {
self.req.as_mut_ptr()
}
}
#[derive(CppgcBase, CppgcInherits)]
#[cppgc_inherits_from(AsyncWrap)]
#[repr(C)]
pub struct ShutdownWrap {
base: AsyncWrap,
req: OwnedPtr<uv_shutdown_t>,
}
unsafe impl GarbageCollected for ShutdownWrap {
fn get_name(&self) -> &'static std::ffi::CStr {
c"ShutdownWrap"
}
fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
}
impl ShutdownWrap {
pub fn new(op_state: &mut OpState) -> Self {
Self {
base: AsyncWrap::create(op_state, ProviderType::ShutdownWrap as i32),
req: OwnedPtr::from_box(Box::new(uv_compat::new_shutdown())),
}
}
pub fn req_ptr(&mut self) -> *mut uv_shutdown_t {
self.req.as_mut_ptr()
}
}
#[derive(CppgcBase, CppgcInherits)]
#[repr(C)]
#[cppgc_inherits_from(HandleWrap)]
pub struct LibUvStreamWrap {
base: HandleWrap,
fd: Cell<i32>,
stream: Cell<*const uv_stream_t>,
bytes_read: Rc<Cell<u64>>,
bytes_written: Rc<Cell<u64>>,
handle_data: Box<StreamHandleData>,
reading_started: Cell<bool>,
}
impl LibUvStreamWrap {
pub fn new(base: HandleWrap, fd: i32, stream: *const uv_stream_t) -> Self {
let bytes_read = Rc::new(Cell::new(0));
Self {
base,
fd: Cell::new(fd),
stream: Cell::new(stream),
bytes_read: bytes_read.clone(),
bytes_written: Rc::new(Cell::new(0)),
handle_data: Box::new(StreamHandleData {
js_handle: UnsafeCell::new(GlobalHandle::default()),
isolate: UnsafeCell::new(v8::UnsafeRawIsolatePtr::null()),
bytes_read,
desired_read_interceptor: Cell::new(None),
read_callbacks: RefCell::new(ReadCallbackRegistry::default()),
active_read: Cell::new(None),
request_callbacks: RefCell::new(RequestCallbackRegistry::default()),
user_buffer: RefCell::new(None),
}),
reading_started: Cell::new(false),
}
}
#[inline]
pub fn stream_ptr(&self) -> *mut uv_stream_t {
self.stream.get() as *mut uv_stream_t
}
pub(crate) fn set_fd(&self, fd: i32) {
self.fd.set(fd);
}
pub(crate) fn get_fd(&self) -> i32 {
self.fd.get()
}
pub(crate) fn handle_wrap(&self) -> &HandleWrap {
&self.base
}
pub(crate) fn handle_data_ptr(&self) -> *mut std::ffi::c_void {
(&*self.handle_data as *const StreamHandleData)
.cast_mut()
.cast()
}
pub(crate) fn js_handle_global(
&self,
scope: &mut v8::PinScope,
) -> Option<v8::Global<v8::Object>> {
unsafe { (*self.handle_data.js_handle.get()).to_global(scope) }
}
#[allow(dead_code, reason = "used by upcoming TCPWrap/TLSWrap")]
pub(crate) fn set_js_handle(
&self,
handle: v8::Global<v8::Object>,
scope: &mut v8::PinScope,
) {
unsafe {
*self.handle_data.js_handle.get() =
GlobalHandle::new_strong(scope, v8::Local::new(scope, &handle));
*self.handle_data.isolate.get() = scope.as_raw_isolate_ptr();
}
}
pub(crate) fn clear_js_handle(&self) {
unsafe {
*self.handle_data.js_handle.get() = GlobalHandle::None;
}
}
pub fn make_handle_strong(&self, scope: &mut v8::PinScope) {
unsafe {
(*self.handle_data.js_handle.get()).make_strong(scope);
}
}
pub fn make_handle_weak(&self, scope: &mut v8::PinScope) {
unsafe {
(*self.handle_data.js_handle.get()).make_weak(scope);
}
}
fn install_read_state(&self, state: ReadCallbackState) {
let key = self.handle_data.read_callbacks.borrow_mut().insert(state);
self.handle_data.active_read.set(Some(key));
}
pub(crate) fn stable_handle_data(
stream: *mut uv_stream_t,
) -> Option<NonNull<StreamHandleData>> {
if stream.is_null() {
return None;
}
NonNull::new(unsafe { (*stream).data as *mut StreamHandleData })
}
#[allow(dead_code, reason = "used by upcoming TLSWrap")]
pub(crate) fn set_read_interceptor_for_stream(
stream: *mut uv_stream_t,
interceptor: Option<ReadInterceptor>,
) {
let Some(handle_data_ptr) = Self::stable_handle_data(stream) else {
return;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
handle_data.desired_read_interceptor.set(interceptor);
if let Some(key) = handle_data.active_read.get()
&& let Ok(mut callbacks) = handle_data.read_callbacks.try_borrow_mut()
{
let _ = callbacks.update_interceptor(key, interceptor);
}
}
#[allow(dead_code, reason = "used by upcoming TLSWrap")]
pub(crate) fn read_start_intercepted_for_stream(
stream: *mut uv_stream_t,
) -> i32 {
let Some(handle_data_ptr) = Self::stable_handle_data(stream) else {
return UV_EBADF;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
if handle_data.active_read.get().is_some() {
return 0;
}
let Ok(mut callbacks) = handle_data.read_callbacks.try_borrow_mut() else {
return 0;
};
let key = callbacks.insert(ReadCallbackState {
isolate: v8::UnsafeRawIsolatePtr::null(),
onread: None,
stream_base_state: None,
handle: None,
bytes_read: handle_data.bytes_read.clone(),
read_interceptor: handle_data.desired_read_interceptor.get(),
});
drop(callbacks);
handle_data.active_read.set(Some(key));
unsafe {
uv_compat::uv_read_start(stream, Some(on_uv_alloc), Some(on_uv_read))
}
}
pub(crate) fn read_stop_for_stream(stream: *mut uv_stream_t) -> i32 {
let Some(handle_data_ptr) = Self::stable_handle_data(stream) else {
return UV_EBADF;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
if let Some(key) = handle_data.active_read.take() {
if let Ok(mut callbacks) = handle_data.read_callbacks.try_borrow_mut() {
let _ = callbacks.remove(key);
}
}
unsafe { uv_compat::uv_read_stop(stream) }
}
fn read_start_with_handle(
&self,
this: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let stream = self.stream_ptr();
if stream.is_null() {
return UV_EBADF;
}
if self.reading_started.get() {
return 0;
}
let onread_key =
v8::String::new_external_onebyte_static(scope, b"onread").unwrap();
let Some(onread_val) = this.get(scope, onread_key.into()) else {
return UV_EBADF;
};
let Ok(onread) = v8::Local::<v8::Function>::try_from(onread_val) else {
return UV_EBADF;
};
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
self.install_read_state(ReadCallbackState {
isolate: unsafe { scope.as_raw_isolate_ptr() },
onread: Some(v8::Global::new(scope, onread)),
stream_base_state: Some(v8::Global::new(scope, state_array)),
handle: Some(v8::Global::new(scope, this)),
bytes_read: self.handle_data.bytes_read.clone(),
read_interceptor: self.handle_data.desired_read_interceptor.get(),
});
self.reading_started.set(true);
self.make_handle_strong(scope);
unsafe {
uv_compat::uv_read_start(stream, Some(on_uv_alloc), Some(on_uv_read))
}
}
pub(crate) fn read_stop_internal(&self) -> i32 {
let stream = self.stream_ptr();
if stream.is_null() {
return UV_EBADF;
}
if LibUvStreamWrap::stable_handle_data(stream).is_none() {
return UV_EBADF;
}
self.reading_started.set(false);
Self::read_stop_for_stream(stream)
}
}
unsafe impl GarbageCollected for LibUvStreamWrap {
fn get_name(&self) -> &'static std::ffi::CStr {
c"LibUvStreamWrap"
}
fn trace(&self, visitor: &mut v8::cppgc::Visitor) {
self.base.trace(visitor);
}
}
impl LibUvStreamWrap {
pub(crate) fn detach_stream(&self) {
self.base.clear_handle();
let stream = self.stream.get();
if !stream.is_null() {
unsafe {
(*(stream as *mut uv_stream_t)).data = std::ptr::null_mut();
}
self.stream.set(std::ptr::null());
}
}
}
const POOLED_BUF_SIZE: usize = 65536;
const POOLED_BUF_MAX: usize = 128;
thread_local! {
static READ_BUF_POOL: std::cell::RefCell<Vec<*mut u8>> =
const { std::cell::RefCell::new(Vec::new()) };
}
#[inline]
fn pool_acquire_buf() -> Option<*mut u8> {
READ_BUF_POOL.with(|p| p.borrow_mut().pop())
}
#[inline]
fn pool_release_buf(ptr: *mut u8) -> bool {
READ_BUF_POOL.with(|p| {
let mut pool = p.borrow_mut();
if pool.len() < POOLED_BUF_MAX {
pool.push(ptr);
true
} else {
false
}
})
}
unsafe extern "C" fn on_uv_alloc(
handle: *mut uv_compat::uv_handle_t,
suggested_size: usize,
buf: *mut uv_buf_t,
) {
let stream = handle as *mut uv_stream_t;
if let Some(handle_data_ptr) = LibUvStreamWrap::stable_handle_data(stream) {
let handle_data = unsafe { handle_data_ptr.as_ref() };
if let Ok(user_buffer) = handle_data.user_buffer.try_borrow()
&& let Some(user_buf) = user_buffer.as_ref()
{
unsafe {
(*buf).base = user_buf.ptr as *mut c_char;
(*buf).len = user_buf.len;
}
return;
}
}
let ptr = if suggested_size == POOLED_BUF_SIZE
&& let Some(ptr) = pool_acquire_buf()
{
ptr
} else {
let layout =
std::alloc::Layout::from_size_align(suggested_size, 1).unwrap();
unsafe { std::alloc::alloc(layout) }
};
if ptr.is_null() {
unsafe {
(*buf).base = std::ptr::null_mut();
(*buf).len = 0;
}
return;
}
unsafe {
(*buf).base = ptr as *mut c_char;
(*buf).len = suggested_size;
}
}
unsafe extern "C" fn on_uv_read(
stream: *mut uv_stream_t,
nread: isize,
buf: *const uv_buf_t,
) {
let Some(handle_data_ptr) = LibUvStreamWrap::stable_handle_data(stream)
else {
free_uv_buf(buf);
return;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
let Some(snapshot) = handle_data
.active_read
.get()
.and_then(|key| handle_data.read_callbacks.borrow().snapshot(key))
else {
if !user_owned_buf(handle_data, buf) {
free_uv_buf(buf);
}
return;
};
let user_owned = user_owned_buf(handle_data, buf);
if let Some(interceptor) = snapshot.read_interceptor {
if nread < 0 {
let _ = LibUvStreamWrap::read_stop_for_stream(stream);
} else {
if nread > 0 {
snapshot
.bytes_read
.set(snapshot.bytes_read.get() + nread as u64);
}
unsafe { (interceptor.callback)(interceptor.ptr, stream, nread, buf) };
if !user_owned {
free_uv_buf(buf);
}
return;
}
}
let Some(stream_base_state) = snapshot.stream_base_state else {
if !user_owned {
free_uv_buf(buf);
}
return;
};
let Some(onread_global) = snapshot.onread else {
if !user_owned {
free_uv_buf(buf);
}
return;
};
let Some(handle_global) = snapshot.handle else {
if !user_owned {
free_uv_buf(buf);
}
return;
};
let mut isolate =
unsafe { v8::Isolate::from_raw_isolate_ptr(snapshot.isolate) };
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 = v8::Local::new(handle_scope, context);
let scope = &mut v8::ContextScope::new(handle_scope, context);
if nread <= 0 {
if !user_owned {
free_uv_buf(buf);
}
if nread < 0 {
let state_array = v8::Local::new(scope, &stream_base_state);
state_array.set_index(
scope,
StreamBaseStateFields::ReadBytesOrError as u32,
v8::Integer::new(scope, nread as i32).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::ArrayBufferOffset as u32,
v8::Integer::new(scope, 0).into(),
);
let onread = v8::Local::new(scope, &onread_global);
let recv = v8::Local::new(scope, &handle_global);
let undef = v8::undefined(scope);
onread.call(scope, recv.into(), &[undef.into()]);
}
return;
}
snapshot
.bytes_read
.set(snapshot.bytes_read.get() + nread as u64);
let state_array = v8::Local::new(scope, &stream_base_state);
state_array.set_index(
scope,
StreamBaseStateFields::ReadBytesOrError as u32,
v8::Integer::new(scope, nread as i32).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::ArrayBufferOffset as u32,
v8::Integer::new(scope, 0).into(),
);
let nread_usize = nread as usize;
let onread = v8::Local::new(scope, &onread_global);
let recv = v8::Local::new(scope, &handle_global);
let caught_exception = {
v8::tc_scope!(tc, scope);
let arg: v8::Local<v8::Value> = if user_owned {
v8::undefined(tc).into()
} else {
let buf_ref = unsafe { &*buf };
let backing_store = unsafe {
v8::ArrayBuffer::new_backing_store_from_ptr(
buf_ref.base as *mut std::ffi::c_void,
nread_usize,
backing_store_deleter,
buf_ref.len as *mut std::ffi::c_void,
)
};
let ab = v8::ArrayBuffer::with_backing_store(tc, &backing_store.into());
ab.into()
};
let result = onread.call(tc, recv.into(), &[arg]);
if result.is_none() && tc.has_caught() {
let exc = tc.exception();
tc.reset();
exc
} else {
None
}
};
if let Some(exception) = caught_exception {
call_fatal_exception(scope, exception);
}
}
fn user_owned_buf(
handle_data: &StreamHandleData,
buf: *const uv_buf_t,
) -> bool {
if buf.is_null() {
return false;
}
let Ok(borrow) = handle_data.user_buffer.try_borrow() else {
return true;
};
let Some(user_buf) = borrow.as_ref() else {
return false;
};
let base = unsafe { (*buf).base };
std::ptr::eq(base as *const u8, user_buf.ptr)
}
fn call_fatal_exception(
scope: &mut v8::ContextScope<v8::HandleScope>,
exception: v8::Local<v8::Value>,
) {
let global = scope.get_current_context().global(scope);
let key = v8::String::new(scope, "reportError").unwrap();
if let Some(report_fn_val) = global.get(scope, key.into())
&& let Ok(report_fn) = v8::Local::<v8::Function>::try_from(report_fn_val)
{
let undef = v8::undefined(scope);
report_fn.call(scope, undef.into(), &[exception]);
}
}
pub(crate) fn free_uv_buf(buf: *const uv_buf_t) {
unsafe {
if !(*buf).base.is_null() && (*buf).len > 0 {
let len = (*buf).len;
let ptr = (*buf).base as *mut u8;
if len == POOLED_BUF_SIZE && pool_release_buf(ptr) {
return;
}
let layout = std::alloc::Layout::from_size_align(len, 1).unwrap();
std::alloc::dealloc(ptr, layout);
}
}
}
pub(crate) unsafe fn clone_context_from_uv_loop(
isolate: &mut v8::Isolate,
loop_ptr: *mut uv_compat::uv_loop_t,
) -> v8::Global<v8::Context> {
let raw = NonNull::new(unsafe { (*loop_ptr).data as *mut v8::Context })
.expect("uv loop missing registered V8 context");
let global = unsafe { v8::Global::from_raw(isolate, raw) };
let cloned = global.clone();
global.into_raw();
cloned
}
unsafe extern "C" fn backing_store_deleter(
data: *mut std::ffi::c_void,
_len: usize,
deleter_data: *mut std::ffi::c_void,
) {
let alloc_size = deleter_data as usize;
if !data.is_null() && alloc_size > 0 {
let ptr = data as *mut u8;
if alloc_size == POOLED_BUF_SIZE && pool_release_buf(ptr) {
return;
}
let layout = std::alloc::Layout::from_size_align(alloc_size, 1).unwrap();
unsafe { std::alloc::dealloc(ptr, layout) };
}
}
unsafe extern "C" fn after_uv_write(req: *mut uv_write_t, status: i32) {
let req = unsafe { Box::from_raw(req) };
let req_data = req.data;
let handle_data = LibUvStreamWrap::stable_handle_data(req.handle);
let Some(handle_data_ptr) = handle_data else {
return;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
let Some(RequestCallbackState::Write(cb_data)) =
handle_data.request_callbacks.borrow_mut().take(req_data)
else {
return;
};
let mut isolate =
unsafe { v8::Isolate::from_raw_isolate_ptr(cb_data.isolate) };
let loop_ptr = unsafe { (*req.handle).loop_ };
let context = unsafe { clone_context_from_uv_loop(&mut isolate, loop_ptr) };
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let state = v8::Local::new(scope, &cb_data.stream_base_state);
state.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, cb_data.bytes as f64).into(),
);
let req_obj = v8::Local::new(scope, &cb_data.req_wrap_obj);
let handle = v8::Local::new(scope, &cb_data.stream_handle);
let oncomplete_str =
v8::String::new_external_onebyte_static(scope, b"oncomplete").unwrap();
if let Some(Ok(oncomplete)) = req_obj
.get(scope, oncomplete_str.into())
.map(TryInto::<v8::Local<v8::Function>>::try_into)
{
let status_val = v8::Integer::new(scope, status);
let undef = v8::undefined(scope);
oncomplete.call(
scope,
req_obj.into(),
&[status_val.into(), handle.into(), undef.into()],
);
}
}
unsafe extern "C" fn after_uv_shutdown(req: *mut uv_shutdown_t, status: i32) {
let req = unsafe { Box::from_raw(req) };
let req_data = req.data;
let handle_data = LibUvStreamWrap::stable_handle_data(req.handle);
let Some(handle_data_ptr) = handle_data else {
return;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
let Some(RequestCallbackState::Shutdown(cb_data)) =
handle_data.request_callbacks.borrow_mut().take(req_data)
else {
return;
};
let mut isolate =
unsafe { v8::Isolate::from_raw_isolate_ptr(cb_data.isolate) };
let loop_ptr = unsafe { (*req.handle).loop_ };
let context = unsafe { clone_context_from_uv_loop(&mut isolate, loop_ptr) };
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let req_obj = v8::Local::new(scope, &cb_data.req_wrap_obj);
let handle = v8::Local::new(scope, &cb_data.stream_handle);
let oncomplete_str =
v8::String::new_external_onebyte_static(scope, b"oncomplete").unwrap();
if let Some(Ok(oncomplete)) = req_obj
.get(scope, oncomplete_str.into())
.map(TryInto::<v8::Local<v8::Function>>::try_into)
{
let status_val = v8::Integer::new(scope, status);
let undef = v8::undefined(scope);
oncomplete.call(
scope,
req_obj.into(),
&[status_val.into(), handle.into(), undef.into()],
);
}
}
enum StringEncoding {
Utf8,
Ascii,
Latin1,
Ucs2,
}
fn parse_encoding_no_alloc(
scope: &mut v8::PinScope,
encoding: v8::Local<v8::String>,
) -> StringEncoding {
let len = encoding.length();
if len == 0 || len > 8 {
return StringEncoding::Utf8;
}
let mut buf = [0u8; 8];
encoding.write_one_byte_v2(
scope,
0,
&mut buf[..len],
v8::WriteFlags::empty(),
);
match &buf[..len] {
b"latin1" | b"binary" => StringEncoding::Latin1,
b"ucs2" | b"ucs-2" | b"utf16le" | b"utf-16le" => StringEncoding::Ucs2,
b"ascii" => StringEncoding::Ascii,
_ => StringEncoding::Utf8,
}
}
fn encode_string_to_vec(
scope: &mut v8::PinScope,
string: v8::Local<v8::String>,
encoding: StringEncoding,
out: &mut Vec<u8>,
) {
match encoding {
StringEncoding::Utf8 => {
let len = string.utf8_length(scope);
let start = out.len();
out.reserve(len);
let written = string.write_utf8_uninit_v2(
scope,
&mut out.spare_capacity_mut()[..len],
v8::WriteFlags::kReplaceInvalidUtf8,
None,
);
unsafe { out.set_len(start + written) };
}
StringEncoding::Latin1 | StringEncoding::Ascii => {
let len = string.length();
let start = out.len();
out.reserve(len);
string.write_one_byte_uninit_v2(
scope,
0,
&mut out.spare_capacity_mut()[..len],
v8::WriteFlags::empty(),
);
unsafe { out.set_len(start + len) };
}
StringEncoding::Ucs2 => {
let len = string.length();
let mut buf = vec![0u16; len];
string.write_v2(scope, 0, &mut buf, v8::WriteFlags::empty());
out.reserve(len * 2);
for &ch in &buf {
out.extend_from_slice(&ch.to_le_bytes());
}
}
}
}
fn encoded_storage_size(
scope: &mut v8::PinScope,
string: v8::Local<v8::String>,
encoding: &StringEncoding,
) -> usize {
match encoding {
StringEncoding::Utf8 => string.utf8_length(scope),
StringEncoding::Latin1 | StringEncoding::Ascii => string.length(),
StringEncoding::Ucs2 => string.length() * 2,
}
}
fn encode_string_into_uninit(
scope: &mut v8::PinScope,
string: v8::Local<v8::String>,
encoding: StringEncoding,
out: &mut [std::mem::MaybeUninit<u8>],
) -> usize {
match encoding {
StringEncoding::Utf8 => {
let len = string.utf8_length(scope);
string.write_utf8_uninit_v2(
scope,
&mut out[..len],
v8::WriteFlags::kReplaceInvalidUtf8,
None,
)
}
StringEncoding::Latin1 | StringEncoding::Ascii => {
let len = string.length();
string.write_one_byte_uninit_v2(
scope,
0,
&mut out[..len],
v8::WriteFlags::empty(),
);
len
}
StringEncoding::Ucs2 => {
let len_chars = string.length();
#[cfg(target_endian = "little")]
{
let dst_ptr = out.as_mut_ptr();
if dst_ptr.align_offset(std::mem::align_of::<u16>()) == 0
&& out.len() >= len_chars * 2
{
let dst: &mut [u16] = unsafe {
std::slice::from_raw_parts_mut(dst_ptr as *mut u16, len_chars)
};
string.write_v2(scope, 0, dst, v8::WriteFlags::empty());
return len_chars * 2;
}
}
let mut tmp = vec![0u16; len_chars];
string.write_v2(scope, 0, &mut tmp, v8::WriteFlags::empty());
for (i, &ch) in tmp.iter().enumerate() {
let bytes = ch.to_le_bytes();
out[i * 2] = std::mem::MaybeUninit::new(bytes[0]);
out[i * 2 + 1] = std::mem::MaybeUninit::new(bytes[1]);
}
len_chars * 2
}
}
}
#[op2(base, inherit = HandleWrap)]
impl LibUvStreamWrap {
#[fast]
pub fn set_handle(
&self,
handle: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) {
unsafe {
*self.handle_data.js_handle.get() =
GlobalHandle::new_strong(scope, handle);
*self.handle_data.isolate.get() = scope.as_raw_isolate_ptr();
}
}
#[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> {
self.clear_js_handle();
self.base.close_handle(op_state, this, scope, cb)
}
#[fast]
pub fn read_start(
&self,
#[this] this: v8::Global<v8::Object>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let this = if let Some(handle) = self.js_handle_global(scope) {
v8::Local::new(scope, handle)
} else {
v8::Local::new(scope, &this)
};
self.read_start_with_handle(this, scope, op_state)
}
#[fast]
#[reentrant]
pub fn read_stop(&self, _scope: &mut v8::PinScope) -> i32 {
self.read_stop_internal()
}
#[fast]
#[rename("useUserBuffer")]
pub fn use_user_buffer(
&self,
buffer: v8::Local<v8::Uint8Array>,
scope: &mut v8::PinScope,
) {
*self.handle_data.user_buffer.borrow_mut() =
UserBuffer::from_view(scope, buffer);
}
#[fast]
#[rename("setBlocking")]
pub fn set_blocking(&self, enable: bool) -> i32 {
let stream = self.stream_ptr();
if stream.is_null() {
return UV_EBADF;
}
unsafe { uv_compat::uv_stream_set_blocking(stream, enable as i32) }
}
#[fast]
pub fn shutdown(
&self,
req_wrap_obj: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
let stream = self.stream_ptr();
if stream.is_null() {
return UV_EBADF;
}
let stream_handle = self
.js_handle_global(scope)
.unwrap_or_else(|| v8::Global::new(scope, v8::Object::new(scope)));
let mut req = Box::new(uv_compat::new_shutdown());
req.data = self.handle_data.request_callbacks.borrow_mut().insert(
RequestCallbackState::Shutdown(ShutdownRequestCallbackState {
isolate: unsafe { scope.as_raw_isolate_ptr() },
req_wrap_obj: v8::Global::new(scope, req_wrap_obj),
stream_handle,
}),
);
let req_ptr = Box::into_raw(req);
let err = unsafe {
uv_compat::uv_shutdown(req_ptr, stream, Some(after_uv_shutdown))
};
if err != 0 {
let req_data = unsafe { (*req_ptr).data };
unsafe {
(*req_ptr).data = std::ptr::null_mut();
}
let _ = self
.handle_data
.request_callbacks
.borrow_mut()
.take(req_data);
unsafe {
drop(Box::from_raw(req_ptr));
}
}
err
}
#[fast]
pub fn write_buffer(
&self,
req_wrap_obj: v8::Local<v8::Object>,
buffer: v8::Local<v8::Uint8Array>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let stream = self.stream_ptr();
if stream.is_null() {
return UV_EBADF;
}
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
let byte_length = buffer.byte_length();
self
.bytes_written
.set(self.bytes_written.get() + byte_length as u64);
let mut buf = [0; v8::TYPED_ARRAY_MAX_SIZE_IN_HEAP];
let stack_base: *const u8 = buf.as_ptr();
let data = buffer.get_contents(&mut buf);
let try_result = unsafe { uv_compat::uv_try_write(stream, data) };
if try_result >= 0 && try_result as usize == byte_length {
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, byte_length as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 0).into(),
);
return 0;
}
let (write_data, write_len) = if try_result > 0 {
let written = try_result as usize;
(&data[written..], byte_length - written)
} else {
(data, byte_length)
};
let stream_handle = self
.js_handle_global(scope)
.unwrap_or_else(|| v8::Global::new(scope, v8::Object::new(scope)));
let mut req = Box::new(uv_compat::new_write());
req.data = self.handle_data.request_callbacks.borrow_mut().insert(
RequestCallbackState::Write(WriteRequestCallbackState {
isolate: unsafe { scope.as_raw_isolate_ptr() },
req_wrap_obj: v8::Global::new(scope, req_wrap_obj),
stream_handle,
stream_base_state: v8::Global::new(scope, state_array),
bytes: byte_length,
owned_buffers: smallvec::SmallVec::new(),
}),
);
let req_ptr = Box::into_raw(req);
let on_stack = std::ptr::eq(data.as_ptr(), stack_base);
let err = if on_stack {
unsafe {
uv_compat::uv_write_owned(
req_ptr,
stream,
write_data.to_vec(),
Some(after_uv_write),
)
}
} else {
let iov: smallvec::SmallVec<[uv_buf_t; 4]> =
smallvec::smallvec![uv_buf_t {
base: write_data.as_ptr() as *mut c_char,
len: write_len,
}];
unsafe {
uv_compat::uv_writev_owned(req_ptr, stream, iov, Some(after_uv_write))
}
};
if err != 0 {
let req_data = unsafe { (*req_ptr).data };
unsafe {
(*req_ptr).data = std::ptr::null_mut();
}
let _ = self
.handle_data
.request_callbacks
.borrow_mut()
.take(req_data);
unsafe {
drop(Box::from_raw(req_ptr));
}
return err;
}
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, byte_length as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 1).into(),
);
0
}
#[fast]
pub fn writev(
&self,
req_wrap_obj: v8::Local<v8::Object>,
chunks: v8::Local<v8::Array>,
all_buffers: bool,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let stream = self.stream_ptr();
if stream.is_null() {
return UV_EBADF;
}
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
let array_len = chunks.length();
let (count, stride): (u32, u32) = if all_buffers {
(array_len, 1)
} else {
(array_len / 2, 2)
};
let mut string_size: usize = 0;
if !all_buffers {
for i in 0..count {
let Some(chunk) = chunks.get_index(scope, i * 2) else {
continue;
};
if TryInto::<v8::Local<v8::Uint8Array>>::try_into(chunk).is_ok() {
continue;
}
if let Ok(s) = TryInto::<v8::Local<v8::String>>::try_into(chunk) {
let enc = chunks
.get_index(scope, i * 2 + 1)
.and_then(|v| TryInto::<v8::Local<v8::String>>::try_into(v).ok())
.map(|e| parse_encoding_no_alloc(scope, e))
.unwrap_or(StringEncoding::Utf8);
string_size += encoded_storage_size(scope, s, &enc);
}
}
}
let mut string_storage: Box<[std::mem::MaybeUninit<u8>]> =
if string_size > 0 {
Box::new_uninit_slice(string_size)
} else {
Box::new_uninit_slice(0)
};
let string_storage_ptr: *mut std::mem::MaybeUninit<u8> =
string_storage.as_mut_ptr();
let mut iovecs: smallvec::SmallVec<[uv_buf_t; 16]> =
smallvec::SmallVec::new();
let mut str_offset: usize = 0;
for i in 0..count {
let Some(chunk) = chunks.get_index(scope, i * stride) else {
continue;
};
if let Ok(buf) = TryInto::<v8::Local<v8::Uint8Array>>::try_into(chunk) {
let byte_len = buf.byte_length();
if byte_len == 0 {
continue;
}
let byte_off = buf.byte_offset();
let ab = buf.buffer(scope).unwrap();
let Some(data_ptr) = ab.data() else {
continue;
};
let base = unsafe {
(data_ptr.as_ptr() as *mut u8).add(byte_off) as *mut c_char
};
iovecs.push(uv_buf_t {
base,
len: byte_len,
});
} else if let Ok(s) = TryInto::<v8::Local<v8::String>>::try_into(chunk) {
let enc = chunks
.get_index(scope, i * 2 + 1)
.and_then(|v| TryInto::<v8::Local<v8::String>>::try_into(v).ok())
.map(|e| parse_encoding_no_alloc(scope, e))
.unwrap_or(StringEncoding::Utf8);
let remaining = string_size.saturating_sub(str_offset);
if remaining == 0 {
continue;
}
let dst_slice = unsafe {
std::slice::from_raw_parts_mut(
string_storage_ptr.add(str_offset),
remaining,
)
};
let written = encode_string_into_uninit(scope, s, enc, dst_slice);
if written > 0 {
let base =
unsafe { string_storage_ptr.add(str_offset) as *mut c_char };
iovecs.push(uv_buf_t { base, len: written });
}
str_offset += written;
}
}
let total_bytes: usize = iovecs.iter().map(|b| b.len).sum();
self
.bytes_written
.set(self.bytes_written.get() + total_bytes as u64);
if total_bytes == 0 {
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Integer::new(scope, 0).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 0).into(),
);
return 0;
}
let (sync_written, fully_drained) = {
let slices: smallvec::SmallVec<[std::io::IoSlice; 16]> = iovecs
.iter()
.map(|b| {
unsafe {
std::io::IoSlice::new(std::slice::from_raw_parts(
b.base as *const u8,
b.len,
))
}
})
.collect();
let rc = unsafe { uv_compat::uv_try_writev(stream, &slices) };
if rc >= 0 {
let n = rc as usize;
(n, n == total_bytes)
} else {
(0, false)
}
};
if fully_drained {
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, total_bytes as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 0).into(),
);
return 0;
}
if sync_written > 0 {
let mut remaining = sync_written;
let mut consumed = 0;
while remaining > 0 && consumed < iovecs.len() {
let head_len = iovecs[consumed].len;
if remaining >= head_len {
remaining -= head_len;
consumed += 1;
} else {
iovecs[consumed].base = unsafe {
(iovecs[consumed].base as *mut u8).add(remaining) as *mut c_char
};
iovecs[consumed].len = head_len - remaining;
remaining = 0;
}
}
if consumed > 0 {
iovecs.drain(0..consumed);
}
}
let mut owned: smallvec::SmallVec<[Box<[std::mem::MaybeUninit<u8>]>; 1]> =
smallvec::SmallVec::new();
if !string_storage.is_empty() {
owned.push(string_storage);
}
self.do_writev_async(
scope,
stream,
iovecs,
owned,
total_bytes,
req_wrap_obj,
state_array,
)
}
#[fast]
#[reentrant]
pub fn write_utf8_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
self.write_string(
scope,
req_wrap_obj,
string,
state_array,
StringEncoding::Utf8,
)
}
#[fast]
pub fn write_ascii_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
self.write_string(
scope,
req_wrap_obj,
string,
state_array,
StringEncoding::Ascii,
)
}
#[fast]
pub fn write_latin1_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
self.write_string(
scope,
req_wrap_obj,
string,
state_array,
StringEncoding::Latin1,
)
}
#[fast]
pub fn write_ucs2_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
self.write_string(
scope,
req_wrap_obj,
string,
state_array,
StringEncoding::Ucs2,
)
}
#[fast]
#[no_side_effects]
pub fn get_bytes_read(&self) -> f64 {
self.bytes_read.get() as f64
}
#[fast]
#[no_side_effects]
pub fn get_bytes_written(&self) -> f64 {
self.bytes_written.get() as f64
}
}
impl LibUvStreamWrap {
fn write_string(
&self,
scope: &mut v8::PinScope,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
state_array: v8::Local<v8::Int32Array>,
encoding: StringEncoding,
) -> i32 {
let stream = self.stream_ptr();
if stream.is_null() {
return UV_EBADF;
}
let mut data = Vec::new();
encode_string_to_vec(scope, string, encoding, &mut data);
let total_bytes = data.len();
self
.bytes_written
.set(self.bytes_written.get() + total_bytes as u64);
if total_bytes <= 16384 {
let try_result = unsafe { uv_compat::uv_try_write(stream, &data) };
if try_result >= 0 && try_result as usize == total_bytes {
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, total_bytes as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 0).into(),
);
return 0;
}
if try_result > 0 {
let written = try_result as usize;
let tail = data.split_off(written);
return self.do_write(
scope,
stream,
tail,
total_bytes,
req_wrap_obj,
state_array,
);
}
}
self.do_write(scope, stream, data, total_bytes, req_wrap_obj, state_array)
}
fn do_write(
&self,
scope: &mut v8::PinScope,
stream: *mut uv_stream_t,
data: Vec<u8>,
total_bytes: usize,
req_wrap_obj: v8::Local<v8::Object>,
state_array: v8::Local<v8::Int32Array>,
) -> i32 {
let stream_handle = self
.js_handle_global(scope)
.unwrap_or_else(|| v8::Global::new(scope, v8::Object::new(scope)));
let mut req = Box::new(uv_compat::new_write());
req.data = self.handle_data.request_callbacks.borrow_mut().insert(
RequestCallbackState::Write(WriteRequestCallbackState {
isolate: unsafe { scope.as_raw_isolate_ptr() },
req_wrap_obj: v8::Global::new(scope, req_wrap_obj),
stream_handle,
stream_base_state: v8::Global::new(scope, state_array),
bytes: total_bytes,
owned_buffers: smallvec::SmallVec::new(),
}),
);
let req_ptr = Box::into_raw(req);
let err = unsafe {
uv_compat::uv_write_owned(
req_ptr,
stream as *mut _,
data,
Some(after_uv_write),
)
};
if err != 0 {
let req_data = unsafe { (*req_ptr).data };
unsafe {
(*req_ptr).data = std::ptr::null_mut();
}
let _ = self
.handle_data
.request_callbacks
.borrow_mut()
.take(req_data);
unsafe {
drop(Box::from_raw(req_ptr));
}
return err;
}
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, total_bytes as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 1).into(),
);
0
}
fn do_writev_async(
&self,
scope: &mut v8::PinScope,
stream: *mut uv_stream_t,
iovecs: smallvec::SmallVec<[uv_buf_t; 16]>,
owned_buffers: smallvec::SmallVec<[Box<[std::mem::MaybeUninit<u8>]>; 1]>,
total_bytes: usize,
req_wrap_obj: v8::Local<v8::Object>,
state_array: v8::Local<v8::Int32Array>,
) -> i32 {
let stream_handle = self
.js_handle_global(scope)
.unwrap_or_else(|| v8::Global::new(scope, v8::Object::new(scope)));
let mut req = Box::new(uv_compat::new_write());
req.data = self.handle_data.request_callbacks.borrow_mut().insert(
RequestCallbackState::Write(WriteRequestCallbackState {
isolate: unsafe { scope.as_raw_isolate_ptr() },
req_wrap_obj: v8::Global::new(scope, req_wrap_obj),
stream_handle,
stream_base_state: v8::Global::new(scope, state_array),
bytes: total_bytes,
owned_buffers,
}),
);
let req_ptr = Box::into_raw(req);
let queue_bufs: smallvec::SmallVec<[uv_buf_t; 4]> =
iovecs.into_iter().collect();
let err = unsafe {
uv_compat::uv_writev_owned(
req_ptr,
stream,
queue_bufs,
Some(after_uv_write),
)
};
if err != 0 {
let req_data = unsafe { (*req_ptr).data };
unsafe {
(*req_ptr).data = std::ptr::null_mut();
}
let _ = self
.handle_data
.request_callbacks
.borrow_mut()
.take(req_data);
unsafe {
drop(Box::from_raw(req_ptr));
}
return err;
}
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, total_bytes as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 1).into(),
);
0
}
}