Try websockets

This commit is contained in:
NGnius (Graham) 2022-06-13 20:12:25 -04:00
parent 0ca02591bd
commit 03d7ac54d1
9 changed files with 579 additions and 59 deletions

323
Cargo.lock generated
View file

@ -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"

View file

@ -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" }

6
usdpl-back/src/errors.rs Normal file
View file

@ -0,0 +1,6 @@
pub enum ServerError {
Tungstenite(tungstenite::error::Error),
Io(std::io::Error),
}
pub type ServerResult = Result<(), ServerError>;

View file

@ -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<const ERROR: bool>(&mut self, packet: socket::Packet, buffer: &mut [u8], incoming: &mut TcpStream) -> std::io::Result<()> {
fn handle_packet<const ERROR: bool>(&mut self, packet: socket::Packet, buffer: &mut [u8], incoming: &mut WebSocket<TcpStream>, 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::<ERROR>(packet, buffer, incoming)?;
self.handle_packet::<ERROR>(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<const ERROR: bool>(&mut self) -> std::io::Result<()> {
pub fn serve<const ERROR: bool>(&mut self) -> super::ServerResult {
let result = self.serve_internal::<ERROR>();
//println!("Stopping server due to serve_internal returning a result");
result
}
/// Receive and execute callbacks forever
pub fn serve_internal<const ERROR: bool>(&mut self) -> std::io::Result<()> {
let listener = TcpListener::bind(socket::socket_addr(self.port))?;
for incoming in listener.incoming() {
let mut incoming = incoming?;
pub fn serve_internal<const ERROR: bool>(&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];
let len = incoming.read(&mut buffer)?;
let (obj_maybe, _) = socket::Packet::load(&buffer[..len]);
for incoming in listener.incoming() {
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 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::<ERROR>(packet, &mut buffer, &mut incoming)?;
self.handle_packet::<ERROR>(packet, &mut buffer, &mut incoming, &peer_addr)?;
} else {
if ERROR {
return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid packet received from {}", incoming.peer_addr()?)));
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 {}", incoming.peer_addr()?);
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");
}
}
}*/
}

View file

@ -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::*;

View file

@ -1,6 +1,6 @@
[package]
name = "usdpl"
version = "0.1.0"
version = "0.2.0"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
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" }

View file

@ -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<socket::Packet> {
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<dyn FnMut(MessageEvent)>);
socket.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
//onmessage_callback.forget();
let onerror_callback = Closure::wrap(Box::new(onerror_factory()) as Box<dyn FnMut(ErrorEvent)>);
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<Sendable<{socket::PACKET_BUFFER_SIZE}>>) -> impl FnMut(MessageEvent) {
move |e: MessageEvent| {
if let Ok(buf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
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<socket::Packet> {
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<const SIZE: usize>(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<const SIZE: usize> wasm_rs_shared_channel::Shareable for Sendable<SIZE> {
type Error = SendableError;
fn to_bytes(&self) -> Result<Uint8Array, Self::Error> {
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<Result<Self, Expects>, 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)))
}
}

View file

@ -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);
}

View file

@ -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<JsValue>) -> Option<Vec<JsValu
for val in parameters {
params.push(convert::js_to_primitive(val));
}
let results = match connection::send_native(Packet::Call(RemoteCall {
let results = match connection::send_js(Packet::Call(RemoteCall {
id: next_id,
function: name,
parameters: params,
}), REMOTE_PORT.load(std::sync::atomic::Ordering::Relaxed)) {
Some(Packet::CallResponse(resp)) => 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 {