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