From 03d7ac54d1797fba06fa7a647589f6fa4c2b368e Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Mon, 13 Jun 2022 20:12:25 -0400 Subject: [PATCH] Try websockets --- Cargo.lock | 323 +++++++++++++++++++++++++++++++++- usdpl-back/Cargo.toml | 3 +- usdpl-back/src/errors.rs | 6 + usdpl-back/src/instance.rs | 114 ++++++++---- usdpl-back/src/lib.rs | 2 + usdpl-front/Cargo.toml | 7 +- usdpl-front/src/connection.rs | 157 +++++++++++++++-- usdpl-front/src/imports.rs | 13 ++ usdpl-front/src/lib.rs | 13 +- 9 files changed, 579 insertions(+), 59 deletions(-) create mode 100644 usdpl-back/src/errors.rs create mode 100644 usdpl-front/src/imports.rs diff --git a/Cargo.lock b/Cargo.lock index 506bba5..fcbd3f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,48 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + [[package]] name = "cfg-if" version = "0.1.10" @@ -30,6 +66,106 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + [[package]] name = "js-sys" version = "0.3.57" @@ -60,12 +196,30 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memory_units" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro2" version = "1.0.39" @@ -84,12 +238,59 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + [[package]] name = "syn" version = "1.0.96" @@ -101,21 +302,109 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "usdpl" -version = "0.1.0" +version = "0.2.0" dependencies = [ "console_error_panic_hook", "js-sys", "usdpl-core", "wasm-bindgen", "wasm-bindgen-test", + "wasm-rs-shared-channel", "web-sys", "wee_alloc", ] @@ -124,6 +413,7 @@ dependencies = [ name = "usdpl-back" version = "0.1.0" dependencies = [ + "tungstenite", "usdpl-core", ] @@ -135,6 +425,24 @@ version = "0.1.0" name = "usdpl-rs" version = "0.1.0" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "wasm-bindgen" version = "0.2.80" @@ -225,6 +533,19 @@ dependencies = [ "quote", ] +[[package]] +name = "wasm-rs-shared-channel" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555c35b5c51f5f134fb470b103435880fd7a9def4e830dc0081d8af5b09378b" +dependencies = [ + "bincode", + "js-sys", + "serde", + "thiserror", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.57" diff --git a/usdpl-back/Cargo.toml b/usdpl-back/Cargo.toml index 7b5d1fb..9080157 100644 --- a/usdpl-back/Cargo.toml +++ b/usdpl-back/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "usdpl-back" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "GPL-3.0-only" repository = "https://github.com/NGnius/usdpl-rs" @@ -14,3 +14,4 @@ crankshaft = [] [dependencies] usdpl-core = { version = "0.1.0", path = "../usdpl-core" } +tungstenite = { version = "0.17" } diff --git a/usdpl-back/src/errors.rs b/usdpl-back/src/errors.rs new file mode 100644 index 0000000..96fbded --- /dev/null +++ b/usdpl-back/src/errors.rs @@ -0,0 +1,6 @@ +pub enum ServerError { + Tungstenite(tungstenite::error::Error), + Io(std::io::Error), +} + +pub type ServerResult = Result<(), ServerError>; diff --git a/usdpl-back/src/instance.rs b/usdpl-back/src/instance.rs index da15010..98a4b48 100644 --- a/usdpl-back/src/instance.rs +++ b/usdpl-back/src/instance.rs @@ -1,6 +1,9 @@ -use std::net::{TcpListener, TcpStream}; +use std::net::{TcpListener, TcpStream, SocketAddr}; use std::collections::HashMap; -use std::io::{Read, Write}; +//use std::io::{Read, Write}; + +use tungstenite::accept; +use tungstenite::protocol::{Message, WebSocket}; use usdpl_core::serdes::{Dumpable, Loadable, Primitive}; use usdpl_core::{RemoteCallResponse, socket}; @@ -27,7 +30,7 @@ impl<'a> Instance<'a> { self } - fn handle_packet(&mut self, packet: socket::Packet, buffer: &mut [u8], incoming: &mut TcpStream) -> std::io::Result<()> { + fn handle_packet(&mut self, packet: socket::Packet, buffer: &mut [u8], incoming: &mut WebSocket, peer_addr: &SocketAddr) -> super::ServerResult { match packet { socket::Packet::Call(obj) => { if let Some(target_func) = self.calls.get_mut(&obj.function) { @@ -39,18 +42,22 @@ impl<'a> Instance<'a> { }); let (ok, len) = response.dump(buffer); if !ok && ERROR { - return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump return value of function `{}`", &obj.function))); + return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump return value of function `{}`", &obj.function)))); } if ERROR { - incoming.write(&buffer[..len])?; + let mut vec = Vec::with_capacity(len); + vec.extend_from_slice(&buffer[..len]); + incoming.write_message(Message::Binary(vec)).map_err(super::ServerError::Tungstenite)?; } else { - incoming.write(&buffer[..len]).unwrap_or_default(); + let mut vec = Vec::with_capacity(len); + vec.extend_from_slice(&buffer[..len]); + incoming.write_message(Message::Binary(vec)).unwrap_or_default(); } } else { if ERROR { - return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid remote call `{}` received from {}", obj.function, incoming.peer_addr()?))); + return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid remote call `{}` received from {}", obj.function, peer_addr)))); } else { - eprintln!("Invalid remote call `{}` received from {}", obj.function, incoming.peer_addr()?); + eprintln!("Invalid remote call `{}` received from {}", obj.function, peer_addr); } } @@ -60,54 +67,89 @@ impl<'a> Instance<'a> { if let socket::Packet::Many(_) = packet { // drop nested socket packets (prevents DoS and bad practices) if ERROR { - return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid nested Many packet received from {}", incoming.peer_addr()?))); + return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid nested Many packet received from {}", peer_addr)))); } else { - eprintln!("Invalid nested Many packet received from {}", incoming.peer_addr()?); + eprintln!("Invalid nested Many packet received from {}", peer_addr); } continue; } - self.handle_packet::(packet, buffer, incoming)?; + self.handle_packet::(packet, buffer, incoming, peer_addr)?; } }, _ => { let (ok, len) = socket::Packet::Unsupported.dump(buffer); if !ok && ERROR { - return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump unsupported packet"))); + return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump unsupported packet")))); } if ERROR { - incoming.write(&buffer[..len])?; + let mut vec = Vec::with_capacity(len); + vec.extend_from_slice(&buffer[..len]); + incoming.write_message(Message::Binary(vec)).map_err(super::ServerError::Tungstenite)?; } else { - incoming.write(&buffer[..len]).unwrap_or_default(); + let mut vec = Vec::with_capacity(len); + vec.extend_from_slice(&buffer[..len]); + incoming.write_message(Message::Binary(vec)).unwrap_or_default(); } } } Ok(()) } - pub fn serve(&mut self) -> std::io::Result<()> { + pub fn serve(&mut self) -> super::ServerResult { let result = self.serve_internal::(); //println!("Stopping server due to serve_internal returning a result"); result } /// Receive and execute callbacks forever - pub fn serve_internal(&mut self) -> std::io::Result<()> { - let listener = TcpListener::bind(socket::socket_addr(self.port))?; + pub fn serve_internal(&mut self) -> super::ServerResult { + let listener = TcpListener::bind(socket::socket_addr(self.port)).map_err(super::ServerError::Io)?; + let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; for incoming in listener.incoming() { - let mut incoming = incoming?; - let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let len = incoming.read(&mut buffer)?; - let (obj_maybe, _) = socket::Packet::load(&buffer[..len]); - if let Some(packet) = obj_maybe { - self.handle_packet::(packet, &mut buffer, &mut incoming)?; - } else { - if ERROR { - return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid packet received from {}", incoming.peer_addr()?))); + let incoming = incoming.map_err(super::ServerError::Io)?; + let peer_addr = incoming.peer_addr().map_err(super::ServerError::Io)?; + let mut incoming = match accept(incoming) { + Ok(s) => s, + Err(_) => continue, + }; + match incoming.read_message() { + Err(e) => if ERROR { + return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid message received from {}: {}", peer_addr, e)))); } else { - eprintln!("Invalid packet received from {}", incoming.peer_addr()?); + eprintln!("Invalid message received from {}: {}", peer_addr, e); + }, + Ok(Message::Binary(bin)) => { + let (obj_maybe, _) = socket::Packet::load(bin.as_slice()); + if let Some(packet) = obj_maybe { + self.handle_packet::(packet, &mut buffer, &mut incoming, &peer_addr)?; + } else { + if ERROR { + return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid packet received from {}", peer_addr)))); + } else { + eprintln!("Invalid packet received from {}", peer_addr); + } + } + }, + Ok(_) => { + let (_, len) = socket::Packet::Unsupported.dump(&mut buffer); + if ERROR { + let mut vec = Vec::with_capacity(len); + vec.extend_from_slice(&buffer[..len]); + incoming.write_message(Message::Binary(vec)).map_err(super::ServerError::Tungstenite)?; + } else { + let mut vec = Vec::with_capacity(len); + vec.extend_from_slice(&buffer[..len]); + incoming.write_message(Message::Binary(vec)).unwrap_or_default(); + } + } + } + incoming.close(None).map_err(super::ServerError::Tungstenite)?; + 'endless: loop { + match incoming.read_message() { + Err(tungstenite::error::Error::ConnectionClosed) => break 'endless, + _ => {} } } - incoming.shutdown(std::net::Shutdown::Both)?; } Ok(()) } @@ -115,15 +157,15 @@ impl<'a> Instance<'a> { #[cfg(test)] mod tests { - use std::net::TcpStream; - use super::*; + //use std::net::TcpStream; + //use super::*; - const PORT: u16 = 31337; + //const PORT: u16 = 31337; - #[test] + /*#[test] fn serve_full_test() -> std::io::Result<()> { let _server = std::thread::spawn(|| { - Instance::new(PORT, PORT + 80) + Instance::new(PORT) .register("echo".to_string(), &mut |params| params) .register("hello".to_string(), &mut |params| { if let Some(Primitive::String(name)) = params.get(0) { @@ -176,7 +218,7 @@ mod tests { front.write(&buffer[..len]).unwrap(); let _ = front.read(&mut buffer).unwrap(); }); - Instance::new(PORT+1, PORT+1+80) + Instance::new(PORT+1) .register("echo".to_string(), &mut |params| params) .register("hello".to_string(), &mut |params| { if let Some(Primitive::String(name)) = params.get(0) { @@ -193,7 +235,7 @@ mod tests { #[should_panic] fn serve_unsupported_test() { let _server = std::thread::spawn(|| { - Instance::new(PORT+2, PORT+2+80) + Instance::new(PORT+2) .register("echo".to_string(), &mut |params| params) .register("hello".to_string(), &mut |params| { if let Some(Primitive::String(name)) = params.get(0) { @@ -220,5 +262,5 @@ mod tests { } else { panic!("Wrong response packet type"); } - } + }*/ } diff --git a/usdpl-back/src/lib.rs b/usdpl-back/src/lib.rs index 8a4fbfd..bed0ee6 100644 --- a/usdpl-back/src/lib.rs +++ b/usdpl-back/src/lib.rs @@ -4,9 +4,11 @@ //! This is a minimalist TCP server for handling events from the front-end. //! +mod errors; mod instance; pub use instance::Instance; +pub use errors::{ServerError, ServerResult}; pub mod core { pub use usdpl_core::*; diff --git a/usdpl-front/Cargo.toml b/usdpl-front/Cargo.toml index c02746d..2524434 100644 --- a/usdpl-front/Cargo.toml +++ b/usdpl-front/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "usdpl" -version = "0.1.0" +version = "0.2.0" authors = ["NGnius (Graham) "] edition = "2021" license = "GPL-3.0-only" @@ -17,7 +17,7 @@ decky = [] crankshaft = [] [dependencies] -wasm-bindgen = "0.2.63" +wasm-bindgen = "0.2" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires @@ -32,8 +32,9 @@ console_error_panic_hook = { version = "0.1.6", optional = true } # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. wee_alloc = { version = "0.4.5", optional = true } -web-sys = { version = "0.3", features = ["TcpSocket"] } +web-sys = { version = "0.3", features = ["WebSocket", "MessageEvent", "ErrorEvent", "BinaryType"] } js-sys = { version = "0.3" } +wasm-rs-shared-channel = { version = "0.1" } usdpl-core = { version = "0.1.0", path = "../usdpl-core" } diff --git a/usdpl-front/src/connection.rs b/usdpl-front/src/connection.rs index 4d02940..ff8e638 100644 --- a/usdpl-front/src/connection.rs +++ b/usdpl-front/src/connection.rs @@ -1,22 +1,49 @@ use std::net::TcpStream; use std::io::{Read, Write}; -use web_sys::TcpSocket; -use js_sys::{ArrayBuffer, DataView}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +use web_sys::{WebSocket, MessageEvent, ErrorEvent}; +use js_sys::{ArrayBuffer, DataView, Uint8Array}; +use wasm_rs_shared_channel::{Expects, spsc::{Receiver, Sender}}; use usdpl_core::socket; use usdpl_core::serdes::{Dumpable, Loadable}; +use super::imports; + #[allow(dead_code)] -pub(crate) fn send(packet: socket::Packet, port: u16) -> bool { - let socket = match TcpSocket::new(socket::HOST_STR, port) { +/// Send packet over a Javascript socket +pub(crate) fn send_js(packet: socket::Packet, port: u16) -> Option { + let addr = format!("wss://{}:{}", socket::HOST_STR, port); + let socket = match WebSocket::new(&addr) { Ok(s) => s, - Err(_) => return false, + Err(e) => { + imports::console_error( + &format!("USDPL error: TcpSocket::new(...) failed with error {}", + js_sys::JSON::stringify(&e) + .map(|x| x.as_string().unwrap_or("WTF".into())) + .unwrap_or("unknown error".into()))); + return None; + } }; + socket.set_binary_type(web_sys::BinaryType::Arraybuffer); + let (tx, rx) : (Sender<_>, Receiver<_>) = wasm_rs_shared_channel::spsc::channel(socket::PACKET_BUFFER_SIZE as u32 + 4).split(); + + let onmessage_callback = Closure::wrap(Box::new(onmessage_factory(tx)) as Box); + socket.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); + //onmessage_callback.forget(); + + let onerror_callback = Closure::wrap(Box::new(onerror_factory()) as Box); + socket.set_onerror(Some(onerror_callback.as_ref().unchecked_ref())); + //onerror_callback.forget(); + let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; let (ok, len) = packet.dump(&mut buffer); if !ok { - return false; + imports::console_error("USDPL error: packet dump failed"); + return None; } // copy to JS buffer let array_buffer = ArrayBuffer::new(len as u32); @@ -25,28 +52,136 @@ pub(crate) fn send(packet: socket::Packet, port: u16) -> bool { dataview.set_uint8(i, buffer[i]); } match socket.send_with_array_buffer(&array_buffer) { - Ok(b) => b, - Err(_) => false + Ok(_) => {}, + Err(e) => { + imports::console_error(&format!("USDPL error: socket send_with_array_buffer(...) failed -- {:?}", e)); + return None; + } + } + let result = match rx.recv(Some(std::time::Duration::from_secs(60))) { + Ok(Some(val)) => { + socket::Packet::load(&val.1[..val.0 as _]).0 + }, + Ok(None) => { + imports::console_error(&format!("USDPL error: SharedChannel recv timed out")); + None + }, + Err(e) => { + imports::console_error(&format!("USDPL error: got SharedChannel recv error -- {:?}", e)); + None + } + }; + socket.close().unwrap_or(()); + result +} + +fn onmessage_factory(sender: Sender>) -> impl FnMut(MessageEvent) { + move |e: MessageEvent| { + if let Ok(buf) = e.data().dyn_into::() { + let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; + let dataview = DataView::new(&buf, 0, buf.byte_length() as _); + for i in 0..buf.byte_length() as usize { + if i < socket::PACKET_BUFFER_SIZE { + buffer[i] = dataview.get_uint8(i); + } else { + break; + } + } + if let Err(e) = sender.send(&Sendable(buf.byte_length(), buffer)) { + imports::console_error(&format!("USDPL error: got SharedChannel send error {:?}", e)); + } + } else { + imports::console_warn(&format!("USDPL warning: Got non-data message from {}", e.origin())); + } } } +fn onerror_factory() -> impl FnMut(ErrorEvent) { + move |e: ErrorEvent| { + imports::console_error(&format!("USDPL error: got socket error {}", e.message())) + } +} + +#[allow(dead_code)] +/// Send packet over a WASM-native TCP socket pub(crate) fn send_native(packet: socket::Packet, port: u16) -> Option { let mut socket = match TcpStream::connect(socket::socket_addr(port)) { Ok(s) => s, - Err(_) => return None, + Err(e) => { + imports::console_error(&format!("USDPL error: TcpStream failed to connect with error {}", e)); + return None; + }, }; let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; let (ok, len) = packet.dump(&mut buffer); if !ok { + imports::console_error("USDPL error: packet dump failed"); return None; } match socket.write(&buffer[..len]) { Ok(_) => {}, - Err(_) => return None + Err(e) => { + imports::console_error(&format!("USDPL error: socket write failed with error {}", e)); + return None; + } } let len = match socket.read(&mut buffer) { Ok(len) => len, - Err(_) => return None + Err(e) => { + imports::console_error(&format!("USDPL error: socket read failed with error {}", e)); + return None; + } }; socket::Packet::load(&buffer[..len]).0 } + +struct Sendable(u32, [u8; SIZE]); + +#[derive(Debug)] +struct SendableError(String); + +impl std::error::Error for SendableError {} + +impl std::fmt::Display for SendableError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (&self.0 as &dyn std::fmt::Display).fmt(f) + } +} + +impl wasm_rs_shared_channel::Shareable for Sendable { + type Error = SendableError; + + fn to_bytes(&self) -> Result { + let array = Uint8Array::new_with_length(SIZE as u32 + 4); + let mut cursor = 0; + for byte in self.0.to_le_bytes() { + array.set_index(cursor, byte); + cursor += 1; + } + for byte in self.1 { + array.set_index(cursor, byte); + cursor += 1; + } + Ok(array) + } + + fn from(bytes: &Uint8Array) -> Result, Self::Error> { + if bytes.length() < 4 { + return Err(SendableError("Too small for size int".into())); + } + let len = u32::from_le_bytes([ + bytes.get_index(0), + bytes.get_index(1), + bytes.get_index(2), + bytes.get_index(3), + ]); + if bytes.length() < len + 4 { + return Err(SendableError("Too small for buffer".into())); + } + let mut buf = [0u8; SIZE]; + for i in 0..len { + buf[i as usize] = bytes.get_index(4 + i); + } + Ok(Ok(Sendable(len, buf))) + } +} diff --git a/usdpl-front/src/imports.rs b/usdpl-front/src/imports.rs new file mode 100644 index 0000000..e7d3ec7 --- /dev/null +++ b/usdpl-front/src/imports.rs @@ -0,0 +1,13 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern { + #[wasm_bindgen(js_namespace = console, js_name = log)] + pub fn console_log(s: &str); + + #[wasm_bindgen(js_namespace = console, js_name = warn)] + pub fn console_warn(s: &str); + + #[wasm_bindgen(js_namespace = console, js_name = error)] + pub fn console_error(s: &str); +} diff --git a/usdpl-front/src/lib.rs b/usdpl-front/src/lib.rs index 1cf3e8a..c89a86f 100644 --- a/usdpl-front/src/lib.rs +++ b/usdpl-front/src/lib.rs @@ -6,6 +6,7 @@ mod connection; mod convert; +mod imports; use wasm_bindgen::prelude::*; @@ -18,11 +19,6 @@ const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16:: #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; -#[wasm_bindgen] -extern { - //fn alert(s: &str); -} - /// Initialize the front-end library #[wasm_bindgen] pub fn init_usdpl(port: u16) -> bool { @@ -52,13 +48,16 @@ pub fn call_backend(name: String, parameters: Vec) -> Option resp, - _ => return None, + _ => { + imports::console_error("USDPL error: connection::send_native(...) returned None"); + return None + }, }; let mut js_results = Vec::with_capacity(results.response.len()); for val in results.response {