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