1#![allow(clippy::result_large_err)]
21
22use crate::policy::Policy;
23use indexmap::IndexMap;
24use lex_bytecode::vm::Vm;
25use lex_bytecode::{Program, Value};
26use std::net::TcpListener;
27use std::sync::atomic::{AtomicU64, Ordering};
28use std::sync::mpsc;
29use std::sync::{Arc, Mutex};
30use std::thread;
31use std::time::Duration;
32
33struct Conn {
35 room: String,
36 outbound: mpsc::Sender<String>,
40}
41
42#[derive(Default)]
44pub struct ChatRegistry {
45 conns: Mutex<IndexMap<u64, Conn>>,
46}
47
48impl ChatRegistry {
49 fn register(&self, room: String, outbound: mpsc::Sender<String>) -> u64 {
50 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
51 let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
52 self.conns.lock().unwrap().insert(id, Conn { room, outbound });
53 id
54 }
55 fn unregister(&self, id: u64) {
56 self.conns.lock().unwrap().shift_remove(&id);
57 }
58 fn broadcast(&self, room: &str, body: &str) {
59 let conns = self.conns.lock().unwrap();
60 for c in conns.values() {
61 if c.room == room {
62 let _ = c.outbound.send(body.to_string());
63 }
64 }
65 }
66 fn send_to(&self, id: u64, body: &str) -> bool {
67 if let Some(c) = self.conns.lock().unwrap().get(&id) {
68 let _ = c.outbound.send(body.to_string());
69 true
70 } else {
71 false
72 }
73 }
74}
75
76pub fn chat_broadcast(reg: &Arc<ChatRegistry>, room: &str, body: &str) {
79 reg.broadcast(room, body);
80}
81
82pub fn chat_send(reg: &Arc<ChatRegistry>, conn_id: u64, body: &str) -> bool {
83 reg.send_to(conn_id, body)
84}
85
86pub fn serve_ws(
89 port: u16,
90 handler_name: String,
91 program: Arc<Program>,
92 policy: Policy,
93 registry: Arc<ChatRegistry>,
94) -> Result<Value, String> {
95 let listener = TcpListener::bind(("127.0.0.1", port))
96 .map_err(|e| format!("net.serve_ws bind {port}: {e}"))?;
97 eprintln!("net.serve_ws: listening on ws://127.0.0.1:{port}");
98 for stream in listener.incoming() {
99 let stream = match stream {
100 Ok(s) => s,
101 Err(e) => { eprintln!("net.serve_ws accept: {e}"); continue; }
102 };
103 let program = Arc::clone(&program);
104 let policy = policy.clone();
105 let handler_name = handler_name.clone();
106 let registry = Arc::clone(®istry);
107 thread::spawn(move || {
108 if let Err(e) = handle_connection(stream, program, policy, handler_name, registry) {
109 eprintln!("net.serve_ws connection error: {e}");
110 }
111 });
112 }
113 Ok(Value::Unit)
114}
115
116fn accept_or_drop_probe<Cb>(
129 result: Result<
130 tungstenite::WebSocket<std::net::TcpStream>,
131 tungstenite::HandshakeError<
132 tungstenite::handshake::server::ServerHandshake<std::net::TcpStream, Cb>,
133 >,
134 >,
135) -> Result<Option<tungstenite::WebSocket<std::net::TcpStream>>, String>
136where
137 Cb: tungstenite::handshake::server::Callback,
138{
139 match result {
140 Ok(ws) => Ok(Some(ws)),
141 Err(tungstenite::HandshakeError::Failure(tungstenite::Error::Protocol(
142 tungstenite::error::ProtocolError::HandshakeIncomplete,
143 ))) => Ok(None),
144 Err(e) => Err(format!("ws handshake: {e}")),
145 }
146}
147
148fn handle_connection(
149 stream: std::net::TcpStream,
150 program: Arc<Program>,
151 policy: Policy,
152 handler_name: String,
153 registry: Arc<ChatRegistry>,
154) -> Result<(), String> {
155 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
156
157 let mut path = String::new();
159 let path_ref = &mut path;
160 let accepted = accept_hdr(stream, |req: &Request, resp: Response| {
161 *path_ref = req.uri().path().to_string();
162 Ok(resp)
163 });
164 let mut ws = match accept_or_drop_probe(accepted)? {
165 Some(ws) => ws,
166 None => return Ok(()), };
168
169 let room = path.trim_start_matches('/').to_string();
170
171 let (tx, rx) = mpsc::channel::<String>();
174 let conn_id = registry.register(room.clone(), tx);
175
176 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
180
181 let result = run_loop(&mut ws, &rx, conn_id, &room, &program, &policy, &handler_name, ®istry);
182 registry.unregister(conn_id);
183 let _ = ws.close(None);
184 result
185}
186
187#[allow(clippy::too_many_arguments)]
188fn run_loop(
189 ws: &mut tungstenite::WebSocket<std::net::TcpStream>,
190 rx: &mpsc::Receiver<String>,
191 conn_id: u64,
192 room: &str,
193 program: &Arc<Program>,
194 policy: &Policy,
195 handler_name: &str,
196 registry: &Arc<ChatRegistry>,
197) -> Result<(), String> {
198 use tungstenite::Message;
199 use std::io::ErrorKind;
200 loop {
201 match ws.read() {
203 Ok(Message::Text(body)) => {
204 let ev = build_ws_event(conn_id, room, &body);
205 let handler = crate::handler::DefaultHandler::new(policy.clone())
206 .with_program(Arc::clone(program))
207 .with_chat_registry(Arc::clone(registry));
208 let mut vm = Vm::with_handler(program, Box::new(handler));
209 if let Err(e) = vm.call(handler_name, vec![ev]) {
210 eprintln!("on_message {conn_id}: {e}");
211 }
212 }
213 Ok(Message::Binary(_)) => { }
214 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => break,
215 Ok(_) => {} Err(tungstenite::Error::Io(ref e)) if e.kind() == ErrorKind::WouldBlock
217 || e.kind() == ErrorKind::TimedOut => {}
218 Err(e) => return Err(format!("ws read: {e}")),
219 }
220 loop {
222 match rx.try_recv() {
223 Ok(msg) => {
224 if let Err(e) = ws.send(Message::Text(msg.into())) {
225 return Err(format!("ws send: {e}"));
226 }
227 }
228 Err(mpsc::TryRecvError::Empty) => break,
229 Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
230 }
231 }
232 }
233 Ok(())
234}
235
236fn build_ws_event(conn_id: u64, room: &str, body: &str) -> Value {
237 let mut rec = IndexMap::new();
238 rec.insert("body".into(), Value::Str(body.into()));
239 rec.insert("conn_id".into(), Value::Int(conn_id as i64));
240 rec.insert("room".into(), Value::Str(room.into()));
241 Value::record_dynamic(rec)
242}
243
244fn build_ws_conn(conn_id: u64, path: &str, subprotocol: &str) -> Value {
248 let mut rec = IndexMap::new();
249 rec.insert("id".into(), Value::Str(conn_id.to_string().into()));
250 rec.insert("path".into(), Value::Str(path.into()));
251 rec.insert("subprotocol".into(), Value::Str(subprotocol.into()));
252 Value::record_dynamic(rec)
253}
254
255fn build_ws_message_text(body: &str) -> Value {
257 Value::Variant { name: "WsText".into(), args: vec![Value::Str(body.into())] }
258}
259
260fn build_ws_message_close() -> Value {
261 Value::Variant { name: "WsClose".into(), args: vec![] }
262}
263
264fn build_ws_message_ping() -> Value {
265 Value::Variant { name: "WsPing".into(), args: vec![] }
266}
267
268fn build_ws_message_binary(payload: &[u8]) -> Value {
269 let bytes = payload.iter().map(|b| Value::Int(*b as i64)).collect();
270 Value::Variant { name: "WsBinary".into(), args: vec![Value::List(bytes)] }
271}
272
273fn apply_ws_action<S: std::io::Read + std::io::Write>(
278 action: &Value,
279 ws: &mut tungstenite::WebSocket<S>,
280) -> Result<(), String> {
281 use tungstenite::Message;
282 match action {
283 Value::Variant { name, args } if name == "WsSend" => {
284 let text = match args.first() {
285 Some(Value::Str(s)) => s.clone(),
286 _ => return Err("WsSend payload must be Str".into()),
287 };
288 ws.send(Message::Text(text.to_string().into()))
289 .map_err(|e| format!("ws send: {e}"))
290 }
291 Value::Variant { name, args } if name == "WsSendBinary" => {
292 let bytes: Vec<u8> = match args.first() {
293 Some(Value::List(elems)) => elems
294 .iter()
295 .map(|v| match v {
296 Value::Int(n) => Ok(*n as u8),
297 _ => Err("WsSendBinary payload must be List[Int]".into()),
298 })
299 .collect::<Result<Vec<_>, String>>()?,
300 _ => return Err("WsSendBinary payload must be List[Int]".into()),
301 };
302 ws.send(Message::Binary(bytes.into()))
303 .map_err(|e| format!("ws send binary: {e}"))
304 }
305 Value::Variant { name, .. } if name == "WsNoOp" => Ok(()),
306 other => Err(format!("unexpected WsAction: {other:?}")),
307 }
308}
309
310pub fn serve_ws_fn(
312 port: u16,
313 subprotocol: String,
314 closure: Value,
315 program: Arc<Program>,
316 policy: Policy,
317 registry: Arc<ChatRegistry>,
318) -> Result<Value, String> {
319 if !subprotocol.is_empty() {
324 if let Err(e) =
325 tungstenite::http::HeaderValue::from_str(&subprotocol)
326 {
327 return Err(format!(
328 "net.serve_ws_fn: subprotocol {subprotocol:?} is not a valid \
329 HTTP header value: {e}"
330 ));
331 }
332 }
333 let listener = TcpListener::bind(("127.0.0.1", port))
334 .map_err(|e| format!("net.serve_ws_fn bind {port}: {e}"))?;
335 eprintln!("net.serve_ws_fn: listening on ws://127.0.0.1:{port}");
336 for stream in listener.incoming() {
337 let stream = match stream {
338 Ok(s) => s,
339 Err(e) => { eprintln!("net.serve_ws_fn accept: {e}"); continue; }
340 };
341 let program = Arc::clone(&program);
342 let policy = policy.clone();
343 let closure = closure.clone();
344 let subprotocol = subprotocol.clone();
345 let registry = Arc::clone(®istry);
346 thread::spawn(move || {
347 if let Err(e) = handle_connection_fn(
348 stream, program, policy, closure, subprotocol, registry,
349 ) {
350 eprintln!("net.serve_ws_fn connection error: {e}");
351 }
352 });
353 }
354 Ok(Value::Unit)
355}
356
357fn handle_connection_fn(
358 stream: std::net::TcpStream,
359 program: Arc<Program>,
360 policy: Policy,
361 closure: Value,
362 subprotocol: String,
363 registry: Arc<ChatRegistry>,
364) -> Result<(), String> {
365 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
366
367 let mut path = String::new();
368 let path_ref = &mut path;
369 let subproto_for_handshake = subprotocol.clone();
370 let accepted = accept_hdr(stream, |req: &Request, mut resp: Response| {
371 *path_ref = req.uri().path().to_string();
372 maybe_echo_subprotocol(req, &mut resp, &subproto_for_handshake);
373 Ok(resp)
374 });
375 let mut ws = match accept_or_drop_probe(accepted)? {
376 Some(ws) => ws,
377 None => return Ok(()), };
379
380 let (tx, rx) = mpsc::channel::<String>();
381 let conn_id = registry.register(path.trim_start_matches('/').to_string(), tx);
382 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
383
384 let result = run_loop_fn(
385 &mut ws, &rx, conn_id, &path, &subprotocol,
386 &program, &policy, &closure, ®istry,
387 );
388 registry.unregister(conn_id);
389 let _ = ws.close(None);
390 result
391}
392
393fn maybe_echo_subprotocol(
403 req: &tungstenite::handshake::server::Request,
404 resp: &mut tungstenite::handshake::server::Response,
405 subprotocol: &str,
406) {
407 use tungstenite::http::HeaderValue;
408 if subprotocol.is_empty() {
409 return;
410 }
411 let offered = match req.headers().get("Sec-WebSocket-Protocol") {
412 Some(v) => v,
413 None => return,
414 };
415 let offered_str = match offered.to_str() {
416 Ok(s) => s,
417 Err(_) => return,
418 };
419 let matches = offered_str
420 .split(',')
421 .map(|p| p.trim())
422 .any(|p| p == subprotocol);
423 if !matches {
424 return;
425 }
426 if let Ok(h) = HeaderValue::from_str(subprotocol) {
430 resp.headers_mut().insert("Sec-WebSocket-Protocol", h);
431 }
432}
433
434pub fn serve_ws_fn_auth(
465 port: u16,
466 subprotocol: String,
467 auth_closure: Value,
468 handler_closure: Value,
469 program: Arc<Program>,
470 policy: Policy,
471 registry: Arc<ChatRegistry>,
472) -> Result<Value, String> {
473 if !subprotocol.is_empty() {
474 if let Err(e) =
475 tungstenite::http::HeaderValue::from_str(&subprotocol)
476 {
477 return Err(format!(
478 "net.serve_ws_fn_auth: subprotocol {subprotocol:?} is not a \
479 valid HTTP header value: {e}"
480 ));
481 }
482 }
483 let listener = TcpListener::bind(("127.0.0.1", port))
484 .map_err(|e| format!("net.serve_ws_fn_auth bind {port}: {e}"))?;
485 eprintln!("net.serve_ws_fn_auth: listening on ws://127.0.0.1:{port}");
486 for stream in listener.incoming() {
487 let stream = match stream {
488 Ok(s) => s,
489 Err(e) => {
490 eprintln!("net.serve_ws_fn_auth accept: {e}");
491 continue;
492 }
493 };
494 let program = Arc::clone(&program);
495 let policy = policy.clone();
496 let auth_closure = auth_closure.clone();
497 let handler_closure = handler_closure.clone();
498 let subprotocol = subprotocol.clone();
499 let registry = Arc::clone(®istry);
500 thread::spawn(move || {
501 if let Err(e) = handle_connection_fn_auth(
502 stream, program, policy, auth_closure, handler_closure,
503 subprotocol, registry,
504 ) {
505 eprintln!("net.serve_ws_fn_auth connection error: {e}");
506 }
507 });
508 }
509 Ok(Value::Unit)
510}
511
512#[allow(clippy::too_many_arguments)]
513fn handle_connection_fn_auth(
514 stream: std::net::TcpStream,
515 program: Arc<Program>,
516 policy: Policy,
517 auth_closure: Value,
518 handler_closure: Value,
519 subprotocol: String,
520 registry: Arc<ChatRegistry>,
521) -> Result<(), String> {
522 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
523 use tungstenite::http::StatusCode;
524
525 let mut path = String::new();
526 let path_ref = &mut path;
527 let subproto_for_handshake = subprotocol.clone();
528 let auth_program = Arc::clone(&program);
532 let auth_policy = policy.clone();
533 let auth_registry = Arc::clone(®istry);
534 let auth_closure_for_cb = auth_closure;
535
536 let accepted = accept_hdr(stream, move |req: &Request, mut resp: Response| {
537 *path_ref = req.uri().path().to_string();
538
539 let headers_value = build_headers_value(req);
540 let path_arg = Value::Str(path_ref.clone().into());
541
542 let dh = crate::handler::DefaultHandler::new(auth_policy.clone())
543 .with_program(Arc::clone(&auth_program))
544 .with_chat_registry(Arc::clone(&auth_registry));
545 let mut vm = Vm::with_handler(&auth_program, Box::new(dh));
546 let auth_result = vm.invoke_closure_value(
547 auth_closure_for_cb,
548 vec![path_arg, headers_value],
549 );
550
551 match auth_result {
552 Ok(Value::Variant { name, .. }) if name == "Ok" => {
553 maybe_echo_subprotocol(req, &mut resp, &subproto_for_handshake);
554 Ok(resp)
555 }
556 Ok(Value::Variant { name, args }) if name == "Err" => {
557 let msg = match args.first() {
558 Some(Value::Str(s)) => s.to_string(),
559 _ => "unauthorized".to_string(),
560 };
561 let err = build_unauthorized_response(StatusCode::UNAUTHORIZED, msg);
562 Err(err)
563 }
564 Ok(other) => {
565 let err = build_unauthorized_response(
566 StatusCode::INTERNAL_SERVER_ERROR,
567 format!(
568 "net.serve_ws_fn_auth: auth callback returned \
569 non-Result value: {other:?}"
570 ),
571 );
572 Err(err)
573 }
574 Err(e) => {
575 let err = build_unauthorized_response(
576 StatusCode::INTERNAL_SERVER_ERROR,
577 format!("net.serve_ws_fn_auth: auth callback error: {e:?}"),
578 );
579 Err(err)
580 }
581 }
582 });
583 let mut ws = match accept_or_drop_probe(accepted)? {
584 Some(ws) => ws,
585 None => return Ok(()), };
587
588 let (tx, rx) = mpsc::channel::<String>();
589 let conn_id = registry.register(path.trim_start_matches('/').to_string(), tx);
590 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
591
592 let result = run_loop_fn(
593 &mut ws, &rx, conn_id, &path, &subprotocol,
594 &program, &policy, &handler_closure, ®istry,
595 );
596 registry.unregister(conn_id);
597 let _ = ws.close(None);
598 result
599}
600
601fn build_headers_value(req: &tungstenite::handshake::server::Request) -> Value {
607 let mut items: std::collections::VecDeque<Value> = std::collections::VecDeque::new();
608 for (name, val) in req.headers().iter() {
609 let v = match val.to_str() {
610 Ok(s) => s.to_string(),
611 Err(_) => continue,
612 };
613 let mut rec = IndexMap::new();
614 rec.insert("name".into(), Value::Str(name.as_str().into()));
615 rec.insert("value".into(), Value::Str(v.into()));
616 items.push_back(Value::record_dynamic(rec));
617 }
618 Value::List(items)
619}
620
621fn build_unauthorized_response(
622 status: tungstenite::http::StatusCode,
623 msg: String,
624) -> tungstenite::handshake::server::ErrorResponse {
625 tungstenite::http::Response::builder()
626 .status(status)
627 .header("Content-Type", "text/plain; charset=utf-8")
628 .body(Some(msg))
629 .expect("ErrorResponse builder")
630}
631
632#[allow(clippy::too_many_arguments)]
633fn run_loop_fn(
634 ws: &mut tungstenite::WebSocket<std::net::TcpStream>,
635 rx: &mpsc::Receiver<String>,
636 conn_id: u64,
637 path: &str,
638 subprotocol: &str,
639 program: &Arc<Program>,
640 policy: &Policy,
641 closure: &Value,
642 registry: &Arc<ChatRegistry>,
643) -> Result<(), String> {
644 use tungstenite::Message;
645 use std::io::ErrorKind;
646
647 let ws_conn = build_ws_conn(conn_id, path, subprotocol);
648
649 loop {
650 let ws_msg = match ws.read() {
651 Ok(Message::Text(body)) => Some(build_ws_message_text(&body)),
652 Ok(Message::Binary(_)) => None,
653 Ok(Message::Ping(_)) => Some(build_ws_message_ping()),
654 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => {
655 let handler = crate::handler::DefaultHandler::new(policy.clone())
657 .with_program(Arc::clone(program))
658 .with_chat_registry(Arc::clone(registry));
659 let mut vm = Vm::with_handler(program, Box::new(handler));
660 let _ = vm.invoke_closure_value(
661 closure.clone(),
662 vec![ws_conn.clone(), build_ws_message_close()],
663 );
664 break;
665 }
666 Ok(_) => None, Err(tungstenite::Error::Io(ref e))
668 if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut => None,
669 Err(e) => return Err(format!("ws read: {e}")),
670 };
671
672 if let Some(msg) = ws_msg {
673 let handler = crate::handler::DefaultHandler::new(policy.clone())
674 .with_program(Arc::clone(program))
675 .with_chat_registry(Arc::clone(registry));
676 let mut vm = Vm::with_handler(program, Box::new(handler));
677 match vm.invoke_closure_value(closure.clone(), vec![ws_conn.clone(), msg]) {
678 Ok(action) => {
679 if let Err(e) = apply_ws_action(&action, ws) {
680 eprintln!("ws action {conn_id}: {e}");
681 }
682 }
683 Err(e) => eprintln!("ws handler {conn_id}: {e}"),
684 }
685 }
686
687 loop {
689 match rx.try_recv() {
690 Ok(msg) => {
691 if let Err(e) = ws.send(Message::Text(msg.into())) {
692 return Err(format!("ws send: {e}"));
693 }
694 }
695 Err(mpsc::TryRecvError::Empty) => break,
696 Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
697 }
698 }
699 }
700 Ok(())
701}
702
703pub fn serve_ws_fn_actor(
741 port: u16,
742 subprotocol: String,
743 name_of_closure: Value,
744 on_message_closure: Value,
745 program: Arc<Program>,
746 policy: Policy,
747 registry: Arc<ChatRegistry>,
748) -> Result<Value, String> {
749 if !subprotocol.is_empty() {
750 if let Err(e) =
751 tungstenite::http::HeaderValue::from_str(&subprotocol)
752 {
753 return Err(format!(
754 "net.serve_ws_fn_actor: subprotocol {subprotocol:?} is not a valid \
755 HTTP header value: {e}"
756 ));
757 }
758 }
759 let listener = TcpListener::bind(("127.0.0.1", port))
760 .map_err(|e| format!("net.serve_ws_fn_actor bind {port}: {e}"))?;
761 eprintln!("net.serve_ws_fn_actor: listening on ws://127.0.0.1:{port}");
762 for stream in listener.incoming() {
763 let stream = match stream {
764 Ok(s) => s,
765 Err(e) => { eprintln!("net.serve_ws_fn_actor accept: {e}"); continue; }
766 };
767 let program = Arc::clone(&program);
768 let policy = policy.clone();
769 let name_of_closure = name_of_closure.clone();
770 let on_message_closure = on_message_closure.clone();
771 let subprotocol = subprotocol.clone();
772 let registry = Arc::clone(®istry);
773 thread::spawn(move || {
774 if let Err(e) = handle_connection_fn_actor(
775 stream, program, policy, name_of_closure, on_message_closure,
776 subprotocol, registry,
777 ) {
778 eprintln!("net.serve_ws_fn_actor connection error: {e}");
779 }
780 });
781 }
782 Ok(Value::Unit)
783}
784
785#[allow(clippy::too_many_arguments)]
786fn handle_connection_fn_actor(
787 stream: std::net::TcpStream,
788 program: Arc<Program>,
789 policy: Policy,
790 name_of_closure: Value,
791 on_message_closure: Value,
792 subprotocol: String,
793 registry: Arc<ChatRegistry>,
794) -> Result<(), String> {
795 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
796
797 let mut path = String::new();
798 let path_ref = &mut path;
799 let subproto_for_handshake = subprotocol.clone();
800 let accepted = accept_hdr(stream, |req: &Request, mut resp: Response| {
801 *path_ref = req.uri().path().to_string();
802 maybe_echo_subprotocol(req, &mut resp, &subproto_for_handshake);
803 Ok(resp)
804 });
805 let mut ws = match accept_or_drop_probe(accepted)? {
806 Some(ws) => ws,
807 None => return Ok(()), };
809
810 let (tx, rx) = mpsc::channel::<String>();
811 let conn_id = registry.register(path.trim_start_matches('/').to_string(), tx.clone());
812 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
813
814 let ws_conn = build_ws_conn(conn_id, &path, &subprotocol);
823 let registered_name: Option<String> = {
824 let handler = crate::handler::DefaultHandler::new(policy.clone())
825 .with_program(Arc::clone(&program))
826 .with_chat_registry(Arc::clone(®istry));
827 let mut vm = Vm::with_handler(&program, Box::new(handler));
828 match vm.invoke_closure_value(name_of_closure.clone(), vec![ws_conn.clone()]) {
829 Ok(Value::Str(s)) if !s.is_empty() => Some(s.to_string()),
830 Ok(Value::Str(_)) => None,
831 Ok(other) => {
832 registry.unregister(conn_id);
833 let _ = ws.close(None);
834 return Err(format!(
835 "net.serve_ws_fn_actor: name_of must return Str, got {other:?}"
836 ));
837 }
838 Err(e) => {
839 registry.unregister(conn_id);
840 let _ = ws.close(None);
841 return Err(format!(
842 "net.serve_ws_fn_actor: name_of error: {e:?}"
843 ));
844 }
845 }
846 };
847
848 if let Some(ref name) = registered_name {
852 let tx_for_bridge = tx.clone();
853 let bridge = lex_bytecode::value::NativeActorHandler {
854 send: Box::new(move |msg: Value| -> Result<Value, String> {
855 match msg {
856 Value::Str(s) => {
857 tx_for_bridge.send(s.to_string()).map_err(|e| {
858 format!("net.serve_ws_fn_actor: outbound channel closed: {e}")
859 })?;
860 Ok(Value::Unit)
861 }
862 other => Err(format!(
863 "net.serve_ws_fn_actor: native bridge accepts Str messages only, got {other:?}"
864 )),
865 }
866 }),
867 };
868 let cell = Value::Actor(Arc::new(Mutex::new(lex_bytecode::value::ActorCell {
869 state: Value::Unit,
870 handler: lex_bytecode::value::ActorHandler::Native(Arc::new(bridge)),
871 })));
872 if let Err(e) = lex_bytecode::conc_registry::register(name, cell) {
873 registry.unregister(conn_id);
877 let _ = ws.close(None);
878 return Err(format!(
879 "net.serve_ws_fn_actor: conc.register({name:?}) failed: {e:?}"
880 ));
881 }
882 }
883
884 let result = run_loop_fn(
885 &mut ws, &rx, conn_id, &path, &subprotocol,
886 &program, &policy, &on_message_closure, ®istry,
887 );
888
889 if let Some(ref name) = registered_name {
892 let _ = lex_bytecode::conc_registry::unregister(name);
893 }
894 registry.unregister(conn_id);
895 let _ = ws.close(None);
896 result
897}
898
899fn build_dial_result(ok: Result<(), String>) -> Value {
924 match ok {
925 Ok(()) => Value::Variant {
926 name: "Ok".into(),
927 args: vec![Value::Unit],
928 },
929 Err(msg) => Value::Variant {
930 name: "Err".into(),
931 args: vec![Value::Str(msg.into())],
932 },
933 }
934}
935
936pub fn dial_ws(
941 url: String,
942 subprotocol: String,
943 on_open: Value,
944 on_message: Value,
945 program: Arc<Program>,
946 policy: Policy,
947) -> Result<Value, String> {
948 use tungstenite::client::IntoClientRequest;
949 use tungstenite::http::HeaderValue;
950
951 let mut req = match url.as_str().into_client_request() {
961 Ok(r) => r,
962 Err(e) => {
963 return Ok(build_dial_result(Err(format!(
964 "net.dial_ws: bad URL `{url}`: {e}"
965 ))));
966 }
967 };
968 if !subprotocol.is_empty() {
969 let header = match HeaderValue::from_str(&subprotocol) {
970 Ok(h) => h,
971 Err(e) => {
972 return Ok(build_dial_result(Err(format!(
973 "net.dial_ws: invalid subprotocol `{subprotocol}`: {e}"
974 ))));
975 }
976 };
977 req.headers_mut().insert("Sec-WebSocket-Protocol", header);
978 }
979
980 let (mut ws, _resp) = match tungstenite::connect(req) {
981 Ok(pair) => pair,
982 Err(e) => {
983 return Ok(build_dial_result(Err(format!(
984 "net.dial_ws: connect to `{url}`: {e}"
985 ))));
986 }
987 };
988
989 if let Some(stream) = stream_for(&mut ws) {
992 let _ = stream.set_read_timeout(Some(Duration::from_millis(50)));
993 }
994
995 {
997 let handler = crate::handler::DefaultHandler::new(policy.clone())
998 .with_program(Arc::clone(&program));
999 let mut vm = Vm::with_handler(&program, Box::new(handler));
1000 match vm.invoke_closure_value(on_open.clone(), vec![]) {
1001 Ok(action) => {
1002 if let Err(e) = apply_ws_action(&action, &mut ws) {
1003 return Ok(build_dial_result(Err(format!(
1004 "net.dial_ws: on_open action: {e}"
1005 ))));
1006 }
1007 }
1008 Err(e) => {
1009 return Ok(build_dial_result(Err(format!(
1010 "net.dial_ws: on_open: {e}"
1011 ))));
1012 }
1013 }
1014 }
1015
1016 let loop_result = dial_run_loop(&mut ws, &on_message, &program, &policy);
1018 let _ = ws.close(None);
1019 Ok(build_dial_result(loop_result))
1020}
1021
1022fn stream_for(
1028 ws: &mut tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>,
1029) -> Option<&mut std::net::TcpStream> {
1030 use tungstenite::stream::MaybeTlsStream;
1031 match ws.get_mut() {
1032 MaybeTlsStream::Plain(s) => Some(s),
1033 MaybeTlsStream::Rustls(s) => Some(s.get_mut()),
1034 _ => None,
1035 }
1036}
1037
1038pub fn dial_ws_actor(
1053 url: String,
1054 subprotocol: String,
1055 name: String,
1056 on_open: Value,
1057 on_message: Value,
1058 program: Arc<Program>,
1059 policy: Policy,
1060) -> Result<Value, String> {
1061 use tungstenite::client::IntoClientRequest;
1062 use tungstenite::http::HeaderValue;
1063
1064 let mut req = match url.as_str().into_client_request() {
1065 Ok(r) => r,
1066 Err(e) => {
1067 return Ok(build_dial_result(Err(format!(
1068 "net.dial_ws_actor: bad URL `{url}`: {e}"
1069 ))));
1070 }
1071 };
1072 if !subprotocol.is_empty() {
1073 let header = match HeaderValue::from_str(&subprotocol) {
1074 Ok(h) => h,
1075 Err(e) => {
1076 return Ok(build_dial_result(Err(format!(
1077 "net.dial_ws_actor: invalid subprotocol `{subprotocol}`: {e}"
1078 ))));
1079 }
1080 };
1081 req.headers_mut().insert("Sec-WebSocket-Protocol", header);
1082 }
1083
1084 let (mut ws, _resp) = match tungstenite::connect(req) {
1085 Ok(pair) => pair,
1086 Err(e) => {
1087 return Ok(build_dial_result(Err(format!(
1088 "net.dial_ws_actor: connect to `{url}`: {e}"
1089 ))));
1090 }
1091 };
1092 if let Some(stream) = stream_for(&mut ws) {
1093 let _ = stream.set_read_timeout(Some(Duration::from_millis(50)));
1094 }
1095
1096 let (tx, rx) = mpsc::channel::<String>();
1099 if !name.is_empty() {
1100 let tx_for_bridge = tx.clone();
1101 let bridge = lex_bytecode::value::NativeActorHandler {
1102 send: Box::new(move |msg: Value| -> Result<Value, String> {
1103 match msg {
1104 Value::Str(s) => tx_for_bridge
1105 .send(s.to_string())
1106 .map(|_| Value::Unit)
1107 .map_err(|e| format!("net.dial_ws_actor: outbound channel closed: {e}")),
1108 other => Err(format!(
1109 "net.dial_ws_actor: native bridge accepts Str only, got {other:?}"
1110 )),
1111 }
1112 }),
1113 };
1114 let cell = Value::Actor(Arc::new(Mutex::new(lex_bytecode::value::ActorCell {
1115 state: Value::Unit,
1116 handler: lex_bytecode::value::ActorHandler::Native(Arc::new(bridge)),
1117 })));
1118 if let Err(e) = lex_bytecode::conc_registry::register(&name, cell) {
1119 return Ok(build_dial_result(Err(format!(
1120 "net.dial_ws_actor: conc.register({name:?}) failed: {e:?}"
1121 ))));
1122 }
1123 }
1124
1125 {
1127 let handler = crate::handler::DefaultHandler::new(policy.clone())
1128 .with_program(Arc::clone(&program));
1129 let mut vm = Vm::with_handler(&program, Box::new(handler));
1130 match vm.invoke_closure_value(on_open.clone(), vec![]) {
1131 Ok(action) => {
1132 if let Err(e) = apply_ws_action(&action, &mut ws) {
1133 if !name.is_empty() {
1134 let _ = lex_bytecode::conc_registry::unregister(&name);
1135 }
1136 return Ok(build_dial_result(Err(format!(
1137 "net.dial_ws_actor: on_open action: {e}"
1138 ))));
1139 }
1140 }
1141 Err(e) => {
1142 if !name.is_empty() {
1143 let _ = lex_bytecode::conc_registry::unregister(&name);
1144 }
1145 return Ok(build_dial_result(Err(format!(
1146 "net.dial_ws_actor: on_open: {e}"
1147 ))));
1148 }
1149 }
1150 }
1151
1152 let loop_result = dial_actor_run_loop(&mut ws, &rx, &on_message, &program, &policy);
1153 if !name.is_empty() {
1154 let _ = lex_bytecode::conc_registry::unregister(&name);
1155 }
1156 let _ = ws.close(None);
1157 Ok(build_dial_result(loop_result))
1158}
1159
1160fn dial_actor_run_loop(
1161 ws: &mut tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>,
1162 rx: &mpsc::Receiver<String>,
1163 on_message: &Value,
1164 program: &Arc<Program>,
1165 policy: &Policy,
1166) -> Result<(), String> {
1167 use std::io::ErrorKind;
1168 use tungstenite::Message;
1169
1170 loop {
1171 let ws_msg = match ws.read() {
1172 Ok(Message::Text(body)) => Some(build_ws_message_text(&body)),
1173 Ok(Message::Binary(payload)) => Some(build_ws_message_binary(&payload)),
1174 Ok(Message::Ping(_)) => Some(build_ws_message_ping()),
1175 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => {
1176 let handler = crate::handler::DefaultHandler::new(policy.clone())
1177 .with_program(Arc::clone(program));
1178 let mut vm = Vm::with_handler(program, Box::new(handler));
1179 let _ = vm.invoke_closure_value(
1180 on_message.clone(),
1181 vec![build_ws_message_close()],
1182 );
1183 return Ok(());
1184 }
1185 Ok(_) => None,
1186 Err(tungstenite::Error::Io(ref e))
1187 if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut =>
1188 {
1189 None
1190 }
1191 Err(e) => return Err(format!("net.dial_ws_actor: read: {e}")),
1192 };
1193
1194 if let Some(msg) = ws_msg {
1195 let handler = crate::handler::DefaultHandler::new(policy.clone())
1196 .with_program(Arc::clone(program));
1197 let mut vm = Vm::with_handler(program, Box::new(handler));
1198 match vm.invoke_closure_value(on_message.clone(), vec![msg]) {
1199 Ok(action) => {
1200 if let Err(e) = apply_ws_action(&action, ws) {
1201 return Err(format!("net.dial_ws_actor: action: {e}"));
1202 }
1203 }
1204 Err(e) => return Err(format!("net.dial_ws_actor: on_message: {e}")),
1205 }
1206 }
1207
1208 loop {
1210 match rx.try_recv() {
1211 Ok(msg) => {
1212 if let Err(e) = ws.send(Message::Text(msg.into())) {
1213 return Err(format!("net.dial_ws_actor: send: {e}"));
1214 }
1215 }
1216 Err(mpsc::TryRecvError::Empty) => break,
1217 Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
1218 }
1219 }
1220 }
1221}
1222
1223fn dial_run_loop(
1224 ws: &mut tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>,
1225 on_message: &Value,
1226 program: &Arc<Program>,
1227 policy: &Policy,
1228) -> Result<(), String> {
1229 use std::io::ErrorKind;
1230 use tungstenite::Message;
1231
1232 loop {
1233 let ws_msg = match ws.read() {
1234 Ok(Message::Text(body)) => Some(build_ws_message_text(&body)),
1235 Ok(Message::Binary(payload)) => Some(build_ws_message_binary(&payload)),
1236 Ok(Message::Ping(_)) => Some(build_ws_message_ping()),
1237 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => {
1238 let handler = crate::handler::DefaultHandler::new(policy.clone())
1240 .with_program(Arc::clone(program));
1241 let mut vm = Vm::with_handler(program, Box::new(handler));
1242 let _ = vm.invoke_closure_value(
1243 on_message.clone(),
1244 vec![build_ws_message_close()],
1245 );
1246 return Ok(());
1247 }
1248 Ok(_) => None, Err(tungstenite::Error::Io(ref e))
1250 if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut =>
1251 {
1252 None
1253 }
1254 Err(e) => return Err(format!("net.dial_ws: read: {e}")),
1255 };
1256
1257 if let Some(msg) = ws_msg {
1258 let handler = crate::handler::DefaultHandler::new(policy.clone())
1259 .with_program(Arc::clone(program));
1260 let mut vm = Vm::with_handler(program, Box::new(handler));
1261 match vm.invoke_closure_value(on_message.clone(), vec![msg]) {
1262 Ok(action) => {
1263 if let Err(e) = apply_ws_action(&action, ws) {
1264 return Err(format!("net.dial_ws: action: {e}"));
1265 }
1266 }
1267 Err(e) => return Err(format!("net.dial_ws: on_message: {e}")),
1268 }
1269 }
1270 }
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275 use super::accept_or_drop_probe;
1276 use tungstenite::handshake::server::{NoCallback, ServerHandshake};
1277 use tungstenite::{error::ProtocolError, Error, HandshakeError, WebSocket};
1278
1279 type ProbeResult = Result<
1280 WebSocket<std::net::TcpStream>,
1281 HandshakeError<ServerHandshake<std::net::TcpStream, NoCallback>>,
1282 >;
1283
1284 #[test]
1289 fn handshake_incomplete_is_dropped_as_probe() {
1290 let probe: ProbeResult = Err(HandshakeError::Failure(Error::Protocol(
1291 ProtocolError::HandshakeIncomplete,
1292 )));
1293 assert!(matches!(accept_or_drop_probe(probe), Ok(None)));
1294 }
1295
1296 #[test]
1299 fn genuine_handshake_failure_is_surfaced() {
1300 let failure: ProbeResult = Err(HandshakeError::Failure(Error::ConnectionClosed));
1301 assert!(accept_or_drop_probe(failure).is_err());
1302 }
1303}