1use crate::host::{invoke, with_host, JsObj};
13use fusevm::Value;
14use indexmap::IndexMap;
15use std::collections::HashMap;
16use std::net::UdpSocket;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::Arc;
19use std::time::Duration;
20
21pub const MODULE_METHODS: &[&str] = &["createSocket"];
23
24pub const SOCKET_TAG: &str = "UdpSocket";
26
27pub const SOCKET_METHODS: &[&str] = &[
29 "bind",
30 "send",
31 "close",
32 "address",
33 "setBroadcast",
34 "setTTL",
35 "setMulticastTTL",
36 "setMulticastLoopback",
37 "setMulticastInterface",
38 "addMembership",
39 "dropMembership",
40 "addSourceSpecificMembership",
41 "dropSourceSpecificMembership",
42 "setRecvBufferSize",
43 "setSendBufferSize",
44 "getRecvBufferSize",
45 "getSendBufferSize",
46 "connect",
47 "disconnect",
48 "remoteAddress",
49 "ref",
50 "unref",
51];
52
53const POLL: Duration = Duration::from_millis(200);
55
56struct UdpRec {
58 emitter: Value,
60 socket: Arc<UdpSocket>,
62 stop: Arc<AtomicBool>,
64}
65
66#[derive(Default)]
67struct DgramState {
68 next_id: u64,
69 sockets: HashMap<u64, UdpRec>,
70}
71
72thread_local! {
73 static DGRAM: std::cell::RefCell<DgramState> = std::cell::RefCell::new(DgramState::default());
74}
75
76fn next_id() -> u64 {
77 DGRAM.with(|s| {
78 let mut s = s.borrow_mut();
79 s.next_id += 1;
80 s.next_id
81 })
82}
83
84fn get_prop(recv: &Value, key: &str) -> Option<Value> {
87 with_host(|h| match h.get(recv) {
88 Some(JsObj::Object(p)) => p.get(key).cloned(),
89 _ => None,
90 })
91}
92
93fn set_prop(recv: &Value, key: &str, val: Value) {
94 with_host(|h| {
95 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
96 p.insert(key.to_string(), val);
97 }
98 });
99}
100
101fn u64_prop(recv: &Value, key: &str) -> Option<u64> {
102 get_prop(recv, key).map(|v| with_host(|h| h.to_number(&v)) as u64)
103}
104
105fn is_udp6(recv: &Value) -> bool {
106 get_prop(recv, "@@udptype")
107 .map(|v| with_host(|h| h.str_of(&v)))
108 .as_deref()
109 == Some("udp6")
110}
111
112fn default_bind_host(recv: &Value) -> &'static str {
114 if is_udp6(recv) {
115 "::"
116 } else {
117 "0.0.0.0"
118 }
119}
120
121fn default_send_host(recv: &Value) -> &'static str {
122 if is_udp6(recv) {
123 "::1"
124 } else {
125 "127.0.0.1"
126 }
127}
128
129fn is_num(v: &Value) -> bool {
130 matches!(v, Value::Float(_) | Value::Int(_))
131}
132
133fn is_str(v: &Value) -> bool {
134 matches!(v, Value::Str(_)) || with_host(|h| matches!(h.get(v), Some(JsObj::Str(_))))
135}
136
137fn value_bytes(v: &Value) -> Vec<u8> {
140 let is_buffer =
141 with_host(|h| matches!(h.get(v), Some(JsObj::Object(p)) if p.contains_key("@@bytes")));
142 if is_buffer {
143 return with_host(|h| match h.get(v) {
144 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
145 Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
146 _ => Vec::new(),
147 },
148 _ => Vec::new(),
149 });
150 }
151 with_host(|h| h.str_of(v)).into_bytes()
152}
153
154fn emitter_dispatch(recv: &Value, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
156 super::events::METHODS
157 .contains(&method)
158 .then(|| super::events::instance_call(recv, method, args.to_vec()))
159}
160
161pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
165 match method {
166 "createSocket" => Some(Ok(create_socket(args))),
167 _ => None,
168 }
169}
170
171pub fn create_socket(args: &[Value]) -> Value {
175 let first = args.first().cloned().unwrap_or(Value::Undef);
176 let sock_type = if is_str(&first) {
177 with_host(|h| h.str_of(&first))
178 } else {
179 with_host(|h| match h.get(&first) {
181 Some(JsObj::Object(p)) => p.get("type").map(|v| h.str_of(v)),
182 _ => None,
183 })
184 .unwrap_or_else(|| "udp4".to_string())
185 };
186 let sock_type = if sock_type == "udp6" { "udp6" } else { "udp4" };
187
188 let mut extra = IndexMap::new();
189 extra.insert("@@udptype".into(), with_host(|h| h.new_str(sock_type)));
190 let socket = super::net::new_emitter_object(SOCKET_TAG, extra);
191
192 if let Some(cb) = args
194 .get(1)
195 .filter(|v| with_host(|h| crate::host::is_callable(h, v)))
196 {
197 let _ = super::events::instance_call(
198 &socket,
199 "on",
200 vec![with_host(|h| h.new_str("message")), cb.clone()],
201 );
202 }
203 socket
204}
205
206pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
209 if let Some(r) = emitter_dispatch(recv, method, &args) {
210 return r;
211 }
212 match method {
213 "bind" => socket_bind(recv, &args),
214 "send" => socket_send(recv, &args),
215 "close" => socket_close(recv, &args),
216 "address" => socket_address(recv),
217 "setBroadcast" => {
220 let on = with_host(|h| h.truthy(args.first().unwrap_or(&Value::Undef)));
221 if let Some(sock) = live_socket(recv) {
222 sock.set_broadcast(on).ok();
223 }
224 Ok(recv.clone())
225 }
226 "setTTL" | "setMulticastTTL" => {
227 let ttl = with_host(|h| h.to_number(args.first().unwrap_or(&Value::Undef))) as u32;
228 if let Some(sock) = live_socket(recv) {
229 if method == "setTTL" {
230 sock.set_ttl(ttl).ok();
231 } else {
232 sock.set_multicast_ttl_v4(ttl).ok();
233 }
234 }
235 Ok(args.first().cloned().unwrap_or(Value::Undef))
236 }
237 "getRecvBufferSize" | "getSendBufferSize" => Ok(Value::Float(65536.0)),
238 "setMulticastLoopback"
242 | "setMulticastInterface"
243 | "addMembership"
244 | "dropMembership"
245 | "addSourceSpecificMembership"
246 | "dropSourceSpecificMembership"
247 | "setRecvBufferSize"
248 | "setSendBufferSize"
249 | "connect"
250 | "disconnect"
251 | "remoteAddress"
252 | "ref"
253 | "unref" => Ok(recv.clone()),
254 _ => Err(crate::host::type_error(&format!(
255 "socket.{method} is not a function"
256 ))),
257 }
258}
259
260fn live_socket(recv: &Value) -> Option<Arc<UdpSocket>> {
262 let id = u64_prop(recv, "@@dgramid")?;
263 DGRAM.with(|s| s.borrow().sockets.get(&id).map(|r| r.socket.clone()))
264}
265
266fn socket_bind(recv: &Value, args: &[Value]) -> Result<Value, String> {
273 let mut port: u16 = 0;
276 let mut host = default_bind_host(recv).to_string();
277 let mut cb: Option<Value> = None;
278
279 if let Some(first) = args.first() {
280 if is_num(first) {
281 port = with_host(|h| h.to_number(first)) as u16;
282 } else if with_host(
283 |h| matches!(h.get(first), Some(JsObj::Object(p)) if !p.contains_key("@@native")),
284 ) {
285 with_host(|h| {
287 if let Some(JsObj::Object(p)) = h.get(first) {
288 if let Some(pv) = p.get("port") {
289 port = h.to_number(pv) as u16;
290 }
291 if let Some(av) = p.get("address").map(|v| h.str_of(v)) {
292 host = av;
293 }
294 }
295 });
296 }
297 }
298 for a in args.iter().skip(1) {
299 if is_str(a) {
300 host = with_host(|h| h.str_of(a));
301 } else if with_host(|h| crate::host::is_callable(h, a)) {
302 cb = Some(a.clone());
303 }
304 }
305
306 do_bind(recv, &host, port)?;
307
308 let socket = recv.clone();
310 let _ = with_host(|h| h.io_sender()).send(Box::new(move || {
311 if let Some(cb) = cb {
312 super::events::instance_call(
313 &socket,
314 "once",
315 vec![with_host(|h| h.new_str("listening")), cb],
316 )?;
317 }
318 super::events::instance_call(&socket, "emit", vec![with_host(|h| h.new_str("listening"))])?;
319 Ok(())
320 }));
321 Ok(recv.clone())
322}
323
324fn do_bind(recv: &Value, host: &str, port: u16) -> Result<Arc<UdpSocket>, String> {
327 if let Some(sock) = live_socket(recv) {
328 return Ok(sock);
329 }
330 let socket =
331 UdpSocket::bind((host, port)).map_err(|e| format!("Error: bind EADDRINUSE: {e}"))?;
332 socket.set_read_timeout(Some(POLL)).ok();
334 let socket = Arc::new(socket);
335
336 let id = next_id();
337 set_prop(recv, "@@dgramid", Value::Float(id as f64));
338 let stop = Arc::new(AtomicBool::new(false));
339 DGRAM.with(|s| {
340 s.borrow_mut().sockets.insert(
341 id,
342 UdpRec {
343 emitter: recv.clone(),
344 socket: socket.clone(),
345 stop: stop.clone(),
346 },
347 );
348 });
349 with_host(|h| h.incr_handle());
350
351 let tx = with_host(|h| h.io_sender());
353 let recv_sock = socket.clone();
354 std::thread::spawn(move || recv_loop(recv_sock, id, stop, tx));
355
356 Ok(socket)
357}
358
359fn recv_loop(
362 socket: Arc<UdpSocket>,
363 id: u64,
364 stop: Arc<AtomicBool>,
365 tx: std::sync::mpsc::Sender<crate::host::IoTask>,
366) {
367 let mut buf = [0u8; 65536];
368 loop {
369 if stop.load(Ordering::Acquire) {
370 break;
371 }
372 match socket.recv_from(&mut buf) {
373 Ok((n, src)) => {
374 let bytes = buf[..n].to_vec();
375 let address = src.ip().to_string();
376 let port = src.port();
377 let family = if src.is_ipv6() { "IPv6" } else { "IPv4" };
378 let _ = tx.send(Box::new(move || {
379 on_message(id, bytes, address, port, family)
380 }));
381 }
382 Err(ref e)
384 if e.kind() == std::io::ErrorKind::WouldBlock
385 || e.kind() == std::io::ErrorKind::TimedOut =>
386 {
387 continue;
388 }
389 Err(_) => break,
390 }
391 }
392}
393
394fn on_message(
397 id: u64,
398 bytes: Vec<u8>,
399 address: String,
400 port: u16,
401 family: &'static str,
402) -> Result<(), String> {
403 let socket = DGRAM.with(|s| s.borrow().sockets.get(&id).map(|r| r.emitter.clone()));
404 let Some(socket) = socket else { return Ok(()) };
405
406 let size = bytes.len();
407 let msg = super::buffer::from_bytes(&bytes);
408 let rinfo = with_host(|h| {
409 let mut m = IndexMap::new();
410 m.insert("address".into(), h.new_str(address));
411 m.insert("family".into(), h.new_str(family));
412 m.insert("port".into(), Value::Float(port as f64));
413 m.insert("size".into(), Value::Float(size as f64));
414 h.new_object(m)
415 });
416 super::events::instance_call(
417 &socket,
418 "emit",
419 vec![with_host(|h| h.new_str("message")), msg, rinfo],
420 )?;
421 Ok(())
422}
423
424fn socket_send(recv: &Value, args: &[Value]) -> Result<Value, String> {
431 let msg = args.first().cloned().unwrap_or(Value::Undef);
432 let full = value_bytes(&msg);
433
434 let mut nums: Vec<f64> = Vec::new();
437 let mut i = 1;
438 while i < args.len() && is_num(&args[i]) {
439 nums.push(with_host(|h| h.to_number(&args[i])));
440 i += 1;
441 }
442 let (offset, length, port) = if nums.len() >= 3 {
443 (
444 nums[0].max(0.0) as usize,
445 nums[1].max(0.0) as usize,
446 nums[2] as u16,
447 )
448 } else if let Some(p) = nums.first() {
449 (0usize, full.len(), *p as u16)
450 } else {
451 return Err(crate::host::type_error("Port should be > 0 and < 65536"));
452 };
453
454 let mut address = default_send_host(recv).to_string();
456 let mut cb: Option<Value> = None;
457 for a in args.iter().skip(i) {
458 if is_str(a) {
459 address = with_host(|h| h.str_of(a));
460 } else if with_host(|h| crate::host::is_callable(h, a)) {
461 cb = Some(a.clone());
462 }
463 }
464
465 let end = offset.saturating_add(length).min(full.len());
467 let start = offset.min(full.len());
468 let data = &full[start..end.max(start)];
469
470 let socket = do_bind(recv, default_bind_host(recv), 0)?;
472 socket
473 .send_to(data, (address.as_str(), port))
474 .map_err(|e| format!("Error: send {e}"))?;
475
476 if let Some(cb) = cb {
478 let _ = with_host(|h| h.io_sender()).send(Box::new(move || {
479 let nul = with_host(|h| h.null());
480 invoke(&cb, vec![nul], None)?;
481 Ok(())
482 }));
483 }
484 Ok(Value::Undef)
485}
486
487fn socket_close(recv: &Value, args: &[Value]) -> Result<Value, String> {
492 if let Some(id) = u64_prop(recv, "@@dgramid") {
493 let rec = DGRAM.with(|s| s.borrow_mut().sockets.remove(&id));
494 if let Some(rec) = rec {
495 rec.stop.store(true, Ordering::Release);
496 with_host(|h| h.decr_handle());
497 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
499 }
500 }
501 if let Some(cb) = args
503 .first()
504 .filter(|v| with_host(|h| crate::host::is_callable(h, v)))
505 {
506 invoke(cb, Vec::new(), None)?;
507 }
508 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))])?;
509 Ok(Value::Undef)
510}
511
512fn socket_address(recv: &Value) -> Result<Value, String> {
515 let socket = live_socket(recv)
516 .ok_or_else(|| "Error: getsockname EBADF: bad file descriptor".to_string())?;
517 let addr = socket
518 .local_addr()
519 .map_err(|e| format!("Error: getsockname {e}"))?;
520 Ok(with_host(|h| {
521 let mut m = IndexMap::new();
522 m.insert("address".into(), h.new_str(addr.ip().to_string()));
523 m.insert(
524 "family".into(),
525 h.new_str(if addr.is_ipv6() { "IPv6" } else { "IPv4" }),
526 );
527 m.insert("port".into(), Value::Float(addr.port() as f64));
528 h.new_object(m)
529 }))
530}