use crate::host::{invoke, with_host, JsObj};
use fusevm::Value;
use indexmap::IndexMap;
use std::collections::HashMap;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
pub const MODULE_METHODS: &[&str] = &[
"createServer",
"connect",
"createConnection",
"isIP",
"isIPv4",
"isIPv6",
"getDefaultAutoSelectFamily",
"setDefaultAutoSelectFamily",
"getDefaultAutoSelectFamilyAttemptTimeout",
"setDefaultAutoSelectFamilyAttemptTimeout",
];
pub const BLOCKLIST_METHODS: &[&str] = &["addAddress", "addRange", "addSubnet", "check"];
type ConnHook = std::rc::Rc<dyn Fn(&Value, &Value) -> Result<(), String>>;
struct ServerRec {
emitter: Value,
stop: Arc<AtomicBool>,
conn_hook: Option<ConnHook>,
}
struct SocketRec {
emitter: Value,
write: Arc<Mutex<TcpStream>>,
}
#[derive(Default)]
struct NetState {
next_id: u64,
servers: HashMap<u64, ServerRec>,
sockets: HashMap<u64, SocketRec>,
}
thread_local! {
static NET: std::cell::RefCell<NetState> = std::cell::RefCell::new(NetState::default());
}
fn next_id() -> u64 {
NET.with(|s| {
let mut s = s.borrow_mut();
s.next_id += 1;
s.next_id
})
}
pub fn new_emitter_object(tag: &str, mut extra: IndexMap<String, Value>) -> Value {
with_host(|h| {
let on = h.new_object(IndexMap::new());
let once = h.new_object(IndexMap::new());
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str(tag));
m.insert("@@on".into(), on);
m.insert("@@once".into(), once);
for (k, v) in extra.drain(..) {
m.insert(k, v);
}
h.new_object(m)
})
}
fn get_prop(recv: &Value, key: &str) -> Option<Value> {
with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get(key).cloned(),
_ => None,
})
}
fn set_prop(recv: &Value, key: &str, val: Value) {
with_host(|h| {
if let Some(JsObj::Object(p)) = h.get_mut(recv) {
p.insert(key.to_string(), val);
}
});
}
fn u64_prop(recv: &Value, key: &str) -> Option<u64> {
get_prop(recv, key).map(|v| with_host(|h| h.to_number(&v)) as u64)
}
fn emitter_dispatch(recv: &Value, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
match method {
"on"
| "addListener"
| "prependListener"
| "once"
| "prependOnceListener"
| "emit"
| "removeListener"
| "off"
| "removeAllListeners"
| "listenerCount"
| "eventNames" => Some(super::events::instance_call(recv, method, args.to_vec())),
_ => None,
}
}
pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
match method {
"createServer" => Some(Ok(create_server(args.first().cloned()))),
"connect" | "createConnection" => Some(Ok(connect(args))),
"isIP" => Some(Ok(Value::Float(is_ip(&arg_string(args, 0)) as f64))),
"isIPv4" => Some(Ok(Value::Bool(is_ip(&arg_string(args, 0)) == 4))),
"isIPv6" => Some(Ok(Value::Bool(is_ip(&arg_string(args, 0)) == 6))),
"getDefaultAutoSelectFamily" => Some(Ok(Value::Bool(AUTO_SELECT_FAMILY.with(|c| c.get())))),
"setDefaultAutoSelectFamily" => {
let v = args
.first()
.map(|a| with_host(|h| h.truthy(a)))
.unwrap_or(false);
AUTO_SELECT_FAMILY.with(|c| c.set(v));
Some(Ok(Value::Undef))
}
"getDefaultAutoSelectFamilyAttemptTimeout" => {
Some(Ok(Value::Float(AUTO_SELECT_TIMEOUT.with(|c| c.get()))))
}
"setDefaultAutoSelectFamilyAttemptTimeout" => {
let v = args
.first()
.map(|a| with_host(|h| h.to_number(a)))
.unwrap_or(f64::NAN);
if v.is_finite() && v >= 1.0 {
AUTO_SELECT_TIMEOUT.with(|c| c.set(v));
}
Some(Ok(Value::Undef))
}
_ => None,
}
}
thread_local! {
static AUTO_SELECT_FAMILY: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
static AUTO_SELECT_TIMEOUT: std::cell::Cell<f64> = const { std::cell::Cell::new(250.0) };
}
fn arg_string(args: &[Value], i: usize) -> String {
match args.get(i) {
Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
_ => String::new(),
}
}
fn is_ip(input: &str) -> i32 {
use std::net::{Ipv4Addr, Ipv6Addr};
if input.parse::<Ipv4Addr>().is_ok() {
4
} else if input.parse::<Ipv6Addr>().is_ok() {
6
} else {
0
}
}
pub fn constant(name: &str) -> Option<Value> {
match name {
"Server" | "Socket" | "Stream" | "SocketAddress" | "BlockList" => {
Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
}
_ => None,
}
}
pub fn create_server(connection_listener: Option<Value>) -> Value {
let mut extra = IndexMap::new();
if let Some(cb) = connection_listener.filter(|v| !matches!(v, Value::Undef)) {
extra.insert("@@connListener".into(), cb);
}
new_emitter_object("Server", extra)
}
pub fn new_socket() -> Value {
let sock_id = next_id();
let mut extra = IndexMap::new();
extra.insert("@@netid".into(), Value::Float(sock_id as f64));
extra.insert("connecting".into(), Value::Bool(false));
new_emitter_object("Socket", extra)
}
pub fn connect(args: &[Value]) -> Value {
let socket = new_socket();
socket_connect(&socket, args);
socket
}
fn parse_connect_args(args: &[Value]) -> (u16, String, Option<Value>) {
let mut port: u16 = 0;
let mut host = "localhost".to_string();
let mut cb: Option<Value> = None;
for a in args {
if with_host(|h| crate::host::is_callable(h, a)) {
cb = Some(a.clone());
} else if with_host(|h| h.as_str(a)).is_some() {
host = with_host(|h| h.str_of(a));
} else if matches!(a, Value::Obj(_)) {
if let Some(v) = get_prop(a, "port") {
let n = with_host(|h| h.to_number(&v));
if !n.is_nan() {
port = n as u16;
}
}
for key in ["host", "hostname"] {
if let Some(v) = get_prop(a, key).filter(|v| with_host(|h| h.as_str(v)).is_some()) {
host = with_host(|h| h.str_of(&v));
}
}
} else {
let n = with_host(|h| h.to_number(a));
if !n.is_nan() {
port = n as u16;
}
}
}
(port, host, cb)
}
fn socket_connect(socket: &Value, args: &[Value]) {
let (port, host, cb) = parse_connect_args(args);
if let Some(cb) = cb {
let _ = super::events::instance_call(
socket,
"on",
vec![with_host(|h| h.new_str("connect")), cb],
);
}
let sock_id = u64_prop(socket, "@@netid").unwrap_or_else(next_id);
set_prop(socket, "@@netid", Value::Float(sock_id as f64));
set_prop(socket, "connecting", Value::Bool(true));
with_host(|h| h.incr_handle());
let tx = with_host(|h| h.io_sender());
let socket_val = socket.clone();
std::thread::spawn(move || match TcpStream::connect((host.as_str(), port)) {
Ok(stream) => {
let _ = tx.send(Box::new(move || on_connect(sock_id, socket_val, stream)));
}
Err(e) => {
let msg = format!("connect ECONNREFUSED {host}:{port}: {e}");
let _ = tx.send(Box::new(move || on_connect_error(socket_val, msg)));
}
});
}
fn on_connect(sock_id: u64, socket: Value, stream: TcpStream) -> Result<(), String> {
let read_stream = match stream.try_clone() {
Ok(s) => s,
Err(_) => {
with_host(|h| h.decr_handle());
return Ok(());
}
};
let write = Arc::new(Mutex::new(stream));
NET.with(|s| {
s.borrow_mut().sockets.insert(
sock_id,
SocketRec {
emitter: socket.clone(),
write,
},
);
});
set_prop(&socket, "connecting", Value::Bool(false));
let tx = with_host(|h| h.io_sender());
std::thread::spawn(move || reader_loop(read_stream, sock_id, tx));
super::events::instance_call(&socket, "emit", vec![with_host(|h| h.new_str("connect"))])?;
Ok(())
}
fn on_connect_error(socket: Value, msg: String) -> Result<(), String> {
with_host(|h| h.decr_handle());
let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
let err = with_host(|h| {
let mut m = IndexMap::new();
m.insert("message".into(), h.new_str(msg.clone()));
m.insert("code".into(), h.new_str("ECONNREFUSED"));
h.new_object(m)
});
super::events::instance_call(
&socket,
"emit",
vec![with_host(|h| h.new_str("error")), err],
)?;
Ok(())
}
pub fn construct(name: &str, args: &[Value]) -> Option<Result<Value, String>> {
match name {
"Socket" | "Stream" => Some(Ok(new_socket())),
"Server" => Some(Ok(create_server(
args.first()
.cloned()
.filter(|v| with_host(|h| crate::host::is_callable(h, v))),
))),
"SocketAddress" => Some(Ok(socket_address(args))),
"BlockList" => Some(Ok(new_block_list())),
_ => None,
}
}
fn socket_address(args: &[Value]) -> Value {
let opts = args.first().cloned().unwrap_or(Value::Undef);
let mut address = String::new();
let mut family = "ipv4".to_string();
let mut have_family = false;
let mut port = 0f64;
let mut flowlabel = 0f64;
if matches!(opts, Value::Obj(_)) {
if let Some(v) = get_prop(&opts, "address").filter(|v| with_host(|h| h.as_str(v)).is_some())
{
address = with_host(|h| h.str_of(&v));
}
if let Some(v) = get_prop(&opts, "family").filter(|v| with_host(|h| h.as_str(v)).is_some())
{
family = with_host(|h| h.str_of(&v)).to_ascii_lowercase();
have_family = true;
}
if let Some(v) = get_prop(&opts, "port") {
let n = with_host(|h| h.to_number(&v));
if !n.is_nan() {
port = n;
}
}
if let Some(v) = get_prop(&opts, "flowlabel") {
let n = with_host(|h| h.to_number(&v));
if !n.is_nan() {
flowlabel = n;
}
}
}
if !have_family {
family = if is_ip(&address) == 6 { "ipv6" } else { "ipv4" }.to_string();
}
if address.is_empty() {
address = if family == "ipv6" { "::" } else { "127.0.0.1" }.to_string();
}
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str("SocketAddress"));
m.insert("address".into(), h.new_str(address));
m.insert("port".into(), Value::Float(port));
m.insert("family".into(), h.new_str(family));
m.insert("flowlabel".into(), Value::Float(flowlabel));
h.new_object(m)
})
}
enum BlockRule {
Addr { v6: bool, val: u128 },
Range { v6: bool, start: u128, end: u128 },
Subnet {
v6: bool,
network: u128,
prefix: u32,
},
}
thread_local! {
static BLOCK_LISTS: std::cell::RefCell<HashMap<u64, Vec<BlockRule>>> =
std::cell::RefCell::new(HashMap::new());
}
fn new_block_list() -> Value {
let id = next_id();
BLOCK_LISTS.with(|b| {
b.borrow_mut().insert(id, Vec::new());
});
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str("BlockList"));
m.insert("@@blid".into(), Value::Float(id as f64));
h.new_object(m)
})
}
fn ip_to_u128(s: &str) -> Option<(bool, u128)> {
use std::net::{Ipv4Addr, Ipv6Addr};
if let Ok(v4) = s.parse::<Ipv4Addr>() {
return Some((false, u32::from(v4) as u128));
}
if let Ok(v6) = s.parse::<Ipv6Addr>() {
return Some((true, u128::from(v6)));
}
None
}
pub fn block_list_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
let Some(id) = u64_prop(recv, "@@blid") else {
return Err(crate::host::type_error("invalid BlockList"));
};
match method {
"addAddress" => {
let addr = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
if let Some((v6, val)) = ip_to_u128(&addr) {
BLOCK_LISTS.with(|b| {
if let Some(rules) = b.borrow_mut().get_mut(&id) {
rules.push(BlockRule::Addr { v6, val });
}
});
}
Ok(Value::Undef)
}
"addRange" => {
let start = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
let end = with_host(|h| h.str_of(&args.get(1).cloned().unwrap_or(Value::Undef)));
if let (Some((v6, s)), Some((_, e))) = (ip_to_u128(&start), ip_to_u128(&end)) {
BLOCK_LISTS.with(|b| {
if let Some(rules) = b.borrow_mut().get_mut(&id) {
rules.push(BlockRule::Range {
v6,
start: s.min(e),
end: s.max(e),
});
}
});
}
Ok(Value::Undef)
}
"addSubnet" => {
let net = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
let prefix =
with_host(|h| h.to_number(&args.get(1).cloned().unwrap_or(Value::Undef))) as u32;
if let Some((v6, network)) = ip_to_u128(&net) {
BLOCK_LISTS.with(|b| {
if let Some(rules) = b.borrow_mut().get_mut(&id) {
rules.push(BlockRule::Subnet {
v6,
network,
prefix,
});
}
});
}
Ok(Value::Undef)
}
"check" => {
let addr = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
let Some((v6, val)) = ip_to_u128(&addr) else {
return Ok(Value::Bool(false));
};
let blocked = BLOCK_LISTS.with(|b| {
b.borrow()
.get(&id)
.map(|rules| rules.iter().any(|r| rule_matches(r, v6, val)))
.unwrap_or(false)
});
Ok(Value::Bool(blocked))
}
_ => Err(crate::host::type_error(&format!(
"blocklist.{method} is not a function"
))),
}
}
fn rule_matches(rule: &BlockRule, q_v6: bool, q_val: u128) -> bool {
match rule {
BlockRule::Addr { v6, val } => *v6 == q_v6 && *val == q_val,
BlockRule::Range { v6, start, end } => *v6 == q_v6 && q_val >= *start && q_val <= *end,
BlockRule::Subnet {
v6,
network,
prefix,
} => {
if *v6 != q_v6 {
return false;
}
let bits = if q_v6 { 128 } else { 32 };
let p = (*prefix).min(bits);
if p == 0 {
return true;
}
let shift = bits - p;
(q_val >> shift) == (*network >> shift)
}
}
}
pub fn set_conn_hook(server: &Value, hook: ConnHook) {
set_prop(server, "@@httpMode", Value::Bool(true));
PENDING_HOOKS.with(|p| p.borrow_mut().push((server.clone(), hook)));
}
thread_local! {
static PENDING_HOOKS: std::cell::RefCell<Vec<(Value, ConnHook)>> =
const { std::cell::RefCell::new(Vec::new()) };
}
fn take_pending_hook(server: &Value) -> Option<ConnHook> {
PENDING_HOOKS.with(|p| {
let mut p = p.borrow_mut();
p.iter()
.position(|(s, _)| s == server)
.map(|pos| p.remove(pos).1)
})
}
pub fn instance_call(
tag: &str,
recv: &Value,
method: &str,
args: Vec<Value>,
) -> Result<Value, String> {
match tag {
"Server" => server_call(recv, method, args),
"Socket" => socket_call(recv, method, args),
"BlockList" => block_list_call(recv, method, args),
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
}
}
fn server_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
if let Some(r) = emitter_dispatch(recv, method, &args) {
return r;
}
match method {
"listen" => server_listen(recv, &args),
"close" => server_close(recv, &args),
"address" => Ok(get_prop(recv, "@@address").unwrap_or(Value::Undef)),
_ => Err(crate::host::type_error(&format!(
"server.{method} is not a function"
))),
}
}
fn server_listen(recv: &Value, args: &[Value]) -> Result<Value, String> {
let port = with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(0.0)) as u16;
let mut host = "0.0.0.0".to_string();
let mut cb: Option<Value> = None;
for a in &args[1.min(args.len())..] {
if with_host(|h| h.as_str(a)).is_some() {
host = with_host(|h| h.str_of(a));
} else if with_host(|h| crate::host::is_callable(h, a)) {
cb = Some(a.clone());
}
}
let listener = TcpListener::bind((host.as_str(), port))
.map_err(|e| format!("Error: listen EADDRINUSE: {e}"))?;
let local = listener.local_addr().ok();
let id = next_id();
set_prop(recv, "@@netid", Value::Float(id as f64));
if let Some(addr) = local {
let mut a = IndexMap::new();
a.insert("port".into(), Value::Float(addr.port() as f64));
a.insert(
"address".into(),
with_host(|h| h.new_str(addr.ip().to_string())),
);
a.insert(
"family".into(),
with_host(|h| h.new_str(if addr.is_ipv6() { "IPv6" } else { "IPv4" })),
);
let addr_obj = with_host(|h| h.new_object(a));
set_prop(recv, "@@address", addr_obj);
}
let conn_hook = take_pending_hook(recv);
let stop = Arc::new(AtomicBool::new(false));
NET.with(|s| {
s.borrow_mut().servers.insert(
id,
ServerRec {
emitter: recv.clone(),
stop: stop.clone(),
conn_hook,
},
);
});
with_host(|h| h.incr_handle());
let tx = with_host(|h| h.io_sender());
listener.set_nonblocking(true).ok();
std::thread::spawn(move || loop {
if stop.load(Ordering::Acquire) {
break;
}
match listener.accept() {
Ok((stream, _addr)) => {
let tx2 = tx.clone();
let _ = tx.send(Box::new(move || on_connection(id, stream, tx2)));
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
std::thread::sleep(std::time::Duration::from_millis(5));
}
Err(_) => break,
}
});
let server = recv.clone();
let _ = with_host(|h| h.io_sender()).send(Box::new(move || {
super::events::instance_call(&server, "emit", vec![with_host(|h| h.new_str("listening"))])?;
if let Some(cb) = cb {
invoke(&cb, Vec::new(), None)?;
}
Ok(())
}));
Ok(recv.clone())
}
fn server_close(recv: &Value, args: &[Value]) -> Result<Value, String> {
if let Some(id) = u64_prop(recv, "@@netid") {
let rec = NET.with(|s| s.borrow_mut().servers.remove(&id));
if let Some(rec) = rec {
rec.stop.store(true, Ordering::Release);
with_host(|h| h.decr_handle());
let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
}
}
if let Some(cb) = args
.first()
.filter(|v| with_host(|h| crate::host::is_callable(h, v)))
{
invoke(cb, Vec::new(), None)?;
}
super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))])?;
Ok(recv.clone())
}
fn socket_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
if let Some(r) = emitter_dispatch(recv, method, &args) {
return r;
}
match method {
"write" => {
if let Some(id) = u64_prop(recv, "@@netid") {
socket_write_id(id, &value_bytes(args.first()));
}
Ok(Value::Bool(true))
}
"end" => {
if let Some(id) = u64_prop(recv, "@@netid") {
if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
socket_write_id(id, &value_bytes(Some(chunk)));
}
socket_shutdown(id);
}
Ok(recv.clone())
}
"destroy" => {
if let Some(id) = u64_prop(recv, "@@netid") {
socket_shutdown(id);
}
Ok(recv.clone())
}
"connect" => {
socket_connect(recv, &args);
Ok(recv.clone())
}
"address" => Ok(get_prop(recv, "@@address").unwrap_or(Value::Undef)),
"setEncoding" | "setTimeout" | "setNoDelay" | "setKeepAlive" | "ref" | "unref"
| "pause" | "resume" => {
Ok(recv.clone())
}
_ => Err(crate::host::type_error(&format!(
"socket.{method} is not a function"
))),
}
}
fn value_bytes(v: Option<&Value>) -> Vec<u8> {
let Some(v) = v else { return Vec::new() };
let is_buffer =
with_host(|h| matches!(h.get(v), Some(JsObj::Object(p)) if p.contains_key("@@bytes")));
if is_buffer {
return with_host(|h| match h.get(v) {
Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
_ => Vec::new(),
},
_ => Vec::new(),
});
}
with_host(|h| h.str_of(v)).into_bytes()
}
fn on_connection(
server_id: u64,
stream: TcpStream,
tx: std::sync::mpsc::Sender<crate::host::IoTask>,
) -> Result<(), String> {
let server = NET.with(|s| {
s.borrow()
.servers
.get(&server_id)
.map(|r| r.emitter.clone())
});
let Some(server) = server else { return Ok(()) };
let read_stream = match stream.try_clone() {
Ok(s) => s,
Err(_) => return Ok(()),
};
let write = Arc::new(Mutex::new(stream));
let sock_id = next_id();
let mut extra = IndexMap::new();
extra.insert("@@netid".into(), Value::Float(sock_id as f64));
let socket = new_emitter_object("Socket", extra);
NET.with(|s| {
s.borrow_mut().sockets.insert(
sock_id,
SocketRec {
emitter: socket.clone(),
write,
},
);
});
with_host(|h| h.incr_handle());
std::thread::spawn(move || reader_loop(read_stream, sock_id, tx));
super::events::instance_call(&server, "emit", vec![with_host(|_h| socket.clone())])?;
let hook = NET.with(|s| {
s.borrow()
.servers
.get(&server_id)
.and_then(|r| r.conn_hook.clone())
});
if let Some(hook) = hook {
hook(&server, &socket)?;
} else if let Some(cb) = get_prop(&server, "@@connListener") {
invoke(&cb, vec![socket.clone()], None)?;
}
Ok(())
}
fn reader_loop(
mut stream: TcpStream,
sock_id: u64,
tx: std::sync::mpsc::Sender<crate::host::IoTask>,
) {
let mut buf = [0u8; 8192];
loop {
match stream.read(&mut buf) {
Ok(0) => {
let _ = tx.send(Box::new(move || on_socket_end(sock_id)));
break;
}
Ok(n) => {
let bytes = buf[..n].to_vec();
let _ = tx.send(Box::new(move || on_socket_data(sock_id, bytes)));
}
Err(_) => {
let _ = tx.send(Box::new(move || on_socket_close(sock_id)));
break;
}
}
}
}
fn on_socket_data(sock_id: u64, bytes: Vec<u8>) -> Result<(), String> {
let socket = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
let Some(socket) = socket else { return Ok(()) };
super::http::feed(sock_id, &socket, &bytes)?;
let chunk = super::buffer::from_bytes(&bytes);
super::events::instance_call(
&socket,
"emit",
vec![with_host(|h| h.new_str("data")), chunk],
)?;
Ok(())
}
fn on_socket_end(sock_id: u64) -> Result<(), String> {
let socket = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
if let Some(socket) = socket {
super::events::instance_call(&socket, "emit", vec![with_host(|h| h.new_str("end"))])?;
}
on_socket_close(sock_id)
}
fn on_socket_close(sock_id: u64) -> Result<(), String> {
let rec = NET.with(|s| s.borrow_mut().sockets.remove(&sock_id));
super::http::drop_conn(sock_id);
if let Some(rec) = rec {
super::events::instance_call(
&rec.emitter,
"emit",
vec![with_host(|h| h.new_str("close"))],
)?;
with_host(|h| h.decr_handle());
let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
}
Ok(())
}
pub fn socket_write_id(sock_id: u64, data: &[u8]) {
let write = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.write.clone()));
if let Some(write) = write {
if let Ok(mut stream) = write.lock() {
let _ = stream.write_all(data);
let _ = stream.flush();
}
}
}
fn socket_shutdown(sock_id: u64) {
let write = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.write.clone()));
if let Some(write) = write {
if let Ok(stream) = write.lock() {
let _ = stream.shutdown(std::net::Shutdown::Write);
}
}
}