use ::std::cell::{Cell, RefCell};
use ::std::collections::HashMap;
use ::std::net::{TcpListener, TcpStream, ToSocketAddrs};
use ::std::os::fd::AsRawFd;
use ::std::ptr::NonNull;
use ::std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use ::std::sync::{Arc, Mutex, OnceLock};
use ::std::time::{Duration, Instant};
use bun_core::ZBox;
use bao_boringssl_bridge::{
KeyFormat, PeerCertInfo, SslClientHello, TlsClient, TlsConnection, TlsError, TlsServer,
TlsState, pem_parse_certs, pem_parse_key, ssl_servername, SSL_SELECT_CERT_ERROR,
SSL_SELECT_CERT_RETRY, SSL_SELECT_CERT_SUCCESS,
};
use bao_engine::context::RawValueRootGuard;
use bun_boringssl_sys::boringssl::*;
use mozjs::jsapi::*;
use mozjs::jsval::{DoubleValue, Int32Value, JSVal, ObjectValue, StringValue, UndefinedValue};
use mozjs::realm::AutoRealm;
use mozjs::rooted;
use mozjs::rust::wrappers2 as w2;
use crate::node_events::{
ee_emit, ee_off, ee_on, ee_once, ee_prepend, ee_prepend_once, ee_remove_all,
};
use crate::require::cache_builtin;
struct SecureContextState {
key_der: Option<(KeyFormat, Vec<u8>)>,
cert_ders: Vec<Vec<u8>>, ca_certs: Vec<Vec<u8>>, pem_certs: Option<String>, pem_key: Option<String>, alpn_protos: Option<Vec<u8>>,
session_data: Option<Vec<u8>>,
}
impl SecureContextState {
fn new() -> Self {
Self {
key_der: None,
cert_ders: Vec::new(),
ca_certs: Vec::new(),
pem_certs: None,
pem_key: None,
alpn_protos: None,
session_data: None,
}
}
}
#[inline]
fn val_is_private(v: &JSVal) -> bool {
v.is_double() && (v.asBits_ & 0xFFFF000000000000) == 0
}
unsafe fn sc_state_ensure(cx: *mut JSContext, obj: *mut JSObject) -> *mut SecureContextState {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let mut slot_val = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
c"_scState".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut slot_val,
},
);
if val_is_private(&slot_val) {
let ptr = slot_val.to_private() as *mut SecureContextState;
if !ptr.is_null() {
return ptr;
}
}
let state = Box::new(SecureContextState::new());
let ptr = Box::into_raw(state) as *const core::ffi::c_void;
let pv = mozjs::jsval::PrivateValue(ptr);
rooted!(&in(cx_ref) let pv_h = pv);
JS_DefineProperty(
cx,
obj_root.handle().into(),
c"_scState".as_ptr(),
pv_h.handle().into(),
0,
);
ptr as *mut SecureContextState
}
unsafe fn sc_state_set_key(cx: *mut JSContext, obj: *mut JSObject, pem: &str) -> bool {
let key = pem_parse_key(pem);
if let Some(k) = key {
let state = sc_state_ensure(cx, obj);
(*state).key_der = Some(k);
(*state).pem_key = Some(pem.to_string());
true
} else {
false
}
}
unsafe fn sc_state_set_cert(cx: *mut JSContext, obj: *mut JSObject, pem: &str) -> bool {
let ders = pem_parse_certs(pem);
if ders.is_empty() {
return false;
}
let state = sc_state_ensure(cx, obj);
(*state).cert_ders = ders;
(*state).pem_certs = Some(pem.to_string());
true
}
unsafe fn sc_state_add_ca(cx: *mut JSContext, obj: *mut JSObject, pem: &str) -> bool {
let ders = pem_parse_certs(pem);
if ders.is_empty() {
return false;
}
let state = sc_state_ensure(cx, obj);
(*state).ca_certs.extend(ders);
true
}
unsafe fn sc_state_set_alpn_protos(
cx: *mut JSContext,
obj: *mut JSObject,
protos_val: JSVal,
) -> bool {
if !protos_val.is_object() {
return false;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let arr_obj = protos_val.to_object());
let mut wire = Vec::new();
let mut i: u32 = 0;
loop {
let mut elem = UndefinedValue();
JS_GetElement(
cx,
arr_obj.handle().into(),
i,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut elem,
},
);
if elem.is_undefined() {
break;
}
if elem.is_string() {
let proto = crate::js_to_rust_string(cx, elem);
if proto.len() > 255 {
continue; }
wire.push(proto.len() as u8);
wire.extend_from_slice(proto.as_bytes());
}
i += 1;
}
if wire.is_empty() {
return false;
}
let state = sc_state_ensure(cx, obj);
(*state).alpn_protos = Some(wire);
true
}
unsafe fn sc_state_set_session(
cx: *mut JSContext,
obj: *mut JSObject,
session_bytes: &[u8],
) -> bool {
let state = sc_state_ensure(cx, obj);
(*state).session_data = Some(session_bytes.to_vec());
true
}
unsafe fn sc_state_drop(cx: *mut JSContext, obj: *mut JSObject) {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let mut slot_val = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
c"_scState".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut slot_val,
},
);
if val_is_private(&slot_val) {
let ptr = slot_val.to_private() as *mut SecureContextState;
if !ptr.is_null() {
let _ = Box::from_raw(ptr);
}
rooted!(&in(cx_ref) let undef = UndefinedValue());
JS_DefineProperty(
cx,
obj_root.handle().into(),
c"_scState".as_ptr(),
undef.handle().into(),
0,
);
}
}
const SNI_DEADLINE: Duration = Duration::from_secs(120);
enum TlsEvent {
Connection {
conn_id: u64,
shared: Arc<ConnShared>,
},
SniRequest { conn_id: u64, servername: String },
SecureConnection { conn_id: u64, info: TlsSessionInfo },
Data { conn_id: u64, bytes: Vec<u8> },
End { conn_id: u64 },
Close { conn_id: u64 },
ClientError { conn_id: u64, message: String },
ServerClosed,
}
struct TlsSessionInfo {
servername: Option<String>,
alpn: Option<Vec<u8>>,
protocol: Option<String>,
cipher_name: Option<String>,
cipher_version: Option<String>,
peer_cert: Option<PeerCertInfo>,
}
struct ConnShared {
pending_writes: Mutex<Vec<Vec<u8>>>,
want_end: AtomicBool,
want_destroy: AtomicBool,
closed: AtomicBool,
sni_result: Mutex<Option<::std::result::Result<(String, String), String>>>,
}
impl ConnShared {
fn new() -> Self {
Self {
pending_writes: Mutex::new(Vec::new()),
want_end: AtomicBool::new(false),
want_destroy: AtomicBool::new(false),
closed: AtomicBool::new(false),
sni_result: Mutex::new(None),
}
}
}
enum ServerKind {
Listener,
ClientConnect,
}
struct ServerShared {
server_id: u64,
kind: ServerKind,
cx: *mut JSContext,
server_obj_root: Option<RawValueRootGuard>,
sni_fn_root: Option<RawValueRootGuard>,
client_conn: Option<Arc<ConnShared>>,
mini_loop_ptr: *const bun_event_loop::MiniEventLoop::MiniEventLoop<'static>,
concurrent_task: bun_event_loop::AnyTaskWithExtraContext::AnyTaskWithExtraContext,
task_scheduled: AtomicBool,
events: Mutex<Vec<TlsEvent>>,
closing: AtomicBool,
sni_ctx_cache: Mutex<HashMap<(String, String), Arc<TlsServer>>>,
alpn_wire: Option<&'static [u8]>,
}
unsafe impl Send for ServerShared {}
unsafe impl Sync for ServerShared {}
struct DriverConn {
conn_id: u64,
stream: TcpStream,
tls: TlsConnection,
shared: Arc<ConnShared>,
server: Arc<ServerShared>,
parked_for_sni: bool,
sni_requested: bool,
sni_servername: Option<String>,
sni_deadline: Option<Instant>,
out_buf: Vec<u8>,
secure_reported: bool,
finishing: bool,
close_notify_sent: bool,
}
struct DriverServer {
server_id: u64,
listener: TcpListener,
base: TlsServer,
shared: Arc<ServerShared>,
}
enum DriverCmd {
AddListener(TcpListener, Arc<ServerShared>, TlsServer),
AddClientConn(u64, TcpStream, Arc<ServerShared>, Arc<ConnShared>, TlsConnection),
RemoveServer(u64),
}
struct DriverHandle {
wake_fd: i32,
cmds: Mutex<Vec<DriverCmd>>,
}
static DRIVER: OnceLock<DriverHandle> = OnceLock::new();
static DRIVER_INIT: Mutex<()> = Mutex::new(());
static NEXT_TLS_ID: AtomicU64 = AtomicU64::new(1);
thread_local! {
static DRIVER_CURRENT_CONN: Cell<*mut DriverConn> = const { Cell::new(::std::ptr::null_mut()) };
}
thread_local! {
static TLS_SERVER_REGISTRY: RefCell<HashMap<u64, Arc<ServerShared>>> =
RefCell::new(HashMap::new());
}
struct JsConn {
shared: Arc<ConnShared>,
socket_root: Option<RawValueRootGuard>,
}
thread_local! {
static TLS_CONNS: RefCell<HashMap<u64, JsConn>> = RefCell::new(HashMap::new());
}
fn tls_driver_wake() {
if let Some(h) = DRIVER.get() {
let byte = [1u8];
unsafe {
let _ = libc::write(h.wake_fd, byte.as_ptr().cast::<core::ffi::c_void>(), 1);
}
}
}
fn tls_liveness_probe() -> bool {
TLS_SERVER_REGISTRY.with(|r| !r.borrow().is_empty())
}
fn tls_driver_acquire() -> Option<&'static DriverHandle> {
if let Some(h) = DRIVER.get() {
return Some(h);
}
let _init_guard = DRIVER_INIT.lock().unwrap();
if let Some(h) = DRIVER.get() {
return Some(h);
}
let mut fds = [-1i32; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return None;
}
unsafe {
let flags = libc::fcntl(fds[0], libc::F_GETFL);
if flags < 0
|| libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK) < 0
{
libc::close(fds[0]);
libc::close(fds[1]);
return None;
}
}
let handle = DriverHandle {
wake_fd: fds[1],
cmds: Mutex::new(Vec::new()),
};
let spawned = ::std::thread::Builder::new()
.name("bao-tls-driver".into())
.spawn(move || tls_driver_main(fds[0]));
match spawned {
Ok(_) => {
let _ = DRIVER.set(handle);
DRIVER.get()
}
Err(_) => {
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
None
}
}
}
fn tls_push_event(shared: &Arc<ServerShared>, ev: TlsEvent) {
shared.events.lock().unwrap().push(ev);
tls_schedule_tasklet(shared);
}
fn tls_schedule_tasklet(shared: &Arc<ServerShared>) {
if shared
.task_scheduled
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return;
}
let loop_ptr = shared.mini_loop_ptr;
if loop_ptr.is_null() {
shared.task_scheduled.store(false, Ordering::Release);
return;
}
unsafe {
let shared_ptr = Arc::as_ptr(shared) as *mut ServerShared;
let task_ptr = core::ptr::addr_of_mut!((*shared_ptr).concurrent_task);
(*task_ptr).from(shared_ptr, tls_event_tasklet_shim);
let mini = loop_ptr as *mut bun_event_loop::MiniEventLoop::MiniEventLoop<'static>;
if !bun_event_loop::ConcurrentWakeup::enqueue_task_concurrent_cross_thread(
mini,
NonNull::new_unchecked(task_ptr),
) {
shared.task_scheduled.store(false, Ordering::Release);
}
}
}
unsafe extern "C" fn tls_select_cert_cb(client_hello: *const SslClientHello) -> core::ffi::c_int {
let ssl = unsafe { (*client_hello).ssl };
let conn_ptr = DRIVER_CURRENT_CONN.with(|c| c.get());
if conn_ptr.is_null() {
log::error!("[tls] select-certificate callback without an active connection");
return SSL_SELECT_CERT_ERROR;
}
let conn = unsafe { &mut *conn_ptr };
if !conn.sni_requested {
match ssl_servername(ssl) {
Some(servername) => {
conn.sni_requested = true;
conn.sni_servername = Some(servername.clone());
conn.sni_deadline = Some(Instant::now() + SNI_DEADLINE);
tls_push_event(
&conn.server,
TlsEvent::SniRequest {
conn_id: conn.conn_id,
servername,
},
);
SSL_SELECT_CERT_RETRY
}
None => {
SSL_SELECT_CERT_SUCCESS
}
}
} else {
let result = conn.shared.sni_result.lock().unwrap().take();
match result {
Some(Ok((cert_pem, key_pem))) => match tls_sni_ctx_for(&conn.server, &cert_pem, &key_pem) {
Ok(ctx) => {
if unsafe { conn.tls.switch_ssl_ctx(ctx) } {
SSL_SELECT_CERT_SUCCESS
} else {
log::error!("[tls] SSL_set_SSL_CTX failed for SNI resolution");
tls_push_event(
&conn.server,
TlsEvent::ClientError {
conn_id: conn.conn_id,
message: "SNICallback: SSL_set_SSL_CTX failed".to_string(),
},
);
SSL_SELECT_CERT_ERROR
}
}
Err(msg) => {
log::error!("[tls] SNICallback credentials rejected: {}", msg);
tls_push_event(
&conn.server,
TlsEvent::ClientError {
conn_id: conn.conn_id,
message: format!("SNICallback credentials rejected: {}", msg),
},
);
SSL_SELECT_CERT_ERROR
}
},
Some(Err(msg)) => {
log::error!("[tls] SNICallback returned an error: {}", msg);
tls_push_event(
&conn.server,
TlsEvent::ClientError {
conn_id: conn.conn_id,
message: format!("SNICallback error: {}", msg),
},
);
SSL_SELECT_CERT_ERROR
}
None => SSL_SELECT_CERT_RETRY,
}
}
}
fn tls_sni_ctx_for(
shared: &Arc<ServerShared>,
cert_pem: &str,
key_pem: &str,
) -> ::std::result::Result<*mut SSL_CTX, String> {
let key = (cert_pem.to_string(), key_pem.to_string());
let mut cache = shared.sni_ctx_cache.lock().unwrap();
if let Some(existing) = cache.get(&key) {
return Ok(existing.ctx());
}
let server = TlsServer::new(cert_pem, key_pem).map_err(|e| e.to_string())?;
if let Some(wire) = shared.alpn_wire {
unsafe {
SSL_CTX_set_alpn_select_cb(
server.ctx(),
Some(alpn_select_callback),
wire.as_ptr() as *mut core::ffi::c_void,
);
}
}
let ctx = server.ctx();
cache.insert(key, Arc::new(server));
Ok(ctx)
}
fn tls_driver_main(wake_read_fd: i32) {
let mut servers: HashMap<u64, DriverServer> = HashMap::new();
let mut conns: HashMap<u64, DriverConn> = HashMap::new();
let mut remove_queue: Vec<u64> = Vec::new();
let bootstrap_deadline = Instant::now() + Duration::from_secs(5);
let handle = loop {
match DRIVER.get() {
Some(h) => break h,
None if Instant::now() < bootstrap_deadline => {
::std::thread::sleep(Duration::from_millis(1));
}
None => return,
}
};
loop {
{
let mut cmds = handle.cmds.lock().unwrap();
for cmd in cmds.drain(..) {
match cmd {
DriverCmd::AddListener(listener, shared, base) => {
let id = shared.server_id;
servers.insert(id, DriverServer {
server_id: id,
listener,
base,
shared,
});
}
DriverCmd::AddClientConn(conn_id, stream, shared, conn_shared, tls) => {
stream
.set_nonblocking(true)
.expect("client stream nonblocking");
let mut conn = DriverConn {
conn_id,
stream,
tls,
shared: conn_shared,
server: shared,
parked_for_sni: false,
sni_requested: false,
sni_servername: None,
sni_deadline: None,
out_buf: Vec::new(),
secure_reported: false,
finishing: false,
close_notify_sent: false,
};
if !tls_conn_drive(&mut conn) {
tls_conn_finish(&mut conn, true);
} else {
conns.insert(conn_id, conn);
}
}
DriverCmd::RemoveServer(id) => remove_queue.push(id),
}
}
}
for id in remove_queue.drain(..) {
if let Some(ds) = servers.remove(&id) {
let dead: Vec<u64> = conns
.values()
.filter(|c| c.server.server_id == id)
.map(|c| c.conn_id)
.collect();
for cid in dead {
if let Some(mut conn) = conns.remove(&cid) {
tls_conn_finish(&mut conn, true);
}
}
drop(ds.listener);
tls_push_event(&ds.shared, TlsEvent::ServerClosed);
}
}
enum Target {
Wake,
Listener(u64),
Conn(u64),
}
let mut fds: Vec<libc::pollfd> = Vec::with_capacity(2 + servers.len() + conns.len());
let mut targets: Vec<Target> = Vec::with_capacity(fds.capacity());
fds.push(libc::pollfd {
fd: wake_read_fd,
events: libc::POLLIN,
revents: 0,
});
targets.push(Target::Wake);
for ds in servers.values() {
fds.push(libc::pollfd {
fd: ds.listener.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
});
targets.push(Target::Listener(ds.server_id));
}
for conn in conns.values() {
let mut events = 0;
if !conn.parked_for_sni && !conn.finishing {
events |= libc::POLLIN;
}
if !conn.out_buf.is_empty() {
events |= libc::POLLOUT;
}
if events != 0 {
fds.push(libc::pollfd {
fd: conn.stream.as_raw_fd(),
events,
revents: 0,
});
targets.push(Target::Conn(conn.conn_id));
}
}
let mut timeout_ms: i32 = -1;
for conn in conns.values() {
if conn.parked_for_sni {
if let Some(deadline) = conn.sni_deadline {
let remain = deadline.saturating_duration_since(Instant::now());
let ms = remain.as_millis() as i32;
if timeout_ms < 0 || ms < timeout_ms {
timeout_ms = ms;
}
}
}
}
if timeout_ms != -1 && timeout_ms < 0 {
timeout_ms = 0;
}
let ready = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, timeout_ms) };
if ready > 0 {
if fds[0].revents != 0 {
let mut buf = [0u8; 64];
while unsafe { libc::read(wake_read_fd, buf.as_mut_ptr().cast(), buf.len()) } > 0 {}
}
}
if ready > 0 {
for i in 1..fds.len() {
let revents = fds[i].revents;
if revents == 0 {
continue;
}
match &targets[i] {
Target::Wake => {}
Target::Listener(server_id) => {
if revents & libc::POLLIN != 0 {
tls_driver_accept(*server_id, &mut servers, &mut conns);
}
}
Target::Conn(conn_id) => {
let conn_id = *conn_id;
if conns.get(&conn_id).is_none() {
continue;
}
{
let conn = conns.get_mut(&conn_id).unwrap();
if revents & libc::POLLOUT != 0 {
tls_conn_flush_out(conn);
}
}
if revents & libc::POLLIN != 0 {
let conn = conns.get_mut(&conn_id).unwrap();
if !tls_conn_read_and_drive(conn) {
if let Some(mut conn) = conns.remove(&conn_id) {
tls_conn_finish(&mut conn, true);
}
continue;
}
}
if revents & (libc::POLLERR | libc::POLLHUP | libc::POLLNVAL) != 0 {
let conn = conns.get_mut(&conn_id).unwrap();
if !tls_conn_read_and_drive(conn) {
if let Some(mut conn) = conns.remove(&conn_id) {
tls_conn_finish(&mut conn, true);
}
}
}
}
}
}
}
let conn_ids: Vec<u64> = conns.keys().copied().collect();
for conn_id in conn_ids {
let Some(conn) = conns.get_mut(&conn_id) else {
continue;
};
if conn.shared.want_destroy.load(Ordering::Acquire) {
if let Some(mut conn) = conns.remove(&conn_id) {
tls_conn_finish(&mut conn, true);
}
continue;
}
if conn.shared.want_end.load(Ordering::Acquire) && !conn.finishing {
conn.finishing = true;
tls_conn_flush_pending_writes(conn);
if !conn.close_notify_sent {
let _ = conn.tls.queue_close_notify();
conn.out_buf.extend(conn.tls.take_outgoing());
conn.close_notify_sent = true;
}
}
if conn.parked_for_sni {
let resolved = conn
.shared
.sni_result
.lock()
.unwrap()
.as_ref()
.map(|_| ());
if resolved.is_some() {
conn.parked_for_sni = false;
if !tls_conn_drive(conn) {
if let Some(mut conn) = conns.remove(&conn_id) {
tls_conn_finish(&mut conn, true);
}
continue;
}
} else if conn
.sni_deadline
.map(|d| Instant::now() >= d)
.unwrap_or(false)
{
tls_push_event(
&conn.server,
TlsEvent::ClientError {
conn_id,
message: format!(
"SNICallback for '{}' did not resolve within {}s",
conn.sni_servername.clone().unwrap_or_default(),
SNI_DEADLINE.as_secs()
),
},
);
if let Some(mut conn) = conns.remove(&conn_id) {
tls_conn_finish(&mut conn, true);
}
continue;
}
}
if conn.secure_reported && !conn.finishing {
tls_conn_flush_pending_writes(conn);
}
if !conn.out_buf.is_empty() {
tls_conn_flush_out(conn);
}
if conn.finishing && conn.out_buf.is_empty() && conn.close_notify_sent {
if let Some(mut conn) = conns.remove(&conn_id) {
tls_conn_finish(&mut conn, true);
}
}
}
}
}
fn tls_driver_accept(
server_id: u64,
servers: &mut HashMap<u64, DriverServer>,
conns: &mut HashMap<u64, DriverConn>,
) {
let Some(ds) = servers.get(&server_id) else {
return;
};
loop {
match ds.listener.accept() {
Ok((stream, _addr)) => {
let _ = stream.set_nonblocking(true);
let conn_id = NEXT_TLS_ID.fetch_add(1, Ordering::Relaxed);
let tls = match ds.base.accept() {
Ok(t) => t,
Err(e) => {
log::error!("[tls] accept: TlsConnection setup failed: {}", e);
continue;
}
};
let shared = Arc::new(ConnShared::new());
conns.insert(
conn_id,
DriverConn {
conn_id,
stream,
tls,
shared: Arc::clone(&shared),
server: Arc::clone(&ds.shared),
parked_for_sni: false,
sni_requested: false,
sni_servername: None,
sni_deadline: None,
out_buf: Vec::new(),
secure_reported: false,
finishing: false,
close_notify_sent: false,
},
);
tls_push_event(
&ds.shared,
TlsEvent::Connection {
conn_id,
shared: Arc::clone(&shared),
},
);
}
Err(e) if e.kind() == ::std::io::ErrorKind::WouldBlock => break,
Err(_) => break,
}
}
}
fn tls_conn_read_and_drive(conn: &mut DriverConn) -> bool {
let mut buf = [0u8; 16 * 1024];
loop {
let n = unsafe {
libc::read(
conn.stream.as_raw_fd(),
buf.as_mut_ptr().cast::<core::ffi::c_void>(),
buf.len(),
)
};
if n > 0 {
conn.tls.feed(&buf[..n as usize]);
continue;
}
if n == 0 {
let ok = tls_conn_drive(conn);
if ok && conn.secure_reported && !conn.finishing {
return false;
}
return ok;
}
let err = ::std::io::Error::last_os_error();
match err.kind() {
::std::io::ErrorKind::WouldBlock => break,
::std::io::ErrorKind::Interrupted => continue,
_ => return false,
}
}
tls_conn_drive(conn)
}
fn tls_conn_drive(conn: &mut DriverConn) -> bool {
DRIVER_CURRENT_CONN.with(|c| c.set(conn as *mut DriverConn));
let result = conn.tls.process();
DRIVER_CURRENT_CONN.with(|c| c.set(::std::ptr::null_mut()));
let res = match result {
Ok(r) => r,
Err(e) => {
tls_push_event(
&conn.server,
TlsEvent::ClientError {
conn_id: conn.conn_id,
message: format!("TLS handshake/protocol error: {}", e),
},
);
return false;
}
};
conn.out_buf.extend(conn.tls.take_outgoing());
match res.state {
TlsState::PendingCertificate => {
conn.parked_for_sni = true;
true
}
TlsState::Handshaking => true,
TlsState::Active | TlsState::PeerClosed | TlsState::Closed => {
if !conn.secure_reported {
conn.secure_reported = true;
tls_conn_flush_pending_writes(conn);
let info = TlsSessionInfo {
servername: conn.sni_servername.clone().or_else(|| conn.tls.servername()),
alpn: conn.tls.alpn_protocol().map(|a| a.to_vec()),
protocol: conn.tls.protocol_version(),
cipher_name: conn.tls.cipher_name(),
cipher_version: conn.tls.cipher_version(),
peer_cert: conn.tls.peer_cert_info(),
};
tls_push_event(
&conn.server,
TlsEvent::SecureConnection {
conn_id: conn.conn_id,
info,
},
);
}
if !res.plaintext.is_empty() {
let mut bytes = Vec::new();
for chunk in res.plaintext {
bytes.extend_from_slice(&chunk);
}
tls_push_event(
&conn.server,
TlsEvent::Data {
conn_id: conn.conn_id,
bytes,
},
);
}
if res.state == TlsState::PeerClosed || res.state == TlsState::Closed {
tls_push_event(&conn.server, TlsEvent::End { conn_id: conn.conn_id });
conn.finishing = true;
let _ = conn.tls.queue_close_notify();
conn.out_buf.extend(conn.tls.take_outgoing());
conn.close_notify_sent = true;
}
true
}
}
}
fn tls_conn_flush_pending_writes(conn: &mut DriverConn) {
let chunks: Vec<Vec<u8>> = {
let mut g = conn.shared.pending_writes.lock().unwrap();
::std::mem::take(&mut *g)
};
for chunk in chunks {
if chunk.is_empty() {
continue;
}
match conn.tls.write(&chunk) {
Ok(_) => {}
Err(TlsError::NotReady) => {
let mut g = conn.shared.pending_writes.lock().unwrap();
g.insert(0, chunk);
break;
}
Err(e) => {
tls_push_event(
&conn.server,
TlsEvent::ClientError {
conn_id: conn.conn_id,
message: format!("TLS write failed: {}", e),
},
);
}
}
}
conn.out_buf.extend(conn.tls.take_outgoing());
}
fn tls_conn_flush_out(conn: &mut DriverConn) {
while !conn.out_buf.is_empty() {
let n = unsafe {
libc::write(
conn.stream.as_raw_fd(),
conn.out_buf.as_ptr().cast::<core::ffi::c_void>(),
conn.out_buf.len(),
)
};
if n > 0 {
conn.out_buf.drain(..n as usize);
continue;
}
if n == 0 {
break;
}
let err = ::std::io::Error::last_os_error();
match err.kind() {
::std::io::ErrorKind::WouldBlock => break,
::std::io::ErrorKind::Interrupted => continue,
_ => {
conn.out_buf.clear();
break;
}
}
}
}
fn tls_conn_finish(conn: &mut DriverConn, notify: bool) {
conn.shared.closed.store(true, Ordering::Release);
if notify {
tls_push_event(&conn.server, TlsEvent::Close { conn_id: conn.conn_id });
}
}
fn tls_event_tasklet_shim(ctx: *mut ServerShared, _parent: *mut ()) {
unsafe { tls_event_tasklet(ctx) };
}
unsafe fn tls_event_tasklet(ptr: *mut ServerShared) {
let s = unsafe { &mut *ptr };
s.task_scheduled.store(false, Ordering::Release);
let events = {
let mut g = s.events.lock().unwrap();
::std::mem::take(&mut *g)
};
if events.is_empty() {
return;
}
let cx = s.cx;
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let server_val = s
.server_obj_root
.as_ref()
.map(|g| g.get(0))
.unwrap_or_else(UndefinedValue);
if !server_val.is_object() {
return;
}
rooted!(&in(cx_ref) let server_root = server_val.to_object());
{
let mut realm = AutoRealm::new_from_handle(cx_ref, server_root.handle());
let realm_cx: &mut mozjs::context::JSContext = &mut realm;
for ev in events {
match ev {
TlsEvent::Connection { conn_id, shared } => {
let socket = tls_build_socket_js(realm_cx.raw_cx(), conn_id);
if socket.is_null() {
log::error!("[tls] failed to build TLSSocket object for conn {}", conn_id);
continue;
}
let socket_val = ObjectValue(socket);
if let Some(guard) = RawValueRootGuard::new(
cx,
::std::slice::from_ref(&socket_val),
c"TLSSocket.object",
) {
let live = guard.get(0);
TLS_CONNS.with(|m| {
m.borrow_mut().insert(
conn_id,
JsConn {
shared,
socket_root: Some(guard),
},
);
});
tls_emit_js(cx, server_root.get(), "connection", &[live]);
}
}
TlsEvent::SniRequest { conn_id, servername } => {
let sni_fn_val = s
.sni_fn_root
.as_ref()
.map(|g| g.get(0))
.unwrap_or_else(UndefinedValue);
if !sni_fn_val.is_object() {
log::error!("[tls] SniRequest without a rooted SNICallback (conn {})", conn_id);
continue;
}
tls_dispatch_sni_callback(
cx,
realm_cx,
server_root.get(),
sni_fn_val,
conn_id,
&servername,
);
}
TlsEvent::SecureConnection { conn_id, info } => {
let TlsSessionInfo {
servername,
alpn,
protocol,
cipher_name,
cipher_version,
peer_cert,
} = info;
let is_client = matches!(s.kind, ServerKind::ClientConnect);
let mut socket_ptr = tls_socket_ptr_for(conn_id);
if socket_ptr.is_null() && is_client {
let socket = tls_build_socket_js(realm_cx.raw_cx(), conn_id);
if socket.is_null() {
log::error!("[tls] failed to build client TLSSocket for conn {}", conn_id);
tls_reject_promise(cx, realm_cx, server_root.get(), "tls: failed to build socket object");
continue;
}
let socket_val = ObjectValue(socket);
match RawValueRootGuard::new(
cx,
::std::slice::from_ref(&socket_val),
c"TLSSocket.object",
) {
Some(guard) => {
let live = guard.get(0);
let shared = s
.client_conn
.as_ref()
.map(Arc::clone)
.unwrap_or_else(|| Arc::new(ConnShared::new()));
TLS_CONNS.with(|m| {
m.borrow_mut().insert(
conn_id,
JsConn {
shared,
socket_root: Some(guard),
},
);
});
socket_ptr = live.to_object();
}
None => {
tls_reject_promise(cx, realm_cx, server_root.get(), "tls: failed to root socket object");
continue;
}
}
}
if socket_ptr.is_null() {
tls_emit_js(cx, server_root.get(), "secureConnection", &[]);
continue;
}
rooted!(&in(realm_cx) let sock = socket_ptr);
if let Some(name) = &servername {
tls_define_str_prop(cx, sock.get(), "servername", name);
}
if let Some(proto) = &alpn {
let p = String::from_utf8_lossy(proto).to_string();
tls_define_str_prop(cx, sock.get(), "_alpnProtocol", &p);
}
if let Some(p) = &protocol {
tls_define_str_prop(cx, sock.get(), "_tlsProtocol", p);
}
if let Some(n) = &cipher_name {
tls_define_str_prop(cx, sock.get(), "_tlsCipherName", n);
}
if let Some(v) = &cipher_version {
tls_define_str_prop(cx, sock.get(), "_tlsCipherVersion", v);
}
if let Some(cert) = &peer_cert {
tls_define_peer_cert_prop(cx, realm_cx, sock.get(), cert);
}
let socket_val = TLS_CONNS
.with(|m| {
m.borrow()
.get(&conn_id)
.and_then(|e| e.socket_root.as_ref().map(|g| g.get(0)))
})
.unwrap_or_else(|| ObjectValue(socket_ptr));
if is_client {
rooted!(&in(realm_cx) let sock_h = socket_ptr);
rooted!(&in(realm_cx) let undef = UndefinedValue());
JS_DefineProperty(
cx,
sock_h.handle().into(),
c"then".as_ptr(),
undef.handle().into(),
0,
);
rooted!(&in(realm_cx) let sv = socket_val);
JS::ResolvePromise(cx, server_root.handle().into(), sv.handle().into());
tls_emit_js(cx, socket_ptr, "secureConnect", &[]);
} else {
tls_emit_js(cx, server_root.get(), "secureConnection", &[socket_val]);
}
}
TlsEvent::Data { conn_id, bytes } => {
let socket_val =
TLS_CONNS.with(|m| m.borrow().get(&conn_id).and_then(|e| e.socket_root.as_ref().map(|g| g.get(0))));
let Some(socket_val) = socket_val else { continue };
let payload = tls_bytes_to_array_buffer(cx, &bytes);
if payload.is_null() {
continue;
}
tls_emit_js(cx, socket_val.to_object(), "data", &[ObjectValue(payload)]);
}
TlsEvent::End { conn_id } => {
let socket_val =
TLS_CONNS.with(|m| m.borrow().get(&conn_id).and_then(|e| e.socket_root.as_ref().map(|g| g.get(0))));
let Some(socket_val) = socket_val else { continue };
tls_emit_js(cx, socket_val.to_object(), "end", &[]);
}
TlsEvent::Close { conn_id } => {
let entry = TLS_CONNS.with(|m| m.borrow_mut().remove(&conn_id));
if let Some(entry) = entry {
let socket_val = entry
.socket_root
.as_ref()
.map(|g| g.get(0))
.unwrap_or_else(UndefinedValue);
if socket_val.is_object() {
tls_emit_js(cx, socket_val.to_object(), "close", &[]);
}
drop(entry);
}
if matches!(s.kind, ServerKind::ClientConnect) {
drop(s.server_obj_root.take());
TLS_SERVER_REGISTRY.with(|r| {
r.borrow_mut().remove(&s.server_id);
});
}
}
TlsEvent::ClientError { conn_id, message } => {
let err_obj = tls_build_error_js(cx, &message);
let socket_val =
TLS_CONNS.with(|m| m.borrow().get(&conn_id).and_then(|e| e.socket_root.as_ref().map(|g| g.get(0))));
if matches!(s.kind, ServerKind::ClientConnect) {
if let Some(sv) = socket_val.filter(|v| v.is_object()) {
tls_emit_js(cx, sv.to_object(), "error", &[ObjectValue(err_obj)]);
}
tls_reject_promise(cx, realm_cx, server_root.get(), &message);
} else if let Some(sv) = socket_val.filter(|v| v.is_object()) {
tls_emit_js(cx, server_root.get(), "tlsClientError", &[ObjectValue(err_obj), sv]);
} else {
tls_emit_js(cx, server_root.get(), "tlsClientError", &[ObjectValue(err_obj)]);
}
}
TlsEvent::ServerClosed => {
tls_emit_js(cx, server_root.get(), "close", &[]);
let mut cb_val = UndefinedValue();
JS_GetProperty(
cx,
server_root.handle().into(),
c"_closeCb".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cb_val,
},
);
if cb_val.is_object() {
rooted!(&in(realm_cx) let cb_root = cb_val);
let mut rval = UndefinedValue();
JS_CallFunctionValue(
cx,
server_root.handle().into(),
cb_root.handle().into(),
&HandleValueArray::empty(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
JS_ClearPendingException(cx);
}
drop(s.sni_fn_root.take());
drop(s.server_obj_root.take());
TLS_SERVER_REGISTRY.with(|r| {
r.borrow_mut().remove(&s.server_id);
});
}
}
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn tls_reject_promise(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
promise: *mut JSObject,
message: &str,
) {
let err_obj = tls_build_error_js(cx, message);
if err_obj.is_null() {
return;
}
rooted!(&in(cx_ref) let promise_root = promise);
rooted!(&in(cx_ref) let ev = ObjectValue(err_obj));
unsafe {
JS::RejectPromise(cx, promise_root.handle().into(), ev.handle().into());
}
}
fn tls_conn_shared_for(conn_id: u64) -> Option<Arc<ConnShared>> {
TLS_CONNS.with(|m| m.borrow().get(&conn_id).map(|e| Arc::clone(&e.shared)))
}
fn tls_socket_ptr_for(conn_id: u64) -> *mut JSObject {
TLS_CONNS.with(|m| {
m.borrow()
.get(&conn_id)
.and_then(|e| e.socket_root.as_ref().map(|g| g.get(0)))
.filter(|v| v.is_object())
.map(|v| v.to_object())
.unwrap_or(::std::ptr::null_mut())
})
}
unsafe fn tls_build_socket_js(cx: *mut JSContext, conn_id: u64) -> *mut JSObject {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let tls_mod = crate::gc_store::gc_store_get(cx, "builtin:tls").unwrap_or(::std::ptr::null_mut());
if tls_mod.is_null() {
return ::std::ptr::null_mut();
}
rooted!(&in(cx_ref) let mod_root = tls_mod);
let mut ctor_val = UndefinedValue();
JS_GetProperty(
cx,
mod_root.handle().into(),
c"TLSSocket".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ctor_val,
},
);
if !ctor_val.is_object() {
return ::std::ptr::null_mut();
}
rooted!(&in(cx_ref) let ctor = ctor_val.to_object());
let mut proto_val = UndefinedValue();
JS_GetProperty(
cx,
ctor.handle().into(),
c"prototype".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut proto_val,
},
);
if !proto_val.is_object() {
return ::std::ptr::null_mut();
}
rooted!(&in(cx_ref) let proto = proto_val.to_object());
let obj = w2::JS_NewObjectWithGivenProto(cx_ref, ::std::ptr::null(), proto.handle().into());
if obj.is_null() {
return ::std::ptr::null_mut();
}
rooted!(&in(cx_ref) let obj_root = obj);
rooted!(&in(cx_ref) let cid = DoubleValue(conn_id as f64));
JS_DefineProperty(
cx,
obj_root.handle().into(),
c"_connId".as_ptr(),
cid.handle().into(),
0,
);
rooted!(&in(cx_ref) let auth = mozjs::jsval::BooleanValue(false));
JS_DefineProperty(
cx,
obj_root.handle().into(),
c"authorized".as_ptr(),
auth.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let enc = mozjs::jsval::BooleanValue(true));
JS_DefineProperty(
cx,
obj_root.handle().into(),
c"encrypted".as_ptr(),
enc.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let destroyed = mozjs::jsval::BooleanValue(false));
JS_DefineProperty(
cx,
obj_root.handle().into(),
c"destroyed".as_ptr(),
destroyed.handle().into(),
JSPROP_ENUMERATE as u32,
);
obj_root.get()
}
unsafe fn tls_define_str_prop(cx: *mut JSContext, obj: *mut JSObject, name: &str, value: &str) {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let c_name = ZBox::from_bytes(name.as_bytes());
let c_val = ZBox::from_bytes(value.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_val.as_ptr());
if js_str.is_null() {
return;
}
rooted!(&in(cx_ref) let sv = mozjs::jsval::StringValue(&*js_str));
JS_DefineProperty(
cx,
obj_root.handle().into(),
c_name.as_ptr(),
sv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
unsafe fn tls_build_error_js(cx: *mut JSContext, message: &str) -> *mut JSObject {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let err_obj = w2::JS_NewPlainObject(cx_ref));
if err_obj.is_null() {
return ::std::ptr::null_mut();
}
tls_define_str_prop(cx, err_obj.get(), "message", message);
err_obj.get()
}
unsafe fn tls_bytes_to_array_buffer(cx: *mut JSContext, bytes: &[u8]) -> *mut JSObject {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let len = bytes.len();
let layout = ::std::alloc::Layout::from_size_align(len.max(1), 1)
.unwrap_or_else(|_| ::std::alloc::Layout::from_size_align(1, 1).unwrap());
let alloc = unsafe { ::std::alloc::alloc(layout) };
if alloc.is_null() {
return ::std::ptr::null_mut();
}
unsafe { ::std::ptr::copy_nonoverlapping(bytes.as_ptr(), alloc, len) };
let ab = w2::NewArrayBufferWithContents(cx_ref, len, alloc.cast::<core::ffi::c_void>());
if ab.is_null() {
unsafe { ::std::alloc::dealloc(alloc, layout) };
return ::std::ptr::null_mut();
}
ab
}
unsafe fn tls_emit_js(cx: *mut JSContext, obj: *mut JSObject, name: &str, args: &[JSVal]) {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let c_name = ZBox::from_bytes(name.as_bytes());
let name_str = JS_NewStringCopyZ(cx, c_name.as_ptr());
if name_str.is_null() {
return;
}
rooted!(&in(cx_ref) let name_val = mozjs::jsval::StringValue(&*name_str));
let mut emit_val = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
c"emit".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut emit_val,
},
);
if !emit_val.is_object() {
return;
}
rooted!(&in(cx_ref) let emit_root = emit_val);
let mut call_vals: Vec<JSVal> = Vec::with_capacity(args.len() + 1);
call_vals.push(name_val.get());
call_vals.extend_from_slice(args);
let call_args = HandleValueArray {
length_: call_vals.len(),
elements_: call_vals.as_ptr(),
};
let mut rval = UndefinedValue();
JS_CallFunctionValue(
cx,
obj_root.handle().into(),
emit_root.handle().into(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
JS_ClearPendingException(cx);
}
unsafe fn tls_dispatch_sni_callback(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
server_obj: *mut JSObject,
sni_fn_val: JSVal,
conn_id: u64,
servername: &str,
) {
if !sni_fn_val.is_object() {
return;
}
let cb_fn = JS_NewFunction(cx, Some(tls_sni_cb_native), 2, 0, c"onSNICallback".as_ptr());
if cb_fn.is_null() {
log::error!("[tls] SNICallback dispatch: JS_NewFunction failed (conn {})", conn_id);
return;
}
let cb_obj = JS_GetFunctionObject(cb_fn);
if cb_obj.is_null() {
return;
}
rooted!(&in(cx_ref) let cb_root = cb_obj);
rooted!(&in(cx_ref) let cid = DoubleValue(conn_id as f64));
JS_DefineProperty(cx, cb_root.handle().into(), c"_sniConnId".as_ptr(), cid.handle().into(), 0);
let c_servername = ZBox::from_bytes(servername.as_bytes());
let name_js = JS_NewStringCopyZ(cx, c_servername.as_ptr());
if name_js.is_null() {
return;
}
rooted!(&in(cx_ref) let name_val = mozjs::jsval::StringValue(&*name_js));
rooted!(&in(cx_ref) let server_root = server_obj);
rooted!(&in(cx_ref) let sni_root = sni_fn_val);
let call_args = HandleValueArray {
length_: 2,
elements_: [name_val.get(), ObjectValue(cb_obj)].as_ptr(),
};
let mut rval = UndefinedValue();
JS_CallFunctionValue(
cx,
server_root.handle().into(),
sni_root.handle().into(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
JS_ClearPendingException(cx);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_sni_cb_native(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let callee_v = args.calleev();
if !callee_v.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = callee_v.to_object());
let mut cid_val = UndefinedValue();
JS_GetProperty(
cx,
this_obj.handle().into(),
c"_sniConnId".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cid_val,
},
);
let Some(conn_id) = (if cid_val.is_double() {
Some(cid_val.to_double() as u64)
} else {
None
}) else {
args.rval().set(UndefinedValue());
return true;
};
let Some(shared) = tls_conn_shared_for(conn_id) else {
args.rval().set(UndefinedValue());
return true;
};
let err_val = if argc > 0 { *args.get(0).ptr } else { UndefinedValue() };
let result: ::std::result::Result<(String, String), String> = if !err_val.is_null_or_undefined() {
let msg = if err_val.is_object() {
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let wr = &mut wrapped;
rooted!(&in(wr) let err_obj = err_val.to_object());
let mut msg_val = UndefinedValue();
JS_GetProperty(
cx,
err_obj.handle().into(),
c"message".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut msg_val,
},
);
if msg_val.is_string() {
crate::js_to_rust_string(cx, msg_val)
} else {
"SNICallback error".to_string()
}
} else {
crate::js_to_rust_string(cx, err_val)
};
Err(msg)
} else {
let ctx_val = if argc > 1 { *args.get(1).ptr } else { UndefinedValue() };
tls_extract_credentials(cx, ctx_val)
};
*shared.sni_result.lock().unwrap() = Some(result);
tls_driver_wake();
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn tls_extract_credentials(cx: *mut JSContext, val: JSVal) -> ::std::result::Result<(String, String), String> {
if !val.is_object() {
return Err("SNICallback resolved without a SecureContext or {key, cert} object".to_string());
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = val.to_object());
let mut sc_val = UndefinedValue();
JS_GetProperty(
cx,
obj.handle().into(),
c"_scState".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut sc_val,
},
);
if val_is_private(&sc_val) {
let state = sc_val.to_private() as *mut SecureContextState;
if !state.is_null() {
let s = &*state;
if let (Some(cert), Some(key)) = (&s.pem_certs, &s.pem_key) {
return Ok((cert.clone(), key.clone()));
}
return Err("SecureContext passed to SNICallback has no cert/key loaded".to_string());
}
}
let get_str_prop = |name: &str| -> Option<String> {
let cname = ZBox::from_bytes(name.as_bytes());
let mut v = UndefinedValue();
JS_GetProperty(
cx,
obj.handle().into(),
cname.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut v,
},
);
if v.is_string() {
Some(crate::js_to_rust_string(cx, v))
} else {
None
}
};
match (get_str_prop("cert"), get_str_prop("key")) {
(Some(cert), Some(key)) => Ok((cert, key)),
_ => Err("SNICallback result must provide both cert and key".to_string()),
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn tls_collect_write_bytes(cx: *mut JSContext, v: JSVal) -> Option<Vec<u8>> {
if v.is_string() {
return Some(crate::js_to_rust_string(cx, v).into_bytes());
}
if !v.is_object() {
return None;
}
let obj = v.to_object();
let mut length: usize = 0;
let mut is_shared = false;
let mut data_ptr: *mut u8 = ::std::ptr::null_mut();
let unwrapped = mozjs_sys::jsapi::JS_GetObjectAsUint8Array(
obj,
&mut length,
&mut is_shared,
&mut data_ptr,
);
if !unwrapped.is_null() && !data_ptr.is_null() {
return Some(unsafe { ::std::slice::from_raw_parts(data_ptr, length) }.to_vec());
}
let mut ab_length: usize = 0;
let mut ab_data: *mut u8 = ::std::ptr::null_mut();
let ab_unwrapped = mozjs_sys::jsapi::JS::GetObjectAsArrayBuffer(obj, &mut ab_length, &mut ab_data);
if !ab_unwrapped.is_null() && !ab_data.is_null() {
return Some(unsafe { ::std::slice::from_raw_parts(ab_data, ab_length) }.to_vec());
}
if !ab_unwrapped.is_null() || !unwrapped.is_null() {
return Some(Vec::new());
}
None
}
pub fn install(cx: &mut mozjs::context::JSContext) {
crate::node_http::register_liveness_probe(tls_liveness_probe);
rooted!(&in(cx) let mod_obj = unsafe { w2::JS_NewPlainObject(cx) });
if mod_obj.get().is_null() {
return;
}
unsafe {
let raw = cx.raw_cx();
let ctor_fn = JS_NewFunction(
raw,
Some(tls_socket_ctor),
2,
JSFUN_CONSTRUCTOR,
c"TLSSocket".as_ptr(),
);
if !ctor_fn.is_null() {
let ctor_obj = JS_GetFunctionObject(ctor_fn);
rooted!(&in(cx) let cv = ObjectValue(ctor_obj));
JS_DefineProperty(
raw,
mod_obj.handle().into(),
c"TLSSocket".as_ptr(),
cv.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx) let proto = w2::JS_NewPlainObject(cx));
if !proto.get().is_null() {
w2::JS_DefineFunction(
cx,
proto.handle(),
c"write".as_ptr(),
Some(tls_socket_write),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"end".as_ptr(),
Some(tls_socket_end),
1,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"destroy".as_ptr(),
Some(tls_socket_destroy),
0,
0,
);
w2::JS_DefineFunction(cx, proto.handle(), c"on".as_ptr(), Some(ee_on), 2, 0);
w2::JS_DefineFunction(cx, proto.handle(), c"once".as_ptr(), Some(ee_once), 2, 0);
w2::JS_DefineFunction(cx, proto.handle(), c"emit".as_ptr(), Some(ee_emit), 1, 0);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"addListener".as_ptr(),
Some(ee_on),
2,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"removeListener".as_ptr(),
Some(ee_off),
2,
0,
);
w2::JS_DefineFunction(cx, proto.handle(), c"off".as_ptr(), Some(ee_off), 2, 0);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"removeAllListeners".as_ptr(),
Some(ee_remove_all),
0,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"prependListener".as_ptr(),
Some(ee_prepend),
2,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"prependOnceListener".as_ptr(),
Some(ee_prepend_once),
2,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"getProtocol".as_ptr(),
Some(tls_get_protocol),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"getCipher".as_ptr(),
Some(tls_get_cipher),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"getPeerCertificate".as_ptr(),
Some(tls_get_peer_cert),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"getFinished".as_ptr(),
Some(tls_socket_get_finished),
0,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"getPeerFinished".as_ptr(),
Some(tls_socket_get_peer_finished),
0,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"getSession".as_ptr(),
Some(tls_socket_get_session),
0,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"setEncoding".as_ptr(),
Some(tls_socket_set_encoding),
1,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"ref".as_ptr(),
Some(tls_socket_ref),
0,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"unref".as_ptr(),
Some(tls_socket_unref),
0,
0,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c"getALPNProtocol".as_ptr(),
Some(tls_socket_get_alpn),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
proto.handle(),
c" renegotiate".as_ptr(),
Some(tls_socket_noop_bool),
0,
0,
);
let proto_val = ObjectValue(proto.get());
rooted!(&in(cx) let pv = proto_val);
rooted!(&in(cx) let ctor_h = ctor_obj);
JS_DefineProperty(
raw,
ctor_h.handle().into(),
c"prototype".as_ptr(),
pv.handle().into(),
0,
);
rooted!(&in(cx) let ctor_val = ObjectValue(ctor_obj));
JS_DefineProperty(
raw,
proto.handle().into(),
c"constructor".as_ptr(),
ctor_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
w2::JS_DefineFunction(
cx,
mod_obj.handle(),
c"connect".as_ptr(),
Some(tls_connect),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
mod_obj.handle(),
c"createServer".as_ptr(),
Some(tls_create_server),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
mod_obj.handle(),
c"createSecureContext".as_ptr(),
Some(tls_create_secure_context),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
mod_obj.handle(),
c"getCiphers".as_ptr(),
Some(tls_get_ciphers),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
mod_obj.handle(),
c"checkServerIdentity".as_ptr(),
Some(tls_check_server_identity),
2,
JSPROP_ENUMERATE as u32,
);
let _ciphers_str =
"TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256";
let cs = JS_NewStringCopyZ(
raw,
c"TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256".as_ptr(),
);
if !cs.is_null() {
rooted!(&in(cx) let csv = mozjs::jsval::StringValue(&*cs));
JS_DefineProperty(
raw,
mod_obj.handle().into(),
c"DEFAULT_CIPHERS".as_ptr(),
csv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let minv = JS_NewStringCopyZ(raw, c"TLSv1.2".as_ptr());
if !minv.is_null() {
rooted!(&in(cx) let mv = mozjs::jsval::StringValue(&*minv));
JS_DefineProperty(
raw,
mod_obj.handle().into(),
c"DEFAULT_MIN_VERSION".as_ptr(),
mv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let maxv = JS_NewStringCopyZ(raw, c"TLSv1.3".as_ptr());
if !maxv.is_null() {
rooted!(&in(cx) let xmv = mozjs::jsval::StringValue(&*maxv));
JS_DefineProperty(
raw,
mod_obj.handle().into(),
c"DEFAULT_MAX_VERSION".as_ptr(),
xmv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
cache_builtin(cx, "tls", mod_obj.get());
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_ctor(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let callee_obj = args.calleev().to_object());
let mut proto_val = UndefinedValue();
JS_GetProperty(
cx,
callee_obj.handle().into(),
c"prototype".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut proto_val,
},
);
let proto_obj = if proto_val.is_object() {
proto_val.to_object()
} else {
::std::ptr::null_mut()
};
rooted!(&in(cx_ref) let proto_rooted = proto_obj);
rooted!(&in(cx_ref) let obj = if !proto_obj.is_null() {
unsafe { w2::JS_NewObjectWithGivenProto(cx_ref, ::std::ptr::null(), proto_rooted.handle().into()) }
} else {
w2::JS_NewPlainObject(cx_ref)
});
if obj.get().is_null() {
args.rval().set(UndefinedValue());
return false;
}
rooted!(&in(cx_ref) let auth = mozjs::jsval::BooleanValue(false));
JS_DefineProperty(
cx,
obj.handle().into(),
c"authorized".as_ptr(),
auth.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let enc = mozjs::jsval::BooleanValue(true));
JS_DefineProperty(
cx,
obj.handle().into(),
c"encrypted".as_ptr(),
enc.handle().into(),
JSPROP_ENUMERATE as u32,
);
if argc > 0 && (*args.get(0).ptr).is_object() {
rooted!(&in(cx_ref) let sock = (*args.get(0).ptr).to_object());
rooted!(&in(cx_ref) let sv = ObjectValue(sock.get()));
JS_DefineProperty(
cx,
obj.handle().into(),
c"_socket".as_ptr(),
sv.handle().into(),
0,
);
}
if argc > 1 && (*args.get(1).ptr).is_object() {
rooted!(&in(cx_ref) let opts = (*args.get(1).ptr).to_object());
let mut host_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"servername".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut host_val,
},
);
if host_val.is_string() {
rooted!(&in(cx_ref) let hv = host_val);
JS_DefineProperty(
cx,
obj.handle().into(),
c"servername".as_ptr(),
hv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let mut alpn_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"ALPNProtocols".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut alpn_val,
},
);
if alpn_val.is_object() {
rooted!(&in(cx_ref) let alpn_root = alpn_val);
JS_DefineProperty(
cx,
obj.handle().into(),
c"_alpnProtos".as_ptr(),
alpn_root.handle().into(),
0,
);
}
let mut session_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"session".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut session_val,
},
);
if !session_val.is_undefined() {
rooted!(&in(cx_ref) let session_root = session_val);
JS_DefineProperty(
cx,
obj.handle().into(),
c"_session".as_ptr(),
session_root.handle().into(),
0,
);
}
}
rooted!(&in(cx_ref) let refed = mozjs::jsval::BooleanValue(true));
JS_DefineProperty(
cx,
obj.handle().into(),
c"_refed".as_ptr(),
refed.handle().into(),
0,
);
args.rval().set(ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_connect(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let mut servername: Option<String> = None;
let mut reject_unauthorized = true;
let mut ca_pems: Vec<String> = Vec::new();
let (host, port) = if argc > 0 && (*args.get(0).ptr).is_object() {
rooted!(&in(cx_ref) let opts = (*args.get(0).ptr).to_object());
let mut h = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"host".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut h,
},
);
let host = if h.is_string() {
crate::js_to_rust_string(cx, h)
} else {
"localhost".to_string()
};
let mut p = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"port".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut p,
},
);
let port = if p.is_int32() {
p.to_int32() as u16
} else {
443
};
let mut sn = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"servername".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut sn,
},
);
if sn.is_string() {
servername = Some(crate::js_to_rust_string(cx, sn));
}
let mut ra = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"rejectUnauthorized".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ra,
},
);
if ra.is_boolean() {
reject_unauthorized = ra.to_boolean();
}
let mut ca = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"ca".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ca,
},
);
if ca.is_string() {
ca_pems.push(crate::js_to_rust_string(cx, ca));
} else if ca.is_object() {
rooted!(&in(cx_ref) let arr = ca.to_object());
let mut i: u32 = 0;
loop {
let mut elem = UndefinedValue();
JS_GetElement(
cx,
arr.handle().into(),
i,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut elem,
},
);
if elem.is_undefined() {
break;
}
if elem.is_string() {
ca_pems.push(crate::js_to_rust_string(cx, elem));
}
i += 1;
}
}
(host, port)
} else if argc > 0 && (*args.get(0).ptr).is_int32() {
let port = (*args.get(0).ptr).to_int32() as u16;
let host = if argc > 1 && (*args.get(1).ptr).is_string() {
crate::js_to_rust_string(cx, *args.get(1).ptr)
} else {
"localhost".to_string()
};
(host, port)
} else {
args.rval().set(UndefinedValue());
return true;
};
let mut connect_cb_val = UndefinedValue();
{
let mut i: u32 = 1;
while i < argc {
let v = *args.get(i).ptr;
if v.is_object() && IsCallable(v.to_object()) {
connect_cb_val = v;
break;
}
i += 1;
}
}
let promise = {
rooted!(&in(cx_ref) let null_h = ::std::ptr::null_mut::<JSObject>());
mozjs_sys::jsapi::JS::NewPromiseObject(cx, null_h.handle().into())
};
if promise.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let promise_val = mozjs::jsval::ObjectValue(promise);
let mut early_socket: *mut JSObject = ::std::ptr::null_mut();
let setup_err: ::std::result::Result<(), String> = (|| {
let client = TlsClient::new().map_err(|e| format!("tls: client init failed: {}", e))?;
for pem in &ca_pems {
for der in pem_parse_certs(pem) {
if !client.add_trusted_der(&der) {
return Err("tls: add_trusted_der failed".to_string());
}
}
}
let mut conn = TlsConnection::new_client(&client, servername.as_deref().unwrap_or(&host))
.map_err(|e| format!("tls: new_client failed: {}", e))?;
if reject_unauthorized {
let verify_host = servername.clone().unwrap_or_else(|| host.clone());
if !conn.set_verify_peer(&verify_host) {
return Err("tls: set_verify_peer failed".to_string());
}
} else {
conn.set_verify_off();
}
let Some(handle) = tls_driver_acquire() else {
return Err("tls: TLS driver unavailable".to_string());
};
let promise_root = match unsafe {
RawValueRootGuard::new(cx, ::std::slice::from_ref(&promise_val), c"TLSSocket.promise")
} {
Some(g) => g,
None => return Err("tls: rooting the pending Promise failed".to_string()),
};
let conn_shared = Arc::new(ConnShared::new());
let loop_ptr: *const bun_event_loop::MiniEventLoop::MiniEventLoop<'static> =
crate::timers::with_event_loop(|loop_| loop_ as *const _);
let server_id = NEXT_TLS_ID.fetch_add(1, Ordering::Relaxed);
let shared = Arc::new(ServerShared {
server_id,
kind: ServerKind::ClientConnect,
cx,
server_obj_root: Some(promise_root),
sni_fn_root: None,
client_conn: Some(Arc::clone(&conn_shared)),
mini_loop_ptr: loop_ptr,
concurrent_task:
bun_event_loop::AnyTaskWithExtraContext::AnyTaskWithExtraContext::default(),
task_scheduled: AtomicBool::new(false),
events: Mutex::new(Vec::new()),
closing: AtomicBool::new(false),
sni_ctx_cache: Mutex::new(HashMap::new()),
alpn_wire: None,
});
TLS_SERVER_REGISTRY.with(|r| {
r.borrow_mut().insert(server_id, Arc::clone(&shared));
});
let conn_id = NEXT_TLS_ID.fetch_add(1, Ordering::Relaxed);
let socket = tls_build_socket_js(cx, conn_id);
if socket.is_null() {
return Err("tls: failed to build client TLSSocket".to_string());
}
let socket_val = ObjectValue(socket);
let socket_root = match RawValueRootGuard::new(
cx,
::std::slice::from_ref(&socket_val),
c"TLSSocket.object",
) {
Some(g) => g,
None => return Err("tls: rooting the client TLSSocket failed".to_string()),
};
TLS_CONNS.with(|m| {
m.borrow_mut().insert(
conn_id,
JsConn {
shared: Arc::clone(&conn_shared),
socket_root: Some(socket_root),
},
);
});
early_socket = socket;
let worker_host = host.clone();
let worker_shared = Arc::clone(&shared);
let worker_conn_shared = Arc::clone(&conn_shared);
let spawned = ::std::thread::Builder::new()
.name("bao-tls-connect".into())
.spawn(move || {
let addrs: Vec<::std::net::SocketAddr> = (worker_host.as_str(), port)
.to_socket_addrs()
.map(|it| it.collect())
.unwrap_or_default();
let deadline = Instant::now() + Duration::from_secs(10);
let mut stream = None;
for addr in addrs {
let remain = deadline.saturating_duration_since(Instant::now());
if remain.is_zero() {
break;
}
match TcpStream::connect_timeout(&addr, remain) {
Ok(s) => {
stream = Some(s);
break;
}
Err(_) => continue,
}
}
match stream {
Some(s) => {
handle
.cmds
.lock()
.unwrap()
.push(DriverCmd::AddClientConn(
conn_id,
s,
worker_shared,
worker_conn_shared,
conn,
));
tls_driver_wake();
}
None => {
tls_push_event(
&worker_shared,
TlsEvent::ClientError {
conn_id,
message: format!("tls: connect to {}:{} failed", worker_host, port),
},
);
tls_push_event(&worker_shared, TlsEvent::Close { conn_id });
}
}
});
if spawned.is_err() {
TLS_SERVER_REGISTRY.with(|r| {
r.borrow_mut().remove(&server_id);
});
TLS_CONNS.with(|m| {
m.borrow_mut().remove(&conn_id);
});
return Err("tls: failed to spawn connect worker".to_string());
}
Ok(())
})();
if let ::std::result::Result::Err(msg) = setup_err {
tls_reject_promise(cx, cx_ref, promise, &msg);
}
if !early_socket.is_null() {
rooted!(&in(cx_ref) let sock_root = early_socket);
rooted!(&in(cx_ref) let pv = promise_val);
JS_DefineProperty(
cx,
sock_root.handle().into(),
c"_tlsPromise".as_ptr(),
pv.handle().into(),
0,
);
let then_fn = JS_NewFunction(cx, Some(tls_socket_then), 2, 0, c"then".as_ptr());
if !then_fn.is_null() {
let then_obj = JS_GetFunctionObject(then_fn);
if !then_obj.is_null() {
rooted!(&in(cx_ref) let tv = ObjectValue(then_obj));
JS_DefineProperty(
cx,
sock_root.handle().into(),
c"then".as_ptr(),
tv.handle().into(),
0,
);
}
}
if connect_cb_val.is_object() {
rooted!(&in(cx_ref) let cb_root = connect_cb_val);
let mut on_val = UndefinedValue();
JS_GetProperty(
cx,
sock_root.handle().into(),
c"on".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut on_val,
},
);
if on_val.is_object() {
let ev_str = JS_NewStringCopyZ(cx, c"secureConnect".as_ptr());
if !ev_str.is_null() {
rooted!(&in(cx_ref) let ev_val = StringValue(&*ev_str));
let args_vals = [ev_val.get(), cb_root.get()];
let call_args = HandleValueArray {
length_: 2,
elements_: args_vals.as_ptr(),
};
let mut rval = UndefinedValue();
JS_CallFunctionName(
cx,
sock_root.handle().into(),
c"on".as_ptr(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
JS_ClearPendingException(cx);
}
}
}
args.rval().set(ObjectValue(early_socket));
return true;
}
args.rval().set(promise_val);
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_then(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let obj = this.to_object());
let mut pv = UndefinedValue();
JS_GetProperty(
cx,
obj.handle().into(),
c"_tlsPromise".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut pv,
},
);
if !pv.is_object() {
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let promise_root = pv.to_object());
let on_f = if argc > 0 { *args.get(0).ptr } else { UndefinedValue() };
let on_r = if argc > 1 { *args.get(1).ptr } else { UndefinedValue() };
rooted!(&in(cx_ref) let on_f_root = on_f);
rooted!(&in(cx_ref) let on_r_root = on_r);
let args_vals = [on_f_root.get(), on_r_root.get()];
let call_args = HandleValueArray {
length_: 2,
elements_: args_vals.as_ptr(),
};
let mut rval = UndefinedValue();
JS_CallFunctionName(
cx,
promise_root.handle().into(),
c"then".as_ptr(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
args.rval().set(rval);
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_create_server(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let server = w2::JS_NewPlainObject(cx_ref));
if !server.get().is_null() {
w2::JS_DefineFunction(
cx_ref,
server.handle(),
c"listen".as_ptr(),
Some(tls_server_listen),
2,
0,
);
w2::JS_DefineFunction(
cx_ref,
server.handle(),
c"close".as_ptr(),
Some(tls_server_close),
1,
0,
);
w2::JS_DefineFunction(
cx_ref,
server.handle(),
c"address".as_ptr(),
Some(tls_server_address),
0,
0,
);
w2::JS_DefineFunction(cx_ref, server.handle(), c"on".as_ptr(), Some(ee_on), 2, 0);
w2::JS_DefineFunction(
cx_ref,
server.handle(),
c"once".as_ptr(),
Some(ee_once),
2,
0,
);
w2::JS_DefineFunction(
cx_ref,
server.handle(),
c"emit".as_ptr(),
Some(ee_emit),
1,
0,
);
w2::JS_DefineFunction(
cx_ref,
server.handle(),
c"removeListener".as_ptr(),
Some(ee_off),
2,
0,
);
w2::JS_DefineFunction(
cx_ref,
server.handle(),
c"removeAllListeners".as_ptr(),
Some(ee_remove_all),
0,
0,
);
if argc > 0 && (*args.get(0).ptr).is_object() {
rooted!(&in(cx_ref) let opts = (*args.get(0).ptr).to_object());
rooted!(&in(cx_ref) let ov = ObjectValue(opts.get()));
JS_DefineProperty(
cx,
server.handle().into(),
c"_secureContext".as_ptr(),
ov.handle().into(),
0,
);
let mut key_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"key".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut key_val,
},
);
if key_val.is_string() {
let pem = crate::js_to_rust_string(cx, key_val);
sc_state_set_key(cx, server.get(), &pem);
}
let mut cert_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"cert".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cert_val,
},
);
if cert_val.is_string() {
let pem = crate::js_to_rust_string(cx, cert_val);
sc_state_set_cert(cx, server.get(), &pem);
}
let mut alpn_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"ALPNProtocols".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut alpn_val,
},
);
if !alpn_val.is_undefined() {
sc_state_set_alpn_protos(cx, server.get(), alpn_val);
}
let mut sni_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"SNICallback".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut sni_val,
},
);
if sni_val.is_object() {
rooted!(&in(cx_ref) let sni_root = sni_val);
JS_DefineProperty(
cx,
server.handle().into(),
c"_sniCallback".as_ptr(),
sni_root.handle().into(),
0,
);
}
let mut session_val = UndefinedValue();
JS_GetProperty(
cx,
opts.handle().into(),
c"session".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut session_val,
},
);
if !session_val.is_undefined() {
rooted!(&in(cx_ref) let session_root = session_val);
JS_DefineProperty(
cx,
server.handle().into(),
c"_session".as_ptr(),
session_root.handle().into(),
0,
);
}
}
if argc > 1 {
let v = *args.get(1).ptr;
if v.is_object() && IsCallable(v.to_object()) {
rooted!(&in(cx_ref) let cb_root = v);
let mut on_val = UndefinedValue();
JS_GetProperty(
cx,
server.handle().into(),
c"on".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut on_val,
},
);
if on_val.is_object() {
let ev_str = JS_NewStringCopyZ(cx, c"secureConnection".as_ptr());
if !ev_str.is_null() {
rooted!(&in(cx_ref) let ev_val = StringValue(&*ev_str));
let args_vals = [ev_val.get(), cb_root.get()];
let call_args = HandleValueArray {
length_: 2,
elements_: args_vals.as_ptr(),
};
let mut rval = UndefinedValue();
JS_CallFunctionName(
cx,
server.handle().into(),
c"on".as_ptr(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
JS_ClearPendingException(cx);
}
}
}
}
args.rval().set(ObjectValue(server.get()));
return true;
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_create_secure_context(
cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let ctx = w2::JS_NewPlainObject(cx_ref));
if !ctx.get().is_null() {
w2::JS_DefineFunction(
cx_ref,
ctx.handle(),
c"setKey".as_ptr(),
Some(sc_set_key),
1,
0,
);
w2::JS_DefineFunction(
cx_ref,
ctx.handle(),
c"setCert".as_ptr(),
Some(sc_set_cert),
1,
0,
);
w2::JS_DefineFunction(
cx_ref,
ctx.handle(),
c"addCACert".as_ptr(),
Some(sc_add_ca_cert),
1,
0,
);
w2::JS_DefineFunction(
cx_ref,
ctx.handle(),
c"setCA".as_ptr(),
Some(sc_set_ca),
1,
0,
);
w2::JS_DefineFunction(
cx_ref,
ctx.handle(),
c"setALPNProtocols".as_ptr(),
Some(sc_set_alpn_protocols),
1,
0,
);
w2::JS_DefineFunction(
cx_ref,
ctx.handle(),
c"setSession".as_ptr(),
Some(sc_set_session),
1,
0,
);
let state = Box::new(SecureContextState::new());
let ptr = Box::into_raw(state) as *const core::ffi::c_void;
let pv = mozjs::jsval::PrivateValue(ptr);
rooted!(&in(cx_ref) let pv_h = pv);
JS_DefineProperty(
cx,
ctx.handle().into(),
c"_scState".as_ptr(),
pv_h.handle().into(),
0,
);
args.rval().set(ObjectValue(ctx.get()));
return true;
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn sc_set_key(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let val = *args.get(0).ptr;
if val.is_string() {
let pem = crate::js_to_rust_string(cx, val);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
sc_state_set_key(cx, this_obj.get(), &pem);
}
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn sc_set_cert(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let val = *args.get(0).ptr;
if val.is_string() {
let pem = crate::js_to_rust_string(cx, val);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
sc_state_set_cert(cx, this_obj.get(), &pem);
}
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn sc_add_ca_cert(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let val = *args.get(0).ptr;
if val.is_string() {
let pem = crate::js_to_rust_string(cx, val);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
sc_state_add_ca(cx, this_obj.get(), &pem);
}
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn sc_set_ca(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let val = *args.get(0).ptr;
if val.is_string() {
let pem = crate::js_to_rust_string(cx, val);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
let state = sc_state_ensure(cx, this_obj.get());
(*state).ca_certs = Vec::new();
sc_state_add_ca(cx, this_obj.get(), &pem);
}
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn sc_set_alpn_protocols(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let val = *args.get(0).ptr;
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
sc_state_set_alpn_protos(cx, this_obj.get(), val);
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn sc_set_session(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let val = *args.get(0).ptr;
if val.is_string() {
let data = crate::js_to_rust_string(cx, val);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
sc_state_set_session(cx, this_obj.get(), data.as_bytes());
}
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_get_ciphers(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let ciphers = [
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_128_GCM_SHA256",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256",
];
rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, ciphers.len()));
if !arr.get().is_null() {
for (i, name) in ciphers.iter().enumerate() {
let c_name = ZBox::from_bytes(name.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_name.as_ptr());
if !js_str.is_null() {
rooted!(&in(cx_ref) let v = mozjs::jsval::StringValue(&*js_str));
JS_DefineElement(
cx,
arr.handle().into(),
i as u32,
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
args.rval().set(ObjectValue(arr.get()));
return true;
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_check_server_identity(
cx: *mut JSContext,
argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let _ = (cx, argc);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_write(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
let Some((shared, _socket)) = tls_socket_conn_handle(cx, this_obj.get()) else {
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
};
if shared.closed.load(Ordering::Acquire) {
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
if argc > 0 {
let data_val = *args.get(0).ptr;
if let Some(bytes) = tls_collect_write_bytes(cx, data_val) {
if !bytes.is_empty() {
shared.pending_writes.lock().unwrap().push(bytes);
tls_driver_wake();
}
}
}
args.rval().set(mozjs::jsval::BooleanValue(true));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_end(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
if let Some((shared, _socket)) = tls_socket_conn_handle(cx, this_obj.get()) {
if !shared.closed.load(Ordering::Acquire) {
if argc > 0 {
let data_val = *args.get(0).ptr;
if let Some(bytes) = tls_collect_write_bytes(cx, data_val) {
if !bytes.is_empty() {
shared.pending_writes.lock().unwrap().push(bytes);
}
}
}
shared.want_end.store(true, Ordering::Release);
tls_driver_wake();
}
}
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_destroy(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
if let Some((shared, _socket)) = tls_socket_conn_handle(cx, this_obj.get()) {
if !shared.closed.load(Ordering::Acquire) {
shared.want_destroy.store(true, Ordering::Release);
tls_driver_wake();
}
}
tls_set_bool_prop(cx, this_obj.get(), "destroyed", true);
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn tls_socket_conn_handle(
cx: *mut JSContext,
obj: *mut JSObject,
) -> Option<(Arc<ConnShared>, Option<JSVal>)> {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let mut cid_val = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
c"_connId".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cid_val,
},
);
if !cid_val.is_double() {
return None;
}
let conn_id = cid_val.to_double() as u64;
let entry = TLS_CONNS.with(|m| {
m.borrow()
.get(&conn_id)
.map(|e| (Arc::clone(&e.shared), e.socket_root.as_ref().map(|g| g.get(0))))
});
entry
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn tls_set_bool_prop(cx: *mut JSContext, obj: *mut JSObject, name: &str, value: bool) {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let c_name = ZBox::from_bytes(name.as_bytes());
rooted!(&in(cx_ref) let bv = mozjs::jsval::BooleanValue(value));
JS_DefineProperty(
cx,
obj_root.handle().into(),
c_name.as_ptr(),
bv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_noop_bool(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(mozjs::jsval::BooleanValue(false));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_get_finished(
_cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(mozjs::jsval::BooleanValue(false));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_get_peer_finished(
_cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(mozjs::jsval::BooleanValue(false));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_get_session(
_cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_set_encoding(
cx: *mut JSContext,
argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
if argc > 0 && (*args.get(0).ptr).is_string() {
rooted!(&in(cx_ref) let enc_val = *args.get(0).ptr);
JS_DefineProperty(
cx,
this_obj.handle().into(),
c"_encoding".as_ptr(),
enc_val.handle().into(),
0,
);
}
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_ref(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
rooted!(&in(cx_ref) let refed = mozjs::jsval::BooleanValue(true));
JS_DefineProperty(
cx,
this_obj.handle().into(),
c"_refed".as_ptr(),
refed.handle().into(),
0,
);
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_unref(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
rooted!(&in(cx_ref) let refed = mozjs::jsval::BooleanValue(false));
JS_DefineProperty(
cx,
this_obj.handle().into(),
c"_refed".as_ptr(),
refed.handle().into(),
0,
);
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_socket_get_alpn(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
let mut alpn_val = UndefinedValue();
JS_GetProperty(
cx,
this_obj.handle().into(),
c"_alpnProtocol".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut alpn_val,
},
);
if alpn_val.is_string() {
args.rval().set(alpn_val);
} else {
args.rval().set(UndefinedValue());
}
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn tls_socket_prop(cx: *mut JSContext, obj: *mut JSObject, name: &core::ffi::CStr) -> JSVal {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let mut out = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
name.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut out,
},
);
out
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_get_protocol(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let protocol = if args.thisv().is_object() {
tls_socket_prop(cx, args.thisv().to_object(), c"_tlsProtocol")
} else {
UndefinedValue()
};
if protocol.is_string() {
args.rval().set(protocol);
} else {
args.rval().set(mozjs::jsval::NullValue());
}
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_get_cipher(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let name = if args.thisv().is_object() {
tls_socket_prop(cx, args.thisv().to_object(), c"_tlsCipherName")
} else {
UndefinedValue()
};
if !name.is_string() {
args.rval().set(mozjs::jsval::NullValue());
return true;
}
let version = if args.thisv().is_object() {
tls_socket_prop(cx, args.thisv().to_object(), c"_tlsCipherVersion")
} else {
UndefinedValue()
};
rooted!(&in(cx_ref) let obj = w2::JS_NewPlainObject(cx_ref));
if obj.get().is_null() {
args.rval().set(mozjs::jsval::NullValue());
return true;
}
rooted!(&in(cx_ref) let nv = name);
JS_DefineProperty(
cx,
obj.handle().into(),
c"name".as_ptr(),
nv.handle().into(),
JSPROP_ENUMERATE as u32,
);
if version.is_string() {
rooted!(&in(cx_ref) let vv = version);
JS_DefineProperty(
cx,
obj.handle().into(),
c"version".as_ptr(),
vv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
args.rval().set(ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_get_peer_cert(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let cert = if args.thisv().is_object() {
tls_socket_prop(cx, args.thisv().to_object(), c"_tlsPeerCert")
} else {
UndefinedValue()
};
if cert.is_object() {
args.rval().set(cert);
} else {
args.rval().set(mozjs::jsval::NullValue());
}
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn tls_define_peer_cert_prop(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
obj: *mut JSObject,
cert: &PeerCertInfo,
) {
rooted!(&in(cx_ref) let cert_obj = w2::JS_NewPlainObject(cx_ref));
if cert_obj.get().is_null() {
return;
}
for (prop, entries) in [("subject", &cert.subject), ("issuer", &cert.issuer)] {
rooted!(&in(cx_ref) let names_obj = w2::JS_NewPlainObject(cx_ref));
if names_obj.get().is_null() {
continue;
}
for entry in entries.iter() {
tls_define_str_prop(cx, names_obj.get(), entry.key, &entry.value);
}
rooted!(&in(cx_ref) let names_val = ObjectValue(names_obj.get()));
let c_prop = ZBox::from_bytes(prop.as_bytes());
JS_DefineProperty(
cx,
cert_obj.handle().into(),
c_prop.as_ptr(),
names_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
for (prop, value) in [
("valid_from", &cert.valid_from),
("valid_to", &cert.valid_to),
("fingerprint256", &cert.fingerprint256),
("serialNumber", &cert.serial_number),
] {
if let Some(v) = value {
tls_define_str_prop(cx, cert_obj.get(), prop, v);
}
}
rooted!(&in(cx_ref) let cert_val = ObjectValue(cert_obj.get()));
rooted!(&in(cx_ref) let sock_root = obj);
JS_DefineProperty(
cx,
sock_root.handle().into(),
c"_tlsPeerCert".as_ptr(),
cert_val.handle().into(),
0,
);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_server_listen(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut port: u16 = 0;
let mut host: String = "0.0.0.0".to_string();
let mut listen_cb: Option<JSVal> = None;
for i in 0..argc as usize {
let v = *args.get(i as u32).ptr;
if v.is_int32() && i == 0 {
port = v.to_int32() as u16;
} else if v.is_string() {
host = crate::js_to_rust_string(cx, v);
} else if v.is_object() && JS_ObjectIsFunction(v.to_object()) {
listen_cb = Some(v);
}
}
let this_obj = args.thisv().to_object();
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_root = this_obj);
let mut state_ptr: *mut SecureContextState = core::ptr::null_mut();
let mut sc_val = UndefinedValue();
JS_GetProperty(
cx,
this_root.handle().into(),
c"_scState".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut sc_val,
},
);
if val_is_private(&sc_val) {
let ptr = sc_val.to_private() as *mut SecureContextState;
if !ptr.is_null() && (!(*ptr).cert_ders.is_empty() || (*ptr).key_der.is_some()) {
state_ptr = ptr;
}
}
if state_ptr.is_null() {
let mut ctx_val = UndefinedValue();
JS_GetProperty(
cx,
this_root.handle().into(),
c"_secureContext".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ctx_val,
},
);
if ctx_val.is_object() {
rooted!(&in(cx_ref) let ctx_obj = ctx_val.to_object());
let mut ctx_sc_val = UndefinedValue();
JS_GetProperty(
cx,
ctx_obj.handle().into(),
c"_scState".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ctx_sc_val,
},
);
if ctx_sc_val.is_double() && (ctx_sc_val.asBits_ & 0xFFFF000000000000) == 0 {
let ptr = ctx_sc_val.to_private() as *mut SecureContextState;
if !ptr.is_null() {
state_ptr = ptr;
}
}
}
}
if state_ptr.is_null() {
log::warn!("[tls] createServer.listen() called without cert/key");
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
let state = &*state_ptr;
if state.cert_ders.is_empty() || state.key_der.is_none() {
log::warn!("[tls] createServer.listen() called without cert/key");
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
let pem_certs = match &state.pem_certs {
Some(p) => p.clone(),
None => {
log::warn!("[tls] createServer.listen() no PEM cert string available");
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
};
let pem_key = match &state.pem_key {
Some(p) => p.clone(),
None => {
log::warn!("[tls] createServer.listen() no PEM key string available");
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
};
let base_server = match TlsServer::new(&pem_certs, &pem_key) {
Ok(s) => s,
Err(e) => {
log::warn!("[tls] TlsServer::new failed: {}", e);
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
};
let mut alpn_wire: Option<&'static [u8]> = None;
if let Some(ref wire) = state.alpn_protos {
let alpn_box = wire.as_slice().to_vec().into_boxed_slice();
let alpn_static: &'static [u8] = Box::leak(alpn_box);
unsafe {
SSL_CTX_set_alpn_select_cb(
base_server.ctx(),
Some(alpn_select_callback),
alpn_static.as_ptr() as *mut core::ffi::c_void,
);
}
alpn_wire = Some(alpn_static);
}
let mut sni_fn_root: Option<RawValueRootGuard> = None;
let mut sni_val = UndefinedValue();
JS_GetProperty(
cx,
this_root.handle().into(),
c"_sniCallback".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut sni_val,
},
);
let mut has_sni = sni_val.is_object() && JS_ObjectIsFunction(sni_val.to_object());
if has_sni {
match unsafe { RawValueRootGuard::new(cx, ::std::slice::from_ref(&sni_val), c"TLSServer.sniCallback") } {
Some(guard) => sni_fn_root = Some(guard),
None => has_sni = false,
}
}
if has_sni {
base_server.set_select_certificate_callback(Some(tls_select_cert_cb));
}
let listener = match TcpListener::bind((host.as_str(), port)) {
Ok(l) => l,
Err(e) => {
log::warn!("[tls] listen({}:{}) bind failed: {}", host, port, e);
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
};
let real_port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
let _ = listener.set_nonblocking(true);
let server_obj_val = ObjectValue(this_root.get());
let server_obj_root = unsafe {
RawValueRootGuard::new(cx, ::std::slice::from_ref(&server_obj_val), c"TLSServer.object")
};
if server_obj_root.is_none() {
log::warn!("[tls] listen: AddRawValueRoot failed");
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
let loop_ptr: *const bun_event_loop::MiniEventLoop::MiniEventLoop<'static> =
crate::timers::with_event_loop(|loop_| loop_ as *const _);
let server_id = NEXT_TLS_ID.fetch_add(1, Ordering::Relaxed);
let shared = Arc::new(ServerShared {
server_id,
kind: ServerKind::Listener,
cx,
server_obj_root,
sni_fn_root,
client_conn: None,
mini_loop_ptr: loop_ptr,
concurrent_task: bun_event_loop::AnyTaskWithExtraContext::AnyTaskWithExtraContext::default(
),
task_scheduled: AtomicBool::new(false),
events: Mutex::new(Vec::new()),
closing: AtomicBool::new(false),
sni_ctx_cache: Mutex::new(HashMap::new()),
alpn_wire,
});
let Some(handle) = tls_driver_acquire() else {
drop(shared);
log::warn!("[tls] listen: TLS driver unavailable");
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
};
handle
.cmds
.lock()
.unwrap()
.push(DriverCmd::AddListener(listener, Arc::clone(&shared), base_server));
TLS_SERVER_REGISTRY.with(|r| {
r.borrow_mut().insert(server_id, Arc::clone(&shared));
});
tls_driver_wake();
rooted!(&in(cx_ref) let sid_val = DoubleValue(server_id as f64));
JS_DefineProperty(
cx,
this_root.handle().into(),
c"_serverId".as_ptr(),
sid_val.handle().into(),
0,
);
rooted!(&in(cx_ref) let port_val = Int32Value(real_port as i32));
JS_DefineProperty(
cx,
this_root.handle().into(),
c"_listenPort".as_ptr(),
port_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
tls_define_str_prop(cx, this_root.get(), "_listenHost", &host);
log::info!(
"[tls] server listening on {}:{} (SNICallback: {})",
host,
real_port,
if has_sni { "enabled" } else { "off — static cert" }
);
tls_emit_js(cx, this_root.get(), "listening", &[]);
if let Some(cb) = listen_cb {
rooted!(&in(cx_ref) let cb_root = cb);
let mut rval = UndefinedValue();
JS_CallFunctionValue(
cx,
this_root.handle().into(),
cb_root.handle().into(),
&HandleValueArray::empty(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
JS_ClearPendingException(cx);
}
args.rval().set(mozjs::jsval::BooleanValue(true));
true
}
unsafe extern "C" fn alpn_select_callback(
_ssl: *mut SSL,
out: *mut *const u8,
out_len: *mut u8,
client_protos: *const u8,
client_protos_len: ::std::ffi::c_uint,
arg: *mut core::ffi::c_void,
) -> ::std::ffi::c_int {
if arg.is_null() || client_protos.is_null() || client_protos_len == 0 {
return SSL_TLSEXT_ERR_NOACK;
}
let server_protos = unsafe {
core::slice::from_raw_parts(arg as *const u8, 256) };
let client_list =
unsafe { core::slice::from_raw_parts(client_protos, client_protos_len as usize) };
let mut pos = 0usize;
while pos < client_list.len() {
let len = client_list[pos] as usize;
pos += 1;
if pos + len > client_list.len() {
break;
}
let client_proto = &client_list[pos..pos + len];
pos += len;
let mut spos = 0usize;
while spos < server_protos.len() {
let slen = server_protos[spos] as usize;
spos += 1;
if spos + slen > server_protos.len() || slen == 0 {
break;
}
let server_proto = &server_protos[spos..spos + slen];
spos += slen;
if client_proto == server_proto {
unsafe {
*out = client_proto.as_ptr();
*out_len = len as u8;
}
return SSL_TLSEXT_ERR_OK;
}
}
}
SSL_TLSEXT_ERR_NOACK
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_server_close(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let this_obj = args.thisv().to_object();
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_root = this_obj);
if argc > 0 && (*args.get(0).ptr).is_object() {
rooted!(&in(cx_ref) let cb_val = *args.get(0).ptr);
JS_DefineProperty(
cx,
this_root.handle().into(),
c"_closeCb".as_ptr(),
cb_val.handle().into(),
0,
);
}
let mut sid_val = UndefinedValue();
JS_GetProperty(
cx,
this_root.handle().into(),
c"_serverId".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut sid_val,
},
);
if sid_val.is_double() {
let server_id = sid_val.to_double() as u64;
let shared = TLS_SERVER_REGISTRY.with(|r| r.borrow().get(&server_id).cloned());
if let Some(shared) = shared {
if !shared.closing.swap(true, Ordering::AcqRel) {
if let Some(handle) = DRIVER.get() {
handle.cmds.lock().unwrap().push(DriverCmd::RemoveServer(server_id));
tls_driver_wake();
}
}
}
}
sc_state_drop(cx, this_obj);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tls_server_address(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = args.thisv().to_object());
let mut port_val = UndefinedValue();
JS_GetProperty(
cx,
this_obj.handle().into(),
c"_listenPort".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut port_val,
},
);
let mut host_val = UndefinedValue();
JS_GetProperty(
cx,
this_obj.handle().into(),
c"_listenHost".as_ptr(),
MutableHandle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut host_val,
},
);
if !port_val.is_int32() {
args.rval().set(UndefinedValue());
return true;
}
let addr = w2::JS_NewPlainObject(cx_ref);
if addr.is_null() {
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let addr_root = addr);
rooted!(&in(cx_ref) let port_h = port_val);
JS_DefineProperty(
cx,
addr_root.handle().into(),
c"port".as_ptr(),
port_h.handle().into(),
JSPROP_ENUMERATE as u32,
);
let fam_str = JS_NewStringCopyZ(cx, c"IPv4".as_ptr());
if !fam_str.is_null() {
rooted!(&in(cx_ref) let fam = mozjs::jsval::StringValue(&*fam_str));
JS_DefineProperty(
cx,
addr_root.handle().into(),
c"family".as_ptr(),
fam.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
if host_val.is_string() {
rooted!(&in(cx_ref) let host_h = host_val);
JS_DefineProperty(
cx,
addr_root.handle().into(),
c"address".as_ptr(),
host_h.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
args.rval().set(ObjectValue(addr_root.get()));
true
}