Skip to main content

nodejs/stdlib/
net.rs

1//! Node `net` module: TCP `Server` and `Socket`.
2//!
3//! Threading model (see `host::run_event_loop`): each listener runs an `accept`
4//! loop on its own thread and each accepted connection runs a `read` loop on its
5//! own thread. Those threads NEVER touch the JS heap — they only move raw bytes
6//! and post `IoTask` closures onto the host channel. Every JS-visible effect
7//! (creating the `Socket` object, emitting `connection`/`data`/`end`/`close`,
8//! calling listeners) happens on the main thread when the event loop runs the
9//! posted closure. All shared Rust-side state (listener/socket records) lives in
10//! a main-thread `thread_local`, so it needs no locking of its own; only the
11//! write half of each `TcpStream` is shared with... nothing else, but is wrapped
12//! for symmetry and future duplex use.
13
14use crate::host::{invoke, with_host, JsObj};
15use fusevm::Value;
16use indexmap::IndexMap;
17use std::collections::HashMap;
18use std::io::{Read, Write};
19use std::net::{TcpListener, TcpStream};
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::{Arc, Mutex};
22
23/// `net` module functions routed through `stdlib::call`.
24pub const MODULE_METHODS: &[&str] = &[
25    "createServer",
26    "connect",
27    "createConnection",
28    "isIP",
29    "isIPv4",
30    "isIPv6",
31    "getDefaultAutoSelectFamily",
32    "setDefaultAutoSelectFamily",
33    "getDefaultAutoSelectFamilyAttemptTimeout",
34    "setDefaultAutoSelectFamilyAttemptTimeout",
35];
36
37/// Instance methods of a `net.BlockList` (parent wires the `BlockList` tag to
38/// `block_list_call` via `native_tag`/`instance_call`).
39pub const BLOCKLIST_METHODS: &[&str] = &["addAddress", "addRange", "addSubnet", "check"];
40
41/// A native-thread hook run on the main thread for each new connection. Set by
42/// `http` to attach its request parser; `None` for a plain `net` server (which
43/// just emits `connection` and calls its JS `connectionListener`).
44type ConnHook = std::rc::Rc<dyn Fn(&Value, &Value) -> Result<(), String>>;
45
46/// Main-thread record for a listening server.
47struct ServerRec {
48    /// The JS server object (a native emitter).
49    emitter: Value,
50    /// Set by `close` to stop the `accept` loop.
51    stop: Arc<AtomicBool>,
52    /// `http`'s per-connection setup hook (if this is an http server).
53    conn_hook: Option<ConnHook>,
54}
55
56/// Main-thread record for a live connection.
57struct SocketRec {
58    /// The JS socket object (a native emitter).
59    emitter: Value,
60    /// Write half of the TCP stream (shared for future duplex; only the main
61    /// thread writes to it today).
62    write: Arc<Mutex<TcpStream>>,
63}
64
65#[derive(Default)]
66struct NetState {
67    next_id: u64,
68    servers: HashMap<u64, ServerRec>,
69    sockets: HashMap<u64, SocketRec>,
70}
71
72thread_local! {
73    static NET: std::cell::RefCell<NetState> = std::cell::RefCell::new(NetState::default());
74}
75
76fn next_id() -> u64 {
77    NET.with(|s| {
78        let mut s = s.borrow_mut();
79        s.next_id += 1;
80        s.next_id
81    })
82}
83
84// ── object construction ──────────────────────────────────────────────────────
85
86/// Build a native emitter object (`@@native` tag + `@@on`/`@@once` listener maps)
87/// carrying the given extra props. Shared shape with `events::new_emitter` so the
88/// EventEmitter methods (`on`/`once`/`emit`/…) work verbatim.
89pub fn new_emitter_object(tag: &str, mut extra: IndexMap<String, Value>) -> Value {
90    with_host(|h| {
91        let on = h.new_object(IndexMap::new());
92        let once = h.new_object(IndexMap::new());
93        let mut m = IndexMap::new();
94        m.insert("@@native".into(), h.new_str(tag));
95        m.insert("@@on".into(), on);
96        m.insert("@@once".into(), once);
97        for (k, v) in extra.drain(..) {
98            m.insert(k, v);
99        }
100        h.new_object(m)
101    })
102}
103
104fn get_prop(recv: &Value, key: &str) -> Option<Value> {
105    with_host(|h| match h.get(recv) {
106        Some(JsObj::Object(p)) => p.get(key).cloned(),
107        _ => None,
108    })
109}
110
111fn set_prop(recv: &Value, key: &str, val: Value) {
112    with_host(|h| {
113        if let Some(JsObj::Object(p)) = h.get_mut(recv) {
114            p.insert(key.to_string(), val);
115        }
116    });
117}
118
119fn u64_prop(recv: &Value, key: &str) -> Option<u64> {
120    get_prop(recv, key).map(|v| with_host(|h| h.to_number(&v)) as u64)
121}
122
123/// Delegate the EventEmitter methods (`on`/`once`/`emit`/…) to `events`; returns
124/// `None` for a non-emitter method so the caller can handle it.
125fn emitter_dispatch(recv: &Value, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
126    match method {
127        "on"
128        | "addListener"
129        | "prependListener"
130        | "once"
131        | "prependOnceListener"
132        | "emit"
133        | "removeListener"
134        | "off"
135        | "removeAllListeners"
136        | "listenerCount"
137        | "eventNames" => Some(super::events::instance_call(recv, method, args.to_vec())),
138        _ => None,
139    }
140}
141
142// ── module: net.createServer ─────────────────────────────────────────────────
143
144/// `stdlib::call` entry for `net.<method>`.
145pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
146    match method {
147        "createServer" => Some(Ok(create_server(args.first().cloned()))),
148        "connect" | "createConnection" => Some(Ok(connect(args))),
149        "isIP" => Some(Ok(Value::Float(is_ip(&arg_string(args, 0)) as f64))),
150        "isIPv4" => Some(Ok(Value::Bool(is_ip(&arg_string(args, 0)) == 4))),
151        "isIPv6" => Some(Ok(Value::Bool(is_ip(&arg_string(args, 0)) == 6))),
152        "getDefaultAutoSelectFamily" => Some(Ok(Value::Bool(AUTO_SELECT_FAMILY.with(|c| c.get())))),
153        "setDefaultAutoSelectFamily" => {
154            let v = args
155                .first()
156                .map(|a| with_host(|h| h.truthy(a)))
157                .unwrap_or(false);
158            AUTO_SELECT_FAMILY.with(|c| c.set(v));
159            Some(Ok(Value::Undef))
160        }
161        "getDefaultAutoSelectFamilyAttemptTimeout" => {
162            Some(Ok(Value::Float(AUTO_SELECT_TIMEOUT.with(|c| c.get()))))
163        }
164        "setDefaultAutoSelectFamilyAttemptTimeout" => {
165            let v = args
166                .first()
167                .map(|a| with_host(|h| h.to_number(a)))
168                .unwrap_or(f64::NAN);
169            if v.is_finite() && v >= 1.0 {
170                AUTO_SELECT_TIMEOUT.with(|c| c.set(v));
171            }
172            Some(Ok(Value::Undef))
173        }
174        _ => None,
175    }
176}
177
178thread_local! {
179    /// `net` module default for `autoSelectFamily` (best-effort; not consulted by
180    /// our connect path, which is single-address).
181    static AUTO_SELECT_FAMILY: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
182    /// `net` module default for `autoSelectFamilyAttemptTimeout` (ms).
183    static AUTO_SELECT_TIMEOUT: std::cell::Cell<f64> = const { std::cell::Cell::new(250.0) };
184}
185
186/// String value of `args[i]` (empty string if absent/undefined).
187fn arg_string(args: &[Value], i: usize) -> String {
188    match args.get(i) {
189        Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
190        _ => String::new(),
191    }
192}
193
194/// Node `net.isIP(input)` → `4`, `6`, or `0`. Pure parse (no DNS).
195fn is_ip(input: &str) -> i32 {
196    use std::net::{Ipv4Addr, Ipv6Addr};
197    if input.parse::<Ipv4Addr>().is_ok() {
198        4
199    } else if input.parse::<Ipv6Addr>().is_ok() {
200        6
201    } else {
202        0
203    }
204}
205
206/// Non-function `net` namespace members: the class constructors, exposed as
207/// builtin ctor namespaces so `.prototype` resolves and `new net.X(...)` routes
208/// through `stdlib::construct` → `net::construct`. `Stream` is a legacy alias of
209/// `Socket`. Reachable via `namespace_property` → `stdlib::constant` once the
210/// parent adds a `"net" => net::constant(name)` arm.
211pub fn constant(name: &str) -> Option<Value> {
212    match name {
213        "Server" | "Socket" | "Stream" | "SocketAddress" | "BlockList" => {
214            Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
215        }
216        _ => None,
217    }
218}
219
220/// Build a `net.Server`. An optional `connectionListener` is stored on the object
221/// and invoked (plus `connection` emitted) for every accepted socket.
222pub fn create_server(connection_listener: Option<Value>) -> Value {
223    let mut extra = IndexMap::new();
224    if let Some(cb) = connection_listener.filter(|v| !matches!(v, Value::Undef)) {
225        extra.insert("@@connListener".into(), cb);
226    }
227    new_emitter_object("Server", extra)
228}
229
230// ── client: net.connect / net.createConnection / new net.Socket ──────────────
231
232/// Build a bare `net.Socket` (a native emitter) with an id but no live stream, as
233/// produced by `new net.Socket()`. `connect` is called separately.
234pub fn new_socket() -> Value {
235    let sock_id = next_id();
236    let mut extra = IndexMap::new();
237    extra.insert("@@netid".into(), Value::Float(sock_id as f64));
238    extra.insert("connecting".into(), Value::Bool(false));
239    new_emitter_object("Socket", extra)
240}
241
242/// `net.connect(...)` / `net.createConnection(...)`: build a `Socket` and start
243/// the connection immediately. Returns the socket synchronously; the `connect`
244/// event fires on the main thread once the TCP handshake completes.
245pub fn connect(args: &[Value]) -> Value {
246    let socket = new_socket();
247    socket_connect(&socket, args);
248    socket
249}
250
251/// Parse the `(port[, host])` / `(options)` argument shapes of `connect`,
252/// returning `(port, host, connectListener)`.
253fn parse_connect_args(args: &[Value]) -> (u16, String, Option<Value>) {
254    let mut port: u16 = 0;
255    let mut host = "localhost".to_string();
256    let mut cb: Option<Value> = None;
257    for a in args {
258        if with_host(|h| crate::host::is_callable(h, a)) {
259            cb = Some(a.clone());
260        } else if with_host(|h| h.as_str(a)).is_some() {
261            host = with_host(|h| h.str_of(a));
262        } else if matches!(a, Value::Obj(_)) {
263            if let Some(v) = get_prop(a, "port") {
264                let n = with_host(|h| h.to_number(&v));
265                if !n.is_nan() {
266                    port = n as u16;
267                }
268            }
269            for key in ["host", "hostname"] {
270                if let Some(v) = get_prop(a, key).filter(|v| with_host(|h| h.as_str(v)).is_some()) {
271                    host = with_host(|h| h.str_of(&v));
272                }
273            }
274        } else {
275            let n = with_host(|h| h.to_number(a));
276            if !n.is_nan() {
277                port = n as u16;
278            }
279        }
280    }
281    (port, host, cb)
282}
283
284/// Drive `socket.connect(...)`: register the optional `connectListener`, then
285/// spawn the blocking `TcpStream::connect` on a background thread. On success the
286/// posted `IoTask` (`on_connect`) builds the reader; on failure it emits `error`.
287fn socket_connect(socket: &Value, args: &[Value]) {
288    let (port, host, cb) = parse_connect_args(args);
289    if let Some(cb) = cb {
290        let _ = super::events::instance_call(
291            socket,
292            "on",
293            vec![with_host(|h| h.new_str("connect")), cb],
294        );
295    }
296    let sock_id = u64_prop(socket, "@@netid").unwrap_or_else(next_id);
297    set_prop(socket, "@@netid", Value::Float(sock_id as f64));
298    set_prop(socket, "connecting", Value::Bool(true));
299    with_host(|h| h.incr_handle());
300
301    let tx = with_host(|h| h.io_sender());
302    let socket_val = socket.clone();
303    std::thread::spawn(move || match TcpStream::connect((host.as_str(), port)) {
304        Ok(stream) => {
305            let _ = tx.send(Box::new(move || on_connect(sock_id, socket_val, stream)));
306        }
307        Err(e) => {
308            let msg = format!("connect ECONNREFUSED {host}:{port}: {e}");
309            let _ = tx.send(Box::new(move || on_connect_error(socket_val, msg)));
310        }
311    });
312}
313
314/// Main-thread completion of a successful client connect: register the socket,
315/// spawn its reader (same loop the server side uses), then emit `connect`.
316fn on_connect(sock_id: u64, socket: Value, stream: TcpStream) -> Result<(), String> {
317    let read_stream = match stream.try_clone() {
318        Ok(s) => s,
319        Err(_) => {
320            with_host(|h| h.decr_handle());
321            return Ok(());
322        }
323    };
324    let write = Arc::new(Mutex::new(stream));
325    NET.with(|s| {
326        s.borrow_mut().sockets.insert(
327            sock_id,
328            SocketRec {
329                emitter: socket.clone(),
330                write,
331            },
332        );
333    });
334    set_prop(&socket, "connecting", Value::Bool(false));
335    // The reader gets its own handle registration via `on_socket_close`'s
336    // `decr_handle`; the `incr` from `socket_connect` covers this socket's life.
337    let tx = with_host(|h| h.io_sender());
338    std::thread::spawn(move || reader_loop(read_stream, sock_id, tx));
339    super::events::instance_call(&socket, "emit", vec![with_host(|h| h.new_str("connect"))])?;
340    Ok(())
341}
342
343/// Main-thread completion of a failed client connect: release the handle and emit
344/// `error` (Node emits an `Error` with `code: 'ECONNREFUSED'`).
345fn on_connect_error(socket: Value, msg: String) -> Result<(), String> {
346    with_host(|h| h.decr_handle());
347    let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
348    let err = with_host(|h| {
349        let mut m = IndexMap::new();
350        m.insert("message".into(), h.new_str(msg.clone()));
351        m.insert("code".into(), h.new_str("ECONNREFUSED"));
352        h.new_object(m)
353    });
354    super::events::instance_call(
355        &socket,
356        "emit",
357        vec![with_host(|h| h.new_str("error")), err],
358    )?;
359    Ok(())
360}
361
362// ── constructors: new net.Socket() / new net.Server() / SocketAddress / BlockList
363
364/// `stdlib::construct` entry for `net` classes. Parent wires this into
365/// `stdlib::construct`.
366pub fn construct(name: &str, args: &[Value]) -> Option<Result<Value, String>> {
367    match name {
368        "Socket" | "Stream" => Some(Ok(new_socket())),
369        "Server" => Some(Ok(create_server(
370            args.first()
371                .cloned()
372                .filter(|v| with_host(|h| crate::host::is_callable(h, v))),
373        ))),
374        "SocketAddress" => Some(Ok(socket_address(args))),
375        "BlockList" => Some(Ok(new_block_list())),
376        _ => None,
377    }
378}
379
380/// `new net.SocketAddress({ address, port, family, flowlabel })` — a plain data
381/// holder (Node exposes the fields as getters; a data object reads identically).
382fn socket_address(args: &[Value]) -> Value {
383    let opts = args.first().cloned().unwrap_or(Value::Undef);
384    let mut address = String::new();
385    let mut family = "ipv4".to_string();
386    let mut have_family = false;
387    let mut port = 0f64;
388    let mut flowlabel = 0f64;
389    if matches!(opts, Value::Obj(_)) {
390        if let Some(v) = get_prop(&opts, "address").filter(|v| with_host(|h| h.as_str(v)).is_some())
391        {
392            address = with_host(|h| h.str_of(&v));
393        }
394        if let Some(v) = get_prop(&opts, "family").filter(|v| with_host(|h| h.as_str(v)).is_some())
395        {
396            family = with_host(|h| h.str_of(&v)).to_ascii_lowercase();
397            have_family = true;
398        }
399        if let Some(v) = get_prop(&opts, "port") {
400            let n = with_host(|h| h.to_number(&v));
401            if !n.is_nan() {
402                port = n;
403            }
404        }
405        if let Some(v) = get_prop(&opts, "flowlabel") {
406            let n = with_host(|h| h.to_number(&v));
407            if !n.is_nan() {
408                flowlabel = n;
409            }
410        }
411    }
412    if !have_family {
413        family = if is_ip(&address) == 6 { "ipv6" } else { "ipv4" }.to_string();
414    }
415    if address.is_empty() {
416        address = if family == "ipv6" { "::" } else { "127.0.0.1" }.to_string();
417    }
418    with_host(|h| {
419        let mut m = IndexMap::new();
420        m.insert("@@native".into(), h.new_str("SocketAddress"));
421        m.insert("address".into(), h.new_str(address));
422        m.insert("port".into(), Value::Float(port));
423        m.insert("family".into(), h.new_str(family));
424        m.insert("flowlabel".into(), Value::Float(flowlabel));
425        h.new_object(m)
426    })
427}
428
429// ── BlockList ────────────────────────────────────────────────────────────────
430
431/// One `BlockList` rule. All comparisons happen in the integer domain (`u128`
432/// covers both families; IPv4 is mapped into the low 32 bits).
433enum BlockRule {
434    /// Single address (family-tagged).
435    Addr { v6: bool, val: u128 },
436    /// Inclusive `[start, end]` range (family-tagged).
437    Range { v6: bool, start: u128, end: u128 },
438    /// CIDR subnet: `network`/`prefix` (family-tagged).
439    Subnet {
440        v6: bool,
441        network: u128,
442        prefix: u32,
443    },
444}
445
446thread_local! {
447    static BLOCK_LISTS: std::cell::RefCell<HashMap<u64, Vec<BlockRule>>> =
448        std::cell::RefCell::new(HashMap::new());
449}
450
451/// `new net.BlockList()` — a `@@native`-tagged holder whose rules live in the
452/// main-thread `BLOCK_LISTS` registry keyed by `@@blid`.
453fn new_block_list() -> Value {
454    let id = next_id();
455    BLOCK_LISTS.with(|b| {
456        b.borrow_mut().insert(id, Vec::new());
457    });
458    with_host(|h| {
459        let mut m = IndexMap::new();
460        m.insert("@@native".into(), h.new_str("BlockList"));
461        m.insert("@@blid".into(), Value::Float(id as f64));
462        h.new_object(m)
463    })
464}
465
466/// Parse an IP string into `(is_ipv6, u128)`. IPv4 lands in the low 32 bits.
467fn ip_to_u128(s: &str) -> Option<(bool, u128)> {
468    use std::net::{Ipv4Addr, Ipv6Addr};
469    if let Ok(v4) = s.parse::<Ipv4Addr>() {
470        return Some((false, u32::from(v4) as u128));
471    }
472    if let Ok(v6) = s.parse::<Ipv6Addr>() {
473        return Some((true, u128::from(v6)));
474    }
475    None
476}
477
478/// `BlockList` instance dispatch (parent routes the `BlockList` tag here).
479pub fn block_list_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
480    let Some(id) = u64_prop(recv, "@@blid") else {
481        return Err(crate::host::type_error("invalid BlockList"));
482    };
483    match method {
484        "addAddress" => {
485            let addr = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
486            if let Some((v6, val)) = ip_to_u128(&addr) {
487                BLOCK_LISTS.with(|b| {
488                    if let Some(rules) = b.borrow_mut().get_mut(&id) {
489                        rules.push(BlockRule::Addr { v6, val });
490                    }
491                });
492            }
493            Ok(Value::Undef)
494        }
495        "addRange" => {
496            let start = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
497            let end = with_host(|h| h.str_of(&args.get(1).cloned().unwrap_or(Value::Undef)));
498            if let (Some((v6, s)), Some((_, e))) = (ip_to_u128(&start), ip_to_u128(&end)) {
499                BLOCK_LISTS.with(|b| {
500                    if let Some(rules) = b.borrow_mut().get_mut(&id) {
501                        rules.push(BlockRule::Range {
502                            v6,
503                            start: s.min(e),
504                            end: s.max(e),
505                        });
506                    }
507                });
508            }
509            Ok(Value::Undef)
510        }
511        "addSubnet" => {
512            let net = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
513            let prefix =
514                with_host(|h| h.to_number(&args.get(1).cloned().unwrap_or(Value::Undef))) as u32;
515            if let Some((v6, network)) = ip_to_u128(&net) {
516                BLOCK_LISTS.with(|b| {
517                    if let Some(rules) = b.borrow_mut().get_mut(&id) {
518                        rules.push(BlockRule::Subnet {
519                            v6,
520                            network,
521                            prefix,
522                        });
523                    }
524                });
525            }
526            Ok(Value::Undef)
527        }
528        "check" => {
529            let addr = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
530            let Some((v6, val)) = ip_to_u128(&addr) else {
531                return Ok(Value::Bool(false));
532            };
533            let blocked = BLOCK_LISTS.with(|b| {
534                b.borrow()
535                    .get(&id)
536                    .map(|rules| rules.iter().any(|r| rule_matches(r, v6, val)))
537                    .unwrap_or(false)
538            });
539            Ok(Value::Bool(blocked))
540        }
541        _ => Err(crate::host::type_error(&format!(
542            "blocklist.{method} is not a function"
543        ))),
544    }
545}
546
547/// Whether a query address (`v6`/`val`) is covered by a rule (family must match).
548fn rule_matches(rule: &BlockRule, q_v6: bool, q_val: u128) -> bool {
549    match rule {
550        BlockRule::Addr { v6, val } => *v6 == q_v6 && *val == q_val,
551        BlockRule::Range { v6, start, end } => *v6 == q_v6 && q_val >= *start && q_val <= *end,
552        BlockRule::Subnet {
553            v6,
554            network,
555            prefix,
556        } => {
557            if *v6 != q_v6 {
558                return false;
559            }
560            let bits = if q_v6 { 128 } else { 32 };
561            let p = (*prefix).min(bits);
562            if p == 0 {
563                return true;
564            }
565            let shift = bits - p;
566            (q_val >> shift) == (*network >> shift)
567        }
568    }
569}
570
571/// Attach an `http`-style per-connection hook to a server object (called by
572/// `http::create_server`). Stored in the main-thread registry, keyed by the
573/// server's assigned id once it starts listening — so we stash it on the object
574/// until `listen` registers the record.
575pub fn set_conn_hook(server: &Value, hook: ConnHook) {
576    // Marker so `listen` knows to move the hook into the `ServerRec`.
577    set_prop(server, "@@httpMode", Value::Bool(true));
578    PENDING_HOOKS.with(|p| p.borrow_mut().push((server.clone(), hook)));
579}
580
581thread_local! {
582    /// Hooks registered before `listen` assigns a server id.
583    static PENDING_HOOKS: std::cell::RefCell<Vec<(Value, ConnHook)>> =
584        const { std::cell::RefCell::new(Vec::new()) };
585}
586
587fn take_pending_hook(server: &Value) -> Option<ConnHook> {
588    PENDING_HOOKS.with(|p| {
589        let mut p = p.borrow_mut();
590        p.iter()
591            .position(|(s, _)| s == server)
592            .map(|pos| p.remove(pos).1)
593    })
594}
595
596// ── instance methods (Server / Socket) ───────────────────────────────────────
597
598pub fn instance_call(
599    tag: &str,
600    recv: &Value,
601    method: &str,
602    args: Vec<Value>,
603) -> Result<Value, String> {
604    match tag {
605        "Server" => server_call(recv, method, args),
606        "Socket" => socket_call(recv, method, args),
607        "BlockList" => block_list_call(recv, method, args),
608        _ => Err(crate::host::type_error(&format!(
609            "{method} is not a function"
610        ))),
611    }
612}
613
614fn server_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
615    if let Some(r) = emitter_dispatch(recv, method, &args) {
616        return r;
617    }
618    match method {
619        "listen" => server_listen(recv, &args),
620        "close" => server_close(recv, &args),
621        "address" => Ok(get_prop(recv, "@@address").unwrap_or(Value::Undef)),
622        _ => Err(crate::host::type_error(&format!(
623            "server.{method} is not a function"
624        ))),
625    }
626}
627
628/// `server.listen(port[, host][, callback])`. Binds on the main thread (so bind
629/// errors surface synchronously), then spawns the `accept` loop thread. The
630/// `listening` event + callback fire asynchronously via a posted `IoTask`.
631fn server_listen(recv: &Value, args: &[Value]) -> Result<Value, String> {
632    // Argument shapes: (port), (port, cb), (port, host), (port, host, cb).
633    let port = with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(0.0)) as u16;
634    let mut host = "0.0.0.0".to_string();
635    let mut cb: Option<Value> = None;
636    for a in &args[1.min(args.len())..] {
637        if with_host(|h| h.as_str(a)).is_some() {
638            host = with_host(|h| h.str_of(a));
639        } else if with_host(|h| crate::host::is_callable(h, a)) {
640            cb = Some(a.clone());
641        }
642    }
643
644    let listener = TcpListener::bind((host.as_str(), port))
645        .map_err(|e| format!("Error: listen EADDRINUSE: {e}"))?;
646    let local = listener.local_addr().ok();
647
648    // Assign an id and register the server as a live handle.
649    let id = next_id();
650    set_prop(recv, "@@netid", Value::Float(id as f64));
651    if let Some(addr) = local {
652        let mut a = IndexMap::new();
653        a.insert("port".into(), Value::Float(addr.port() as f64));
654        a.insert(
655            "address".into(),
656            with_host(|h| h.new_str(addr.ip().to_string())),
657        );
658        a.insert(
659            "family".into(),
660            with_host(|h| h.new_str(if addr.is_ipv6() { "IPv6" } else { "IPv4" })),
661        );
662        let addr_obj = with_host(|h| h.new_object(a));
663        set_prop(recv, "@@address", addr_obj);
664    }
665    let conn_hook = take_pending_hook(recv);
666    let stop = Arc::new(AtomicBool::new(false));
667    NET.with(|s| {
668        s.borrow_mut().servers.insert(
669            id,
670            ServerRec {
671                emitter: recv.clone(),
672                stop: stop.clone(),
673                conn_hook,
674            },
675        );
676    });
677    with_host(|h| h.incr_handle());
678
679    // Spawn the accept loop. Non-blocking + short poll so `close` can stop it.
680    let tx = with_host(|h| h.io_sender());
681    listener.set_nonblocking(true).ok();
682    std::thread::spawn(move || loop {
683        if stop.load(Ordering::Acquire) {
684            break;
685        }
686        match listener.accept() {
687            Ok((stream, _addr)) => {
688                let tx2 = tx.clone();
689                let _ = tx.send(Box::new(move || on_connection(id, stream, tx2)));
690            }
691            Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
692                std::thread::sleep(std::time::Duration::from_millis(5));
693            }
694            Err(_) => break,
695        }
696    });
697
698    // Fire `listening` + callback asynchronously on the main thread.
699    let server = recv.clone();
700    let _ = with_host(|h| h.io_sender()).send(Box::new(move || {
701        super::events::instance_call(&server, "emit", vec![with_host(|h| h.new_str("listening"))])?;
702        if let Some(cb) = cb {
703            invoke(&cb, Vec::new(), None)?;
704        }
705        Ok(())
706    }));
707    Ok(recv.clone())
708}
709
710/// `server.close([cb])`: stop accepting, drop the handle, wake the loop.
711fn server_close(recv: &Value, args: &[Value]) -> Result<Value, String> {
712    if let Some(id) = u64_prop(recv, "@@netid") {
713        let rec = NET.with(|s| s.borrow_mut().servers.remove(&id));
714        if let Some(rec) = rec {
715            rec.stop.store(true, Ordering::Release);
716            with_host(|h| h.decr_handle());
717            // Wake the blocking loop so it can re-evaluate `open_handles`.
718            let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
719        }
720    }
721    // `close` callback registers as a one-shot `close` listener in Node.
722    if let Some(cb) = args
723        .first()
724        .filter(|v| with_host(|h| crate::host::is_callable(h, v)))
725    {
726        invoke(cb, Vec::new(), None)?;
727    }
728    super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))])?;
729    Ok(recv.clone())
730}
731
732fn socket_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
733    if let Some(r) = emitter_dispatch(recv, method, &args) {
734        return r;
735    }
736    match method {
737        "write" => {
738            if let Some(id) = u64_prop(recv, "@@netid") {
739                socket_write_id(id, &value_bytes(args.first()));
740            }
741            Ok(Value::Bool(true))
742        }
743        "end" => {
744            if let Some(id) = u64_prop(recv, "@@netid") {
745                if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
746                    socket_write_id(id, &value_bytes(Some(chunk)));
747                }
748                socket_shutdown(id);
749            }
750            Ok(recv.clone())
751        }
752        "destroy" => {
753            if let Some(id) = u64_prop(recv, "@@netid") {
754                socket_shutdown(id);
755            }
756            Ok(recv.clone())
757        }
758        "connect" => {
759            socket_connect(recv, &args);
760            Ok(recv.clone())
761        }
762        "address" => Ok(get_prop(recv, "@@address").unwrap_or(Value::Undef)),
763        "setEncoding" | "setTimeout" | "setNoDelay" | "setKeepAlive" | "ref" | "unref"
764        | "pause" | "resume" => {
765            // Accepted no-ops for M1 (curl needs none of these).
766            Ok(recv.clone())
767        }
768        _ => Err(crate::host::type_error(&format!(
769            "socket.{method} is not a function"
770        ))),
771    }
772}
773
774/// Raw bytes of a `write`/`end` argument: a Buffer's bytes, or a string's UTF-8.
775fn value_bytes(v: Option<&Value>) -> Vec<u8> {
776    let Some(v) = v else { return Vec::new() };
777    // Buffer instance: read its `@@bytes` array.
778    let is_buffer =
779        with_host(|h| matches!(h.get(v), Some(JsObj::Object(p)) if p.contains_key("@@bytes")));
780    if is_buffer {
781        return with_host(|h| match h.get(v) {
782            Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
783                Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
784                _ => Vec::new(),
785            },
786            _ => Vec::new(),
787        });
788    }
789    with_host(|h| h.str_of(v)).into_bytes()
790}
791
792// ── main-thread I/O dispatch (run from posted IoTasks) ────────────────────────
793
794/// A newly accepted connection: build the `Socket`, register it, spawn its
795/// reader, then emit `connection` and run the server's listener/hook. Runs on the
796/// main thread.
797fn on_connection(
798    server_id: u64,
799    stream: TcpStream,
800    tx: std::sync::mpsc::Sender<crate::host::IoTask>,
801) -> Result<(), String> {
802    // Server gone (closed before this event drained): drop the connection.
803    let server = NET.with(|s| {
804        s.borrow()
805            .servers
806            .get(&server_id)
807            .map(|r| r.emitter.clone())
808    });
809    let Some(server) = server else { return Ok(()) };
810
811    // Reader gets an independent handle; writes go through the original stream.
812    let read_stream = match stream.try_clone() {
813        Ok(s) => s,
814        Err(_) => return Ok(()),
815    };
816    let write = Arc::new(Mutex::new(stream));
817
818    let sock_id = next_id();
819    let mut extra = IndexMap::new();
820    extra.insert("@@netid".into(), Value::Float(sock_id as f64));
821    let socket = new_emitter_object("Socket", extra);
822    NET.with(|s| {
823        s.borrow_mut().sockets.insert(
824            sock_id,
825            SocketRec {
826                emitter: socket.clone(),
827                write,
828            },
829        );
830    });
831    with_host(|h| h.incr_handle());
832
833    // Reader thread: raw bytes → posted IoTasks. Never touches the host.
834    std::thread::spawn(move || reader_loop(read_stream, sock_id, tx));
835
836    // Emit `connection` + run the server's connection handling.
837    super::events::instance_call(&server, "emit", vec![with_host(|_h| socket.clone())])?;
838    let hook = NET.with(|s| {
839        s.borrow()
840            .servers
841            .get(&server_id)
842            .and_then(|r| r.conn_hook.clone())
843    });
844    if let Some(hook) = hook {
845        hook(&server, &socket)?;
846    } else if let Some(cb) = get_prop(&server, "@@connListener") {
847        invoke(&cb, vec![socket.clone()], None)?;
848    }
849    Ok(())
850}
851
852/// Background reader: blocking `read` loop posting `data`/`end`/`close` events.
853fn reader_loop(
854    mut stream: TcpStream,
855    sock_id: u64,
856    tx: std::sync::mpsc::Sender<crate::host::IoTask>,
857) {
858    let mut buf = [0u8; 8192];
859    loop {
860        match stream.read(&mut buf) {
861            Ok(0) => {
862                let _ = tx.send(Box::new(move || on_socket_end(sock_id)));
863                break;
864            }
865            Ok(n) => {
866                let bytes = buf[..n].to_vec();
867                let _ = tx.send(Box::new(move || on_socket_data(sock_id, bytes)));
868            }
869            Err(_) => {
870                let _ = tx.send(Box::new(move || on_socket_close(sock_id)));
871                break;
872            }
873        }
874    }
875}
876
877fn on_socket_data(sock_id: u64, bytes: Vec<u8>) -> Result<(), String> {
878    let socket = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
879    let Some(socket) = socket else { return Ok(()) };
880    // Feed the http parser first (if this socket is an http connection).
881    super::http::feed(sock_id, &socket, &bytes)?;
882    // Then emit `data` to any JS listeners (as a Buffer, like Node).
883    let chunk = super::buffer::from_bytes(&bytes);
884    super::events::instance_call(
885        &socket,
886        "emit",
887        vec![with_host(|h| h.new_str("data")), chunk],
888    )?;
889    Ok(())
890}
891
892fn on_socket_end(sock_id: u64) -> Result<(), String> {
893    let socket = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
894    if let Some(socket) = socket {
895        super::events::instance_call(&socket, "emit", vec![with_host(|h| h.new_str("end"))])?;
896    }
897    on_socket_close(sock_id)
898}
899
900fn on_socket_close(sock_id: u64) -> Result<(), String> {
901    let rec = NET.with(|s| s.borrow_mut().sockets.remove(&sock_id));
902    super::http::drop_conn(sock_id);
903    if let Some(rec) = rec {
904        super::events::instance_call(
905            &rec.emitter,
906            "emit",
907            vec![with_host(|h| h.new_str("close"))],
908        )?;
909        with_host(|h| h.decr_handle());
910        // Wake the loop so a closed last handle lets it exit.
911        let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
912    }
913    Ok(())
914}
915
916// ── writes (used by http::ServerResponse and net Socket) ──────────────────────
917
918/// Write raw bytes to a live socket by id (no-op if it has closed).
919pub fn socket_write_id(sock_id: u64, data: &[u8]) {
920    let write = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.write.clone()));
921    if let Some(write) = write {
922        if let Ok(mut stream) = write.lock() {
923            let _ = stream.write_all(data);
924            let _ = stream.flush();
925        }
926    }
927}
928
929/// Shut down the write half of a socket (`socket.end()`), signaling EOF to peer.
930fn socket_shutdown(sock_id: u64) {
931    let write = NET.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.write.clone()));
932    if let Some(write) = write {
933        if let Ok(stream) = write.lock() {
934            let _ = stream.shutdown(std::net::Shutdown::Write);
935        }
936    }
937}