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 match method {
157 "on"
158 | "addListener"
159 | "prependListener"
160 | "once"
161 | "prependOnceListener"
162 | "emit"
163 | "removeListener"
164 | "off"
165 | "removeAllListeners"
166 | "listeners"
167 | "listenerCount"
168 | "eventNames"
169 | "setMaxListeners"
170 | "getMaxListeners" => Some(super::events::instance_call(recv, method, args.to_vec())),
171 _ => None,
172 }
173}
174
175pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
179 match method {
180 "createSocket" => Some(Ok(create_socket(args))),
181 _ => None,
182 }
183}
184
185pub fn create_socket(args: &[Value]) -> Value {
189 let first = args.first().cloned().unwrap_or(Value::Undef);
190 let sock_type = if is_str(&first) {
191 with_host(|h| h.str_of(&first))
192 } else {
193 with_host(|h| match h.get(&first) {
195 Some(JsObj::Object(p)) => p.get("type").map(|v| h.str_of(v)),
196 _ => None,
197 })
198 .unwrap_or_else(|| "udp4".to_string())
199 };
200 let sock_type = if sock_type == "udp6" { "udp6" } else { "udp4" };
201
202 let mut extra = IndexMap::new();
203 extra.insert("@@udptype".into(), with_host(|h| h.new_str(sock_type)));
204 let socket = super::net::new_emitter_object(SOCKET_TAG, extra);
205
206 if let Some(cb) = args
208 .get(1)
209 .filter(|v| with_host(|h| crate::host::is_callable(h, v)))
210 {
211 let _ = super::events::instance_call(
212 &socket,
213 "on",
214 vec![with_host(|h| h.new_str("message")), cb.clone()],
215 );
216 }
217 socket
218}
219
220pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
223 if let Some(r) = emitter_dispatch(recv, method, &args) {
224 return r;
225 }
226 match method {
227 "bind" => socket_bind(recv, &args),
228 "send" => socket_send(recv, &args),
229 "close" => socket_close(recv, &args),
230 "address" => socket_address(recv),
231 "setBroadcast" => {
234 let on = with_host(|h| h.truthy(args.first().unwrap_or(&Value::Undef)));
235 if let Some(sock) = live_socket(recv) {
236 sock.set_broadcast(on).ok();
237 }
238 Ok(recv.clone())
239 }
240 "setTTL" | "setMulticastTTL" => {
241 let ttl = with_host(|h| h.to_number(args.first().unwrap_or(&Value::Undef))) as u32;
242 if let Some(sock) = live_socket(recv) {
243 if method == "setTTL" {
244 sock.set_ttl(ttl).ok();
245 } else {
246 sock.set_multicast_ttl_v4(ttl).ok();
247 }
248 }
249 Ok(args.first().cloned().unwrap_or(Value::Undef))
250 }
251 "getRecvBufferSize" | "getSendBufferSize" => Ok(Value::Float(65536.0)),
252 "setMulticastLoopback"
256 | "setMulticastInterface"
257 | "addMembership"
258 | "dropMembership"
259 | "addSourceSpecificMembership"
260 | "dropSourceSpecificMembership"
261 | "setRecvBufferSize"
262 | "setSendBufferSize"
263 | "connect"
264 | "disconnect"
265 | "remoteAddress"
266 | "ref"
267 | "unref" => Ok(recv.clone()),
268 _ => Err(crate::host::type_error(&format!(
269 "socket.{method} is not a function"
270 ))),
271 }
272}
273
274fn live_socket(recv: &Value) -> Option<Arc<UdpSocket>> {
276 let id = u64_prop(recv, "@@dgramid")?;
277 DGRAM.with(|s| s.borrow().sockets.get(&id).map(|r| r.socket.clone()))
278}
279
280fn socket_bind(recv: &Value, args: &[Value]) -> Result<Value, String> {
287 let mut port: u16 = 0;
290 let mut host = default_bind_host(recv).to_string();
291 let mut cb: Option<Value> = None;
292
293 if let Some(first) = args.first() {
294 if is_num(first) {
295 port = with_host(|h| h.to_number(first)) as u16;
296 } else if with_host(
297 |h| matches!(h.get(first), Some(JsObj::Object(p)) if !p.contains_key("@@native")),
298 ) {
299 with_host(|h| {
301 if let Some(JsObj::Object(p)) = h.get(first) {
302 if let Some(pv) = p.get("port") {
303 port = h.to_number(pv) as u16;
304 }
305 if let Some(av) = p.get("address").map(|v| h.str_of(v)) {
306 host = av;
307 }
308 }
309 });
310 }
311 }
312 for a in args.iter().skip(1) {
313 if is_str(a) {
314 host = with_host(|h| h.str_of(a));
315 } else if with_host(|h| crate::host::is_callable(h, a)) {
316 cb = Some(a.clone());
317 }
318 }
319
320 do_bind(recv, &host, port)?;
321
322 let socket = recv.clone();
324 let _ = with_host(|h| h.io_sender()).send(Box::new(move || {
325 if let Some(cb) = cb {
326 super::events::instance_call(
327 &socket,
328 "once",
329 vec![with_host(|h| h.new_str("listening")), cb],
330 )?;
331 }
332 super::events::instance_call(&socket, "emit", vec![with_host(|h| h.new_str("listening"))])?;
333 Ok(())
334 }));
335 Ok(recv.clone())
336}
337
338fn do_bind(recv: &Value, host: &str, port: u16) -> Result<Arc<UdpSocket>, String> {
341 if let Some(sock) = live_socket(recv) {
342 return Ok(sock);
343 }
344 let socket =
345 UdpSocket::bind((host, port)).map_err(|e| format!("Error: bind EADDRINUSE: {e}"))?;
346 socket.set_read_timeout(Some(POLL)).ok();
348 let socket = Arc::new(socket);
349
350 let id = next_id();
351 set_prop(recv, "@@dgramid", Value::Float(id as f64));
352 let stop = Arc::new(AtomicBool::new(false));
353 DGRAM.with(|s| {
354 s.borrow_mut().sockets.insert(
355 id,
356 UdpRec {
357 emitter: recv.clone(),
358 socket: socket.clone(),
359 stop: stop.clone(),
360 },
361 );
362 });
363 with_host(|h| h.incr_handle());
364
365 let tx = with_host(|h| h.io_sender());
367 let recv_sock = socket.clone();
368 std::thread::spawn(move || recv_loop(recv_sock, id, stop, tx));
369
370 Ok(socket)
371}
372
373fn recv_loop(
376 socket: Arc<UdpSocket>,
377 id: u64,
378 stop: Arc<AtomicBool>,
379 tx: std::sync::mpsc::Sender<crate::host::IoTask>,
380) {
381 let mut buf = [0u8; 65536];
382 loop {
383 if stop.load(Ordering::Acquire) {
384 break;
385 }
386 match socket.recv_from(&mut buf) {
387 Ok((n, src)) => {
388 let bytes = buf[..n].to_vec();
389 let address = src.ip().to_string();
390 let port = src.port();
391 let family = if src.is_ipv6() { "IPv6" } else { "IPv4" };
392 let _ = tx.send(Box::new(move || {
393 on_message(id, bytes, address, port, family)
394 }));
395 }
396 Err(ref e)
398 if e.kind() == std::io::ErrorKind::WouldBlock
399 || e.kind() == std::io::ErrorKind::TimedOut =>
400 {
401 continue;
402 }
403 Err(_) => break,
404 }
405 }
406}
407
408fn on_message(
411 id: u64,
412 bytes: Vec<u8>,
413 address: String,
414 port: u16,
415 family: &'static str,
416) -> Result<(), String> {
417 let socket = DGRAM.with(|s| s.borrow().sockets.get(&id).map(|r| r.emitter.clone()));
418 let Some(socket) = socket else { return Ok(()) };
419
420 let size = bytes.len();
421 let msg = super::buffer::from_bytes(&bytes);
422 let rinfo = with_host(|h| {
423 let mut m = IndexMap::new();
424 m.insert("address".into(), h.new_str(address));
425 m.insert("family".into(), h.new_str(family));
426 m.insert("port".into(), Value::Float(port as f64));
427 m.insert("size".into(), Value::Float(size as f64));
428 h.new_object(m)
429 });
430 super::events::instance_call(
431 &socket,
432 "emit",
433 vec![with_host(|h| h.new_str("message")), msg, rinfo],
434 )?;
435 Ok(())
436}
437
438fn socket_send(recv: &Value, args: &[Value]) -> Result<Value, String> {
445 let msg = args.first().cloned().unwrap_or(Value::Undef);
446 let full = value_bytes(&msg);
447
448 let mut nums: Vec<f64> = Vec::new();
451 let mut i = 1;
452 while i < args.len() && is_num(&args[i]) {
453 nums.push(with_host(|h| h.to_number(&args[i])));
454 i += 1;
455 }
456 let (offset, length, port) = if nums.len() >= 3 {
457 (
458 nums[0].max(0.0) as usize,
459 nums[1].max(0.0) as usize,
460 nums[2] as u16,
461 )
462 } else if let Some(p) = nums.first() {
463 (0usize, full.len(), *p as u16)
464 } else {
465 return Err(crate::host::type_error("Port should be > 0 and < 65536"));
466 };
467
468 let mut address = default_send_host(recv).to_string();
470 let mut cb: Option<Value> = None;
471 for a in args.iter().skip(i) {
472 if is_str(a) {
473 address = with_host(|h| h.str_of(a));
474 } else if with_host(|h| crate::host::is_callable(h, a)) {
475 cb = Some(a.clone());
476 }
477 }
478
479 let end = offset.saturating_add(length).min(full.len());
481 let start = offset.min(full.len());
482 let data = &full[start..end.max(start)];
483
484 let socket = do_bind(recv, default_bind_host(recv), 0)?;
486 socket
487 .send_to(data, (address.as_str(), port))
488 .map_err(|e| format!("Error: send {e}"))?;
489
490 if let Some(cb) = cb {
492 let _ = with_host(|h| h.io_sender()).send(Box::new(move || {
493 let nul = with_host(|h| h.null());
494 invoke(&cb, vec![nul], None)?;
495 Ok(())
496 }));
497 }
498 Ok(Value::Undef)
499}
500
501fn socket_close(recv: &Value, args: &[Value]) -> Result<Value, String> {
506 if let Some(id) = u64_prop(recv, "@@dgramid") {
507 let rec = DGRAM.with(|s| s.borrow_mut().sockets.remove(&id));
508 if let Some(rec) = rec {
509 rec.stop.store(true, Ordering::Release);
510 with_host(|h| h.decr_handle());
511 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
513 }
514 }
515 if let Some(cb) = args
517 .first()
518 .filter(|v| with_host(|h| crate::host::is_callable(h, v)))
519 {
520 invoke(cb, Vec::new(), None)?;
521 }
522 super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))])?;
523 Ok(Value::Undef)
524}
525
526fn socket_address(recv: &Value) -> Result<Value, String> {
529 let socket = live_socket(recv)
530 .ok_or_else(|| "Error: getsockname EBADF: bad file descriptor".to_string())?;
531 let addr = socket
532 .local_addr()
533 .map_err(|e| format!("Error: getsockname {e}"))?;
534 Ok(with_host(|h| {
535 let mut m = IndexMap::new();
536 m.insert("address".into(), h.new_str(addr.ip().to_string()));
537 m.insert(
538 "family".into(),
539 h.new_str(if addr.is_ipv6() { "IPv6" } else { "IPv4" }),
540 );
541 m.insert("port".into(), Value::Float(addr.port() as f64));
542 h.new_object(m)
543 }))
544}