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. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.10.0" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 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]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@ -30,6 +66,106 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.57" version = "0.3.57"
@ -60,12 +196,30 @@ dependencies = [
"cfg-if 1.0.0", "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]] [[package]]
name = "memory_units" name = "memory_units"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.39" version = "1.0.39"
@ -84,12 +238,59 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 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]] [[package]]
name = "syn" name = "syn"
version = "1.0.96" version = "1.0.96"
@ -101,21 +302,109 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 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]] [[package]]
name = "usdpl" name = "usdpl"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"js-sys", "js-sys",
"usdpl-core", "usdpl-core",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-test", "wasm-bindgen-test",
"wasm-rs-shared-channel",
"web-sys", "web-sys",
"wee_alloc", "wee_alloc",
] ]
@ -124,6 +413,7 @@ dependencies = [
name = "usdpl-back" name = "usdpl-back"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"tungstenite",
"usdpl-core", "usdpl-core",
] ]
@ -135,6 +425,24 @@ version = "0.1.0"
name = "usdpl-rs" name = "usdpl-rs"
version = "0.1.0" 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]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.80" version = "0.2.80"
@ -225,6 +533,19 @@ dependencies = [
"quote", "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]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.57" version = "0.3.57"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "usdpl-back" name = "usdpl-back"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-only" license = "GPL-3.0-only"
repository = "https://github.com/NGnius/usdpl-rs" repository = "https://github.com/NGnius/usdpl-rs"
@ -14,3 +14,4 @@ crankshaft = []
[dependencies] [dependencies]
usdpl-core = { version = "0.1.0", path = "../usdpl-core" } 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::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::serdes::{Dumpable, Loadable, Primitive};
use usdpl_core::{RemoteCallResponse, socket}; use usdpl_core::{RemoteCallResponse, socket};
@ -27,7 +30,7 @@ impl<'a> Instance<'a> {
self 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 { match packet {
socket::Packet::Call(obj) => { socket::Packet::Call(obj) => {
if let Some(target_func) = self.calls.get_mut(&obj.function) { 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); let (ok, len) = response.dump(buffer);
if !ok && ERROR { 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 { 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 { } 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 { } else {
if ERROR { 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 { } 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 { if let socket::Packet::Many(_) = packet {
// drop nested socket packets (prevents DoS and bad practices) // drop nested socket packets (prevents DoS and bad practices)
if ERROR { 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 { } else {
eprintln!("Invalid nested Many packet received from {}", incoming.peer_addr()?); eprintln!("Invalid nested Many packet received from {}", peer_addr);
} }
continue; 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); let (ok, len) = socket::Packet::Unsupported.dump(buffer);
if !ok && ERROR { 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 { 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 { } 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(()) 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>(); let result = self.serve_internal::<ERROR>();
//println!("Stopping server due to serve_internal returning a result"); //println!("Stopping server due to serve_internal returning a result");
result result
} }
/// Receive and execute callbacks forever /// Receive and execute callbacks forever
pub fn serve_internal<const ERROR: bool>(&mut self) -> std::io::Result<()> { pub fn serve_internal<const ERROR: bool>(&mut self) -> super::ServerResult {
let listener = TcpListener::bind(socket::socket_addr(self.port))?; let listener = TcpListener::bind(socket::socket_addr(self.port)).map_err(super::ServerError::Io)?;
for incoming in listener.incoming() {
let mut incoming = incoming?;
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
let len = incoming.read(&mut buffer)?; for incoming in listener.incoming() {
let (obj_maybe, _) = socket::Packet::load(&buffer[..len]); 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 { 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 { } else {
if ERROR { 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 { } 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(()) Ok(())
} }
@ -115,15 +157,15 @@ impl<'a> Instance<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::net::TcpStream; //use std::net::TcpStream;
use super::*; //use super::*;
const PORT: u16 = 31337; //const PORT: u16 = 31337;
#[test] /*#[test]
fn serve_full_test() -> std::io::Result<()> { fn serve_full_test() -> std::io::Result<()> {
let _server = std::thread::spawn(|| { let _server = std::thread::spawn(|| {
Instance::new(PORT, PORT + 80) Instance::new(PORT)
.register("echo".to_string(), &mut |params| params) .register("echo".to_string(), &mut |params| params)
.register("hello".to_string(), &mut |params| { .register("hello".to_string(), &mut |params| {
if let Some(Primitive::String(name)) = params.get(0) { if let Some(Primitive::String(name)) = params.get(0) {
@ -176,7 +218,7 @@ mod tests {
front.write(&buffer[..len]).unwrap(); front.write(&buffer[..len]).unwrap();
let _ = front.read(&mut buffer).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("echo".to_string(), &mut |params| params)
.register("hello".to_string(), &mut |params| { .register("hello".to_string(), &mut |params| {
if let Some(Primitive::String(name)) = params.get(0) { if let Some(Primitive::String(name)) = params.get(0) {
@ -193,7 +235,7 @@ mod tests {
#[should_panic] #[should_panic]
fn serve_unsupported_test() { fn serve_unsupported_test() {
let _server = std::thread::spawn(|| { let _server = std::thread::spawn(|| {
Instance::new(PORT+2, PORT+2+80) Instance::new(PORT+2)
.register("echo".to_string(), &mut |params| params) .register("echo".to_string(), &mut |params| params)
.register("hello".to_string(), &mut |params| { .register("hello".to_string(), &mut |params| {
if let Some(Primitive::String(name)) = params.get(0) { if let Some(Primitive::String(name)) = params.get(0) {
@ -220,5 +262,5 @@ mod tests {
} else { } else {
panic!("Wrong response packet type"); panic!("Wrong response packet type");
} }
} }*/
} }

View file

@ -4,9 +4,11 @@
//! This is a minimalist TCP server for handling events from the front-end. //! This is a minimalist TCP server for handling events from the front-end.
//! //!
mod errors;
mod instance; mod instance;
pub use instance::Instance; pub use instance::Instance;
pub use errors::{ServerError, ServerResult};
pub mod core { pub mod core {
pub use usdpl_core::*; pub use usdpl_core::*;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "usdpl" name = "usdpl"
version = "0.1.0" version = "0.2.0"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"] authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
edition = "2021" edition = "2021"
license = "GPL-3.0-only" license = "GPL-3.0-only"
@ -17,7 +17,7 @@ decky = []
crankshaft = [] crankshaft = []
[dependencies] [dependencies]
wasm-bindgen = "0.2.63" wasm-bindgen = "0.2"
# The `console_error_panic_hook` crate provides better debugging of panics by # The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires # 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. # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.5", optional = true } 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" } js-sys = { version = "0.3" }
wasm-rs-shared-channel = { version = "0.1" }
usdpl-core = { version = "0.1.0", path = "../usdpl-core" } usdpl-core = { version = "0.1.0", path = "../usdpl-core" }

View file

@ -1,22 +1,49 @@
use std::net::TcpStream; use std::net::TcpStream;
use std::io::{Read, Write}; use std::io::{Read, Write};
use web_sys::TcpSocket; use wasm_bindgen::prelude::*;
use js_sys::{ArrayBuffer, DataView}; 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::socket;
use usdpl_core::serdes::{Dumpable, Loadable}; use usdpl_core::serdes::{Dumpable, Loadable};
use super::imports;
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn send(packet: socket::Packet, port: u16) -> bool { /// Send packet over a Javascript socket
let socket = match TcpSocket::new(socket::HOST_STR, port) { 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, 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 mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
let (ok, len) = packet.dump(&mut buffer); let (ok, len) = packet.dump(&mut buffer);
if !ok { if !ok {
return false; imports::console_error("USDPL error: packet dump failed");
return None;
} }
// copy to JS buffer // copy to JS buffer
let array_buffer = ArrayBuffer::new(len as u32); 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]); dataview.set_uint8(i, buffer[i]);
} }
match socket.send_with_array_buffer(&array_buffer) { match socket.send_with_array_buffer(&array_buffer) {
Ok(b) => b, Ok(_) => {},
Err(_) => false 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> { pub(crate) fn send_native(packet: socket::Packet, port: u16) -> Option<socket::Packet> {
let mut socket = match TcpStream::connect(socket::socket_addr(port)) { let mut socket = match TcpStream::connect(socket::socket_addr(port)) {
Ok(s) => s, 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 mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
let (ok, len) = packet.dump(&mut buffer); let (ok, len) = packet.dump(&mut buffer);
if !ok { if !ok {
imports::console_error("USDPL error: packet dump failed");
return None; return None;
} }
match socket.write(&buffer[..len]) { match socket.write(&buffer[..len]) {
Ok(_) => {}, 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) { let len = match socket.read(&mut buffer) {
Ok(len) => len, 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 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 connection;
mod convert; mod convert;
mod imports;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -18,11 +19,6 @@ const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::
#[global_allocator] #[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
//fn alert(s: &str);
}
/// Initialize the front-end library /// Initialize the front-end library
#[wasm_bindgen] #[wasm_bindgen]
pub fn init_usdpl(port: u16) -> bool { 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 { for val in parameters {
params.push(convert::js_to_primitive(val)); 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, id: next_id,
function: name, function: name,
parameters: params, parameters: params,
}), REMOTE_PORT.load(std::sync::atomic::Ordering::Relaxed)) { }), REMOTE_PORT.load(std::sync::atomic::Ordering::Relaxed)) {
Some(Packet::CallResponse(resp)) => resp, 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()); let mut js_results = Vec::with_capacity(results.response.len());
for val in results.response { for val in results.response {