Initial functionality
This commit is contained in:
commit
0219b4c2b7
21 changed files with 1361 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
270
Cargo.lock
generated
Normal file
270
Cargo.lock
generated
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_error_panic_hook"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memory_units"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.96"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "usdpl"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"console_error_panic_hook",
|
||||||
|
"js-sys",
|
||||||
|
"usdpl-core",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-test",
|
||||||
|
"web-sys",
|
||||||
|
"wee_alloc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "usdpl-back"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"usdpl-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "usdpl-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "usdpl-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.80"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.80"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.80"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.80"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.80"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-test"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4464b3f74729a25f42b1a0cd9e6a515d2f25001f3535a6cfaf35d34a4de3bab"
|
||||||
|
dependencies = [
|
||||||
|
"console_error_panic_hook",
|
||||||
|
"js-sys",
|
||||||
|
"scoped-tls",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-bindgen-test-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-test-macro"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a77c5a6f82cc6093a321ca5fb3dc9327fe51675d477b3799b4a9375bac3b7b4c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wee_alloc"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"libc",
|
||||||
|
"memory_units",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "usdpl-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"usdpl-core",
|
||||||
|
"usdpl-front",
|
||||||
|
"usdpl-back"
|
||||||
|
]
|
15
README.md
Normal file
15
README.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# usdpl-rs
|
||||||
|
|
||||||
|
Universal Steam Deck Plugin Library
|
||||||
|
|
||||||
|
A faster, lighter way to write plugins
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
- [ ] Minimum viable plugin
|
||||||
|
- [ ] Call back-end API from front-end UI
|
||||||
|
- [ ] Async support
|
||||||
|
- [ ] PluginLoader/Decky support
|
||||||
|
- [ ] Crankshaft support
|
||||||
|
- [ ] Unnamed plugin system support
|
||||||
|
- [ ] Cross-framework tooling
|
||||||
|
|
16
src/main.rs
Normal file
16
src/main.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//! Universal Steam Deck Plugin Library
|
||||||
|
//!
|
||||||
|
//! A faster, lighter way to write plugins
|
||||||
|
//!
|
||||||
|
//! ## Goals
|
||||||
|
//! - [ ] Minimum viable plugin
|
||||||
|
//! - [ ] Call back-end API from front-end UI
|
||||||
|
//! - [ ] Async support
|
||||||
|
//! - [ ] PluginLoader/Decky support
|
||||||
|
//! - [ ] Crankshaft support
|
||||||
|
//! - [ ] Unnamed plugin system support
|
||||||
|
//! - [ ] Cross-framework tooling
|
||||||
|
//!
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, USDPL!");
|
||||||
|
}
|
9
usdpl-back/Cargo.toml
Normal file
9
usdpl-back/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "usdpl-back"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
usdpl-core = { version = "0.1.0", path = "../usdpl-core" }
|
194
usdpl-back/src/instance.rs
Normal file
194
usdpl-back/src/instance.rs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
use std::net::TcpListener;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use usdpl_core::serdes::{Dumpable, Loadable, Primitive};
|
||||||
|
use usdpl_core::{RemoteCallResponse, socket};
|
||||||
|
|
||||||
|
/// Instance for interacting with the front-end
|
||||||
|
pub struct Instance<'a> {
|
||||||
|
calls: HashMap<String, &'a mut dyn FnMut(Vec<Primitive>) -> Vec<Primitive>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Instance<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Instance {
|
||||||
|
calls: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a function which can be invoked by the front-end
|
||||||
|
pub fn register<F: (FnMut(Vec<Primitive>) -> Vec<Primitive>) + Send + Sync>(&mut self, name: String, f: &'a mut F) -> &mut Self {
|
||||||
|
self.calls.insert(name, f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive and execute callbacks forever
|
||||||
|
pub fn serve<const ERROR: bool>(&mut self) -> std::io::Result<()> {
|
||||||
|
let listener = TcpListener::bind(socket::socket_addr())?;
|
||||||
|
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 {
|
||||||
|
match packet {
|
||||||
|
socket::Packet::Call(obj) => {
|
||||||
|
if let Some(target_func) = self.calls.get_mut(&obj.function) {
|
||||||
|
// TODO: multithread this
|
||||||
|
let result = target_func(obj.parameters);
|
||||||
|
let response = socket::Packet::CallResponse(RemoteCallResponse {
|
||||||
|
id: obj.id,
|
||||||
|
response: result,
|
||||||
|
});
|
||||||
|
let (ok, len) = response.dump(&mut buffer);
|
||||||
|
if !ok && ERROR {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump return value of function `{}`", &obj.function)));
|
||||||
|
}
|
||||||
|
if ERROR {
|
||||||
|
incoming.write(&buffer[..len])?;
|
||||||
|
} else {
|
||||||
|
incoming.write(&buffer[..len]).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()?)));
|
||||||
|
} else {
|
||||||
|
eprintln!("Invalid remote call `{}` received from {}", obj.function, incoming.peer_addr()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let (ok, len) = socket::Packet::Unsupported.dump(&mut buffer);
|
||||||
|
if !ok && ERROR {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump unsupported packet")));
|
||||||
|
}
|
||||||
|
if ERROR {
|
||||||
|
incoming.write(&buffer[..len])?;
|
||||||
|
} else {
|
||||||
|
incoming.write(&buffer[..len]).unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ERROR {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid packet received from {}", incoming.peer_addr()?)));
|
||||||
|
} else {
|
||||||
|
eprintln!("Invalid packet received from {}", incoming.peer_addr()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
incoming.shutdown(std::net::Shutdown::Both)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serve_full_test() -> std::io::Result<()> {
|
||||||
|
let _server = std::thread::spawn(|| {
|
||||||
|
Instance::new()
|
||||||
|
.register("echo".to_string(), &mut |params| params)
|
||||||
|
.register("hello".to_string(), &mut |params| {
|
||||||
|
if let Some(Primitive::String(name)) = params.get(0) {
|
||||||
|
vec![Primitive::String(format!("Hello {}", name))]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.serve::<true>()
|
||||||
|
});
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
let mut front = TcpStream::connect(socket::socket_addr()).unwrap();
|
||||||
|
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
|
||||||
|
let call = socket::Packet::Call(usdpl_core::RemoteCall {
|
||||||
|
id: 42,
|
||||||
|
function: "hello".to_string(),
|
||||||
|
parameters: vec![Primitive::String("USDPL".to_string())]
|
||||||
|
});
|
||||||
|
let (ok, len) = call.dump(&mut buffer);
|
||||||
|
assert!(ok, "Packet dump failed");
|
||||||
|
assert_eq!(len, 32, "Packet dumped wrong amount of data");
|
||||||
|
front.write(&buffer[..len])?;
|
||||||
|
let len = front.read(&mut buffer)?;
|
||||||
|
let (response, len) = socket::Packet::load(&buffer[..len]);
|
||||||
|
assert!(response.is_some(), "Response load failed");
|
||||||
|
assert_eq!(len, 29, "Response loaded wrong amount of data");
|
||||||
|
let response = response.unwrap();
|
||||||
|
if let socket::Packet::CallResponse(resp) = response {
|
||||||
|
assert_eq!(resp.id, 42);
|
||||||
|
if let Some(Primitive::String(s)) = resp.response.get(0) {
|
||||||
|
assert_eq!(s, "Hello USDPL");
|
||||||
|
} else {
|
||||||
|
panic!("Wrong response data");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Wrong response packet type");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn serve_err_test() {
|
||||||
|
let _client = std::thread::spawn(|| {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
let mut front = TcpStream::connect(socket::socket_addr()).unwrap();
|
||||||
|
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
|
||||||
|
let (_, len) = socket::Packet::Bad.dump(&mut buffer);
|
||||||
|
front.write(&buffer[..len]).unwrap();
|
||||||
|
let _ = front.read(&mut buffer).unwrap();
|
||||||
|
});
|
||||||
|
Instance::new()
|
||||||
|
.register("echo".to_string(), &mut |params| params)
|
||||||
|
.register("hello".to_string(), &mut |params| {
|
||||||
|
if let Some(Primitive::String(name)) = params.get(0) {
|
||||||
|
vec![Primitive::String(format!("Hello {}", name))]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.serve::<true>()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn serve_unsupported_test() {
|
||||||
|
let _server = std::thread::spawn(|| {
|
||||||
|
Instance::new()
|
||||||
|
.register("echo".to_string(), &mut |params| params)
|
||||||
|
.register("hello".to_string(), &mut |params| {
|
||||||
|
if let Some(Primitive::String(name)) = params.get(0) {
|
||||||
|
vec![Primitive::String(format!("Hello {}", name))]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.serve::<true>()
|
||||||
|
});
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
let mut front = TcpStream::connect(socket::socket_addr()).unwrap();
|
||||||
|
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
|
||||||
|
let (ok, len) = socket::Packet::Unsupported.dump(&mut buffer);
|
||||||
|
assert!(ok, "Packet dump failed");
|
||||||
|
assert_eq!(len, 32, "Packet dumped wrong amount of data");
|
||||||
|
front.write(&buffer[..len]).unwrap();
|
||||||
|
let len = front.read(&mut buffer).unwrap();
|
||||||
|
let (response, len) = socket::Packet::load(&buffer[..len]);
|
||||||
|
assert!(response.is_some(), "Response load failed");
|
||||||
|
assert_eq!(len, 29, "Response loaded wrong amount of data");
|
||||||
|
let response = response.unwrap();
|
||||||
|
if let socket::Packet::Unsupported = response {
|
||||||
|
} else {
|
||||||
|
panic!("Wrong response packet type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
usdpl-back/src/lib.rs
Normal file
9
usdpl-back/src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//! Back-end library for plugins.
|
||||||
|
//! Targets x86_64 (native Steam Deck ISA).
|
||||||
|
//!
|
||||||
|
//! This is a minimalist TCP server for handling events from the front-end.
|
||||||
|
//!
|
||||||
|
|
||||||
|
mod instance;
|
||||||
|
|
||||||
|
pub use instance::Instance;
|
8
usdpl-core/Cargo.toml
Normal file
8
usdpl-core/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "usdpl-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
8
usdpl-core/src/lib.rs
Normal file
8
usdpl-core/src/lib.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//! Datatypes and constants core the back-end and front-end libraries' operation.
|
||||||
|
//! This contains serialization functionality and networking datatypes.
|
||||||
|
mod remote_call;
|
||||||
|
|
||||||
|
pub mod socket;
|
||||||
|
pub mod serdes;
|
||||||
|
|
||||||
|
pub use remote_call::{RemoteCall, RemoteCallResponse};
|
84
usdpl-core/src/remote_call.rs
Normal file
84
usdpl-core/src/remote_call.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use crate::serdes::{Primitive, Loadable, Dumpable};
|
||||||
|
|
||||||
|
pub struct RemoteCall {
|
||||||
|
pub id: u64,
|
||||||
|
pub function: String,
|
||||||
|
pub parameters: Vec<Primitive>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loadable for RemoteCall {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
let (id_num, len0) = u64::load(buffer);
|
||||||
|
if id_num.is_none() {
|
||||||
|
return (None, len0);
|
||||||
|
}
|
||||||
|
let (function_name, len1) = String::load(&buffer[len0..]);
|
||||||
|
if function_name.is_none() {
|
||||||
|
return (None, len1);
|
||||||
|
}
|
||||||
|
let (params, len2) = Vec::<Primitive>::load(&buffer[len0+len1..]);
|
||||||
|
if params.is_none() {
|
||||||
|
return (None, len1 + len2);
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Some(Self {
|
||||||
|
id: id_num.unwrap(),
|
||||||
|
function: function_name.unwrap(),
|
||||||
|
parameters: params.unwrap(),
|
||||||
|
}),
|
||||||
|
len0 + len1 + len2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dumpable for RemoteCall {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
let (ok0, len0) = self.id.dump(buffer);
|
||||||
|
if !ok0 {
|
||||||
|
return (ok0, len0);
|
||||||
|
}
|
||||||
|
let (ok1, len1) = self.function.dump(&mut buffer[len0..]);
|
||||||
|
if !ok1 {
|
||||||
|
return (ok1, len1);
|
||||||
|
}
|
||||||
|
let (ok2, len2) = self.parameters.dump(&mut buffer[len0+len1..]);
|
||||||
|
(ok2, len0 + len1 + len2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RemoteCallResponse {
|
||||||
|
pub id: u64,
|
||||||
|
pub response: Vec<Primitive>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loadable for RemoteCallResponse {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
let (id_num, len0) = u64::load(buffer);
|
||||||
|
if id_num.is_none() {
|
||||||
|
return (None, len0);
|
||||||
|
}
|
||||||
|
let (response_var, len1) = Vec::<Primitive>::load(&buffer[len0..]);
|
||||||
|
if response_var.is_none() {
|
||||||
|
return (None, len1);
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Some(Self {
|
||||||
|
id: id_num.unwrap(),
|
||||||
|
response: response_var.unwrap(),
|
||||||
|
}),
|
||||||
|
len0 + len1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dumpable for RemoteCallResponse {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
let (ok0, len0) = self.id.dump(buffer);
|
||||||
|
if !ok0 {
|
||||||
|
return (ok0, len0);
|
||||||
|
}
|
||||||
|
let (ok1, len1) = self.response.dump(&mut buffer[len0..]);
|
||||||
|
(ok1, len0 + len1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
140
usdpl-core/src/serdes/dump_impl.rs
Normal file
140
usdpl-core/src/serdes/dump_impl.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use super::Dumpable;
|
||||||
|
|
||||||
|
impl Dumpable for String {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
let str_bytes = self.as_bytes();
|
||||||
|
let len_bytes = (str_bytes.len() as u32).to_le_bytes();
|
||||||
|
let total_len = str_bytes.len() + 4;
|
||||||
|
if buffer.len() < total_len {
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
(&mut buffer[..4]).copy_from_slice(&len_bytes);
|
||||||
|
(&mut buffer[4..total_len]).copy_from_slice(str_bytes);
|
||||||
|
(true, total_len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Dumpable> Dumpable for Vec<T> {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
let len_bytes = (self.len() as u32).to_le_bytes();
|
||||||
|
(&mut buffer[..4]).copy_from_slice(&len_bytes);
|
||||||
|
let mut cursor = 4;
|
||||||
|
for obj in self.iter() {
|
||||||
|
let (ok, len) = obj.dump(&mut buffer[cursor..]);
|
||||||
|
cursor += len;
|
||||||
|
if !ok {
|
||||||
|
return (false, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(true, cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dumpable for bool {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
if buffer.len() < 1 {
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
buffer[0] = *self as u8;
|
||||||
|
(true, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dumpable for u8 {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
if buffer.len() < 1 {
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
buffer[0] = *self;
|
||||||
|
(true, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dumpable for i8 {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
if buffer.len() < 1 {
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
buffer[0] = self.to_le_bytes()[0];
|
||||||
|
(true, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! int_impl {
|
||||||
|
($type:ty, $size:literal) => {
|
||||||
|
impl Dumpable for $type {
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) {
|
||||||
|
if buffer.len() < $size {
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
(&mut buffer[..$size]).copy_from_slice(&self.to_le_bytes());
|
||||||
|
(true, $size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int_impl!{u16, 2}
|
||||||
|
int_impl!{u32, 4}
|
||||||
|
int_impl!{u64, 8}
|
||||||
|
int_impl!{u128, 16}
|
||||||
|
|
||||||
|
int_impl!{i16, 2}
|
||||||
|
int_impl!{i32, 4}
|
||||||
|
int_impl!{i64, 8}
|
||||||
|
int_impl!{i128, 16}
|
||||||
|
|
||||||
|
int_impl!{f32, 4}
|
||||||
|
int_impl!{f64, 8}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! test_impl {
|
||||||
|
($fn_name:ident, $data:expr, $expected_len:literal, $expected_dump:expr) => {
|
||||||
|
#[test]
|
||||||
|
fn $fn_name() {
|
||||||
|
let data = $data;
|
||||||
|
let mut buffer = [0u8; 128];
|
||||||
|
let (ok, write_len) = data.dump(&mut buffer);
|
||||||
|
assert!(ok, "Dump not ok");
|
||||||
|
assert_eq!(write_len, $expected_len, "Wrong amount written");
|
||||||
|
assert_eq!(&buffer[..write_len], $expected_dump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_impl!{string_dump_test, "test".to_string(), 8, &[4, 0, 0, 0, 116, 101, 115, 116]}
|
||||||
|
|
||||||
|
test_impl!{
|
||||||
|
vec_dump_test,
|
||||||
|
vec![
|
||||||
|
"".to_string(),
|
||||||
|
"test1".to_string(),
|
||||||
|
"test2".to_string()
|
||||||
|
],
|
||||||
|
26,
|
||||||
|
&[3, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 5, 0, 0, 0, 116, 101, 115, 116, 50]
|
||||||
|
}
|
||||||
|
|
||||||
|
test_impl!{bool_true_dump_test, true, 1, &[1]}
|
||||||
|
test_impl!{bool_false_dump_test, false, 1, &[0]}
|
||||||
|
|
||||||
|
// testing macro-generated code isn't particularly useful, but do it anyway
|
||||||
|
|
||||||
|
test_impl!{u8_dump_test, 42u8, 1, &[42]}
|
||||||
|
test_impl!{u16_dump_test, 42u16, 2, &[42, 0]}
|
||||||
|
test_impl!{u32_dump_test, 42u32, 4, &[42, 0, 0, 0]}
|
||||||
|
test_impl!{u64_dump_test, 42u64, 8, &[42, 0, 0, 0, 0, 0, 0, 0]}
|
||||||
|
test_impl!{u128_dump_test, 42u128, 16, &[42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
|
||||||
|
|
||||||
|
test_impl!{i8_dump_test, 42i8, 1, &[42]}
|
||||||
|
test_impl!{i16_dump_test, 42i16, 2, &[42, 0]}
|
||||||
|
test_impl!{i32_dump_test, 42i32, 4, &[42, 0, 0, 0]}
|
||||||
|
test_impl!{i64_dump_test, 42i64, 8, &[42, 0, 0, 0, 0, 0, 0, 0]}
|
||||||
|
test_impl!{i128_dump_test, 42i128, 16, &[42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
|
||||||
|
|
||||||
|
test_impl!{f32_dump_test, 42f32, 4, &[0, 0, 40, 66]}
|
||||||
|
test_impl!{f64_dump_test, 42f64, 8, &[0, 0, 0, 0, 0, 0, 69, 64]}
|
||||||
|
}
|
143
usdpl-core/src/serdes/load_impl.rs
Normal file
143
usdpl-core/src/serdes/load_impl.rs
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
use super::Loadable;
|
||||||
|
|
||||||
|
impl Loadable for String {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buffer.len() < 4 {
|
||||||
|
return (None, 0);
|
||||||
|
}
|
||||||
|
let mut u32_bytes: [u8; 4] = [u8::MAX; 4];
|
||||||
|
u32_bytes.copy_from_slice(&buffer[..4]);
|
||||||
|
let str_size = u32::from_le_bytes(u32_bytes) as usize;
|
||||||
|
(Some(Self::from_utf8_lossy(&buffer[4..str_size + 4]).into_owned()), str_size + 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Loadable> Loadable for Vec<T> {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buffer.len() < 4 {
|
||||||
|
return (None, 0);
|
||||||
|
}
|
||||||
|
let mut u32_bytes: [u8; 4] = [u8::MAX; 4];
|
||||||
|
u32_bytes.copy_from_slice(&buffer[..4]);
|
||||||
|
let count = u32::from_le_bytes(u32_bytes) as usize;
|
||||||
|
let mut cursor = 4;
|
||||||
|
let mut items = Vec::with_capacity(count);
|
||||||
|
for _ in 0..count {
|
||||||
|
let (obj, len) = T::load(&buffer[cursor..]);
|
||||||
|
cursor += len;
|
||||||
|
if let Some(obj) = obj {
|
||||||
|
items.push(obj);
|
||||||
|
} else {
|
||||||
|
return (None, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(items), cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loadable for bool {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buffer.len() < 1 {
|
||||||
|
return (None, 0);
|
||||||
|
}
|
||||||
|
(Some(buffer[0] != 0), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loadable for u8 {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buffer.len() < 1 {
|
||||||
|
return (None, 0);
|
||||||
|
}
|
||||||
|
(Some(buffer[0]), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loadable for i8 {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buffer.len() < 1 {
|
||||||
|
return (None, 0);
|
||||||
|
}
|
||||||
|
(Some(i8::from_le_bytes([buffer[0]])), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! int_impl {
|
||||||
|
($type:ty, $size:literal) => {
|
||||||
|
impl Loadable for $type {
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buffer.len() < $size {
|
||||||
|
return (None, 0);
|
||||||
|
}
|
||||||
|
let mut bytes: [u8; $size] = [u8::MAX; $size];
|
||||||
|
bytes.copy_from_slice(&buffer[..$size]);
|
||||||
|
let i = <$type>::from_le_bytes(bytes);
|
||||||
|
(Some(i), $size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int_impl!{u16, 2}
|
||||||
|
int_impl!{u32, 4}
|
||||||
|
int_impl!{u64, 8}
|
||||||
|
int_impl!{u128, 16}
|
||||||
|
|
||||||
|
int_impl!{i16, 2}
|
||||||
|
int_impl!{i32, 4}
|
||||||
|
int_impl!{i64, 8}
|
||||||
|
int_impl!{i128, 16}
|
||||||
|
|
||||||
|
int_impl!{f32, 4}
|
||||||
|
int_impl!{f64, 8}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! test_impl {
|
||||||
|
($fn_name:ident, $data:expr, $type:ty, $expected_len:literal, $expected_load:expr) => {
|
||||||
|
#[test]
|
||||||
|
fn $fn_name() {
|
||||||
|
let buffer = $data;
|
||||||
|
let (obj, read_len) = <$type>::load(&buffer);
|
||||||
|
assert!(obj.is_some(), "Load not ok");
|
||||||
|
assert_eq!(read_len, $expected_len, "Wrong amount read");
|
||||||
|
assert_eq!(obj.unwrap(), $expected_load, "Loaded value not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_impl!{string_load_test, [4u8, 0, 0, 0, 116, 101, 115, 116, 0, 128], String, 8, "test"}
|
||||||
|
test_impl!{
|
||||||
|
vec_load_test,
|
||||||
|
[3u8, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 5, 0, 0, 0, 116, 101, 115, 116, 50],
|
||||||
|
Vec<String>,
|
||||||
|
26,
|
||||||
|
vec![
|
||||||
|
"".to_string(),
|
||||||
|
"test1".to_string(),
|
||||||
|
"test2".to_string()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
test_impl!{bool_true_load_test, [1], bool, 1, true}
|
||||||
|
test_impl!{bool_false_load_test, [0], bool, 1, false}
|
||||||
|
|
||||||
|
// testing macro-generated code isn't particularly useful, but do it anyway
|
||||||
|
|
||||||
|
test_impl!{u8_load_test, [42], u8, 1, 42u8}
|
||||||
|
test_impl!{u16_load_test, [42, 0], u16, 2, 42u16}
|
||||||
|
test_impl!{u32_load_test, [42, 0, 0, 0], u32, 4, 42u32}
|
||||||
|
test_impl!{u64_load_test, [42, 0, 0, 0, 0, 0, 0, 0], u64, 8, 42u64}
|
||||||
|
test_impl!{u128_load_test, [42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], u128, 16, 42u128}
|
||||||
|
|
||||||
|
test_impl!{i8_load_test, [42], i8, 1, 42i8}
|
||||||
|
test_impl!{i16_load_test, [42, 0], i16, 2, 42i16}
|
||||||
|
test_impl!{i32_load_test, [42, 0, 0, 0], i32, 4, 42i32}
|
||||||
|
test_impl!{i64_load_test, [42, 0, 0, 0, 0, 0, 0, 0], i64, 8, 42i64}
|
||||||
|
test_impl!{i128_load_test, [42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], i128, 16, 42i128}
|
||||||
|
|
||||||
|
test_impl!{f32_load_test, [0, 0, 40, 66], f32, 4, 42f32}
|
||||||
|
test_impl!{f64_load_test, [0, 0, 0, 0, 0, 0, 69, 64], f64, 8, 42f64}
|
||||||
|
}
|
10
usdpl-core/src/serdes/mod.rs
Normal file
10
usdpl-core/src/serdes/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//! Serialization and deserialization functionality.
|
||||||
|
//! Little endian is preferred.
|
||||||
|
|
||||||
|
mod dump_impl;
|
||||||
|
mod load_impl;
|
||||||
|
mod primitive;
|
||||||
|
mod traits;
|
||||||
|
|
||||||
|
pub use traits::{Dumpable, Loadable};
|
||||||
|
pub use primitive::Primitive;
|
156
usdpl-core/src/serdes/primitive.rs
Normal file
156
usdpl-core/src/serdes/primitive.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
use super::{Loadable, Dumpable};
|
||||||
|
|
||||||
|
pub enum Primitive {
|
||||||
|
Empty,
|
||||||
|
String(String),
|
||||||
|
F32(f32),
|
||||||
|
F64(f64),
|
||||||
|
U32(u32),
|
||||||
|
U64(u64),
|
||||||
|
I32(i32),
|
||||||
|
I64(i64),
|
||||||
|
Bool(bool),
|
||||||
|
Json(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Primitive {
|
||||||
|
const fn discriminant(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Empty => 1,
|
||||||
|
Self::String(_) => 2,
|
||||||
|
Self::F32(_)=> 3,
|
||||||
|
Self::F64(_)=> 4,
|
||||||
|
Self::U32(_)=> 5,
|
||||||
|
Self::U64(_)=> 6,
|
||||||
|
Self::I32(_)=> 7,
|
||||||
|
Self::I64(_)=> 8,
|
||||||
|
Self::Bool(_) => 9,
|
||||||
|
Self::Json(_) => 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loadable for Primitive {
|
||||||
|
fn load(buf: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buf.len() == 0 {
|
||||||
|
return (None, 1);
|
||||||
|
}
|
||||||
|
let mut result: (Option<Self>, usize) = match buf[0] {
|
||||||
|
//0 => (None, 0),
|
||||||
|
1 => (Some(Self::Empty), 0),
|
||||||
|
2 => {
|
||||||
|
let (obj, len) = String::load(&buf[1..]);
|
||||||
|
(obj.map(Self::String), len)
|
||||||
|
},
|
||||||
|
3 => {
|
||||||
|
let (obj, len) = f32::load(&buf[1..]);
|
||||||
|
(obj.map(Self::F32), len)
|
||||||
|
},
|
||||||
|
4 => {
|
||||||
|
let (obj, len) = f64::load(&buf[1..]);
|
||||||
|
(obj.map(Self::F64), len)
|
||||||
|
},
|
||||||
|
5 => {
|
||||||
|
let (obj, len) = u32::load(&buf[1..]);
|
||||||
|
(obj.map(Self::U32), len)
|
||||||
|
},
|
||||||
|
6 => {
|
||||||
|
let (obj, len) = u64::load(&buf[1..]);
|
||||||
|
(obj.map(Self::U64), len)
|
||||||
|
},
|
||||||
|
7 => {
|
||||||
|
let (obj, len) = i32::load(&buf[1..]);
|
||||||
|
(obj.map(Self::I32), len)
|
||||||
|
},
|
||||||
|
8 => {
|
||||||
|
let (obj, len) = i64::load(&buf[1..]);
|
||||||
|
(obj.map(Self::I64), len)
|
||||||
|
},
|
||||||
|
9 => {
|
||||||
|
let (obj, len) = bool::load(&buf[1..]);
|
||||||
|
(obj.map(Self::Bool), len)
|
||||||
|
},
|
||||||
|
10 => {
|
||||||
|
let (obj, len) = String::load(&buf[1..]);
|
||||||
|
(obj.map(Self::Json), len)
|
||||||
|
}
|
||||||
|
_ => (None, 0)
|
||||||
|
};
|
||||||
|
result.1 += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Dumpable for Primitive {
|
||||||
|
fn dump(&self, buf: &mut [u8]) -> (bool, usize) {
|
||||||
|
if buf.len() == 0 {
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
buf[0] = self.discriminant();
|
||||||
|
let mut result = match self {
|
||||||
|
Self::Empty => (true, 0),
|
||||||
|
Self::String(s) => s.dump(&mut buf[1..]),
|
||||||
|
Self::F32(x)=> x.dump(&mut buf[1..]),
|
||||||
|
Self::F64(x)=> x.dump(&mut buf[1..]),
|
||||||
|
Self::U32(x)=> x.dump(&mut buf[1..]),
|
||||||
|
Self::U64(x)=> x.dump(&mut buf[1..]),
|
||||||
|
Self::I32(x)=> x.dump(&mut buf[1..]),
|
||||||
|
Self::I64(x)=> x.dump(&mut buf[1..]),
|
||||||
|
Self::Bool(x)=> x.dump(&mut buf[1..]),
|
||||||
|
Self::Json(x) => x.dump(&mut buf[1..]),
|
||||||
|
};
|
||||||
|
result.1 += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::Into<Primitive> for String {
|
||||||
|
fn into(self) -> Primitive {
|
||||||
|
Primitive::String(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::Into<Primitive> for () {
|
||||||
|
fn into(self) -> Primitive {
|
||||||
|
Primitive::Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_idempotence_test() {
|
||||||
|
let data = "Test";
|
||||||
|
let primitive = Primitive::String(data.to_string());
|
||||||
|
let mut buffer = [0u8; 128];
|
||||||
|
let (ok, write_len) = primitive.dump(&mut buffer);
|
||||||
|
assert!(ok, "Dump not ok");
|
||||||
|
let (obj, read_len) = Primitive::load(&buffer);
|
||||||
|
assert_eq!(write_len, read_len, "Amount written and amount read do not match");
|
||||||
|
assert!(obj.is_some(), "Load not ok");
|
||||||
|
if let Some(Primitive::String(result)) = obj {
|
||||||
|
assert_eq!(data, result, "Data written and read does not match");
|
||||||
|
} else {
|
||||||
|
panic!("Read non-string primitive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_idempotence_test() {
|
||||||
|
let primitive = Primitive::Empty;
|
||||||
|
let mut buffer = [0u8; 128];
|
||||||
|
let (ok, write_len) = primitive.dump(&mut buffer);
|
||||||
|
assert!(ok, "Dump not ok");
|
||||||
|
let (obj, read_len) = Primitive::load(&buffer);
|
||||||
|
assert_eq!(write_len, read_len, "Amount written and amount read do not match");
|
||||||
|
assert!(obj.is_some(), "Load not ok");
|
||||||
|
if let Some(Primitive::Empty) = obj {
|
||||||
|
//assert_eq!(data, result, "Data written and read does not match");
|
||||||
|
} else {
|
||||||
|
panic!("Read non-string primitive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
usdpl-core/src/serdes/traits.rs
Normal file
13
usdpl-core/src/serdes/traits.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/// Load an object from the buffer
|
||||||
|
pub trait Loadable: Sized {
|
||||||
|
/// Read the buffer, building the object and returning the amount of bytes read.
|
||||||
|
/// If anything is wrong with the buffer, None should be returned.
|
||||||
|
fn load(buffer: &[u8]) -> (Option<Self>, usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dump an object into the buffer
|
||||||
|
pub trait Dumpable {
|
||||||
|
/// Write the object to the buffer, returning the amount of bytes written.
|
||||||
|
/// If anything is wrong, false should be returned.
|
||||||
|
fn dump(&self, buffer: &mut [u8]) -> (bool, usize);
|
||||||
|
}
|
91
usdpl-core/src/socket.rs
Normal file
91
usdpl-core/src/socket.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use std::net::{SocketAddrV4, SocketAddr, Ipv4Addr};
|
||||||
|
|
||||||
|
use crate::serdes::{Loadable, Dumpable};
|
||||||
|
use crate::{RemoteCall, RemoteCallResponse};
|
||||||
|
|
||||||
|
pub const PORT: u16 = 31337;
|
||||||
|
pub const HTTP_PORT: u16 = 31338;
|
||||||
|
pub const HOST_STR: &str = "127.0.0.1";
|
||||||
|
pub const HOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||||
|
pub const SOCKET_ADDR_STR: &str = "127.0.0.1:31337";
|
||||||
|
//pub const SOCKET_ADDR: SocketAddr = SocketAddr::V4(SocketAddrV4::new(HOST, PORT));
|
||||||
|
|
||||||
|
pub const PACKET_BUFFER_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
pub fn socket_addr() -> SocketAddr {
|
||||||
|
SocketAddr::V4(SocketAddrV4::new(HOST, PORT))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Packet {
|
||||||
|
Call(RemoteCall),
|
||||||
|
CallResponse(RemoteCallResponse),
|
||||||
|
KeepAlive,
|
||||||
|
Invalid,
|
||||||
|
Message(String),
|
||||||
|
Unsupported,
|
||||||
|
Bad,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Packet {
|
||||||
|
const fn discriminant(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Call(_) => 1,
|
||||||
|
Self::CallResponse(_) => 2,
|
||||||
|
Self::KeepAlive => 3,
|
||||||
|
Self::Invalid => 4,
|
||||||
|
Self::Message(_) => 5,
|
||||||
|
Self::Unsupported => 6,
|
||||||
|
Self::Bad => 7,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Loadable for Packet {
|
||||||
|
fn load(buf: &[u8]) -> (Option<Self>, usize) {
|
||||||
|
if buf.len() == 0 {
|
||||||
|
return (None, 1);
|
||||||
|
}
|
||||||
|
let mut result: (Option<Self>, usize) = match buf[0] {
|
||||||
|
//0 => (None, 0),
|
||||||
|
1 => {
|
||||||
|
let (obj, len) = RemoteCall::load(&buf[1..]);
|
||||||
|
(obj.map(Self::Call), len)
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
let (obj, len) = RemoteCallResponse::load(&buf[1..]);
|
||||||
|
(obj.map(Self::CallResponse), len)
|
||||||
|
},
|
||||||
|
3 => (Some(Self::KeepAlive), 0),
|
||||||
|
4 => (Some(Self::Invalid), 0),
|
||||||
|
5 => {
|
||||||
|
let (obj, len) = String::load(&buf[1..]);
|
||||||
|
(obj.map(Self::Message), len)
|
||||||
|
},
|
||||||
|
6 => (Some(Self::Unsupported), 0),
|
||||||
|
7 => (None, 0),
|
||||||
|
_ => (None, 0)
|
||||||
|
};
|
||||||
|
result.1 += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dumpable for Packet {
|
||||||
|
fn dump(&self, buf: &mut [u8]) -> (bool, usize) {
|
||||||
|
if buf.len() == 0 {
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
buf[0] = self.discriminant();
|
||||||
|
let mut result = match self {
|
||||||
|
Self::Call(c) => c.dump(&mut buf[1..]),
|
||||||
|
Self::CallResponse(c) => c.dump(&mut buf[1..]),
|
||||||
|
Self::KeepAlive => (true, 0),
|
||||||
|
Self::Invalid => (true, 0),
|
||||||
|
Self::Message(s) => s.dump(&mut buf[1..]),
|
||||||
|
Self::Unsupported => (true, 0),
|
||||||
|
Self::Bad => (false, 0),
|
||||||
|
};
|
||||||
|
result.1 += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
40
usdpl-front/Cargo.toml
Normal file
40
usdpl-front/Cargo.toml
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
[package]
|
||||||
|
name = "usdpl"
|
||||||
|
description = "WASM front-end library for USDPL"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = "0.2.63"
|
||||||
|
|
||||||
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
|
# code size when deploying.
|
||||||
|
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||||
|
|
||||||
|
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||||
|
# compared to the default allocator's ~10K. It is slower than the default
|
||||||
|
# allocator, however.
|
||||||
|
#
|
||||||
|
# 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"] }
|
||||||
|
js-sys = { version = "0.3" }
|
||||||
|
|
||||||
|
usdpl-core = { version = "0.1.0", path = "../usdpl-core" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.3.13"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
opt-level = "s"
|
3
usdpl-front/build.sh
Executable file
3
usdpl-front/build.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
wasm-pack build --target web
|
52
usdpl-front/src/connection.rs
Normal file
52
usdpl-front/src/connection.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use web_sys::TcpSocket;
|
||||||
|
use js_sys::{ArrayBuffer, DataView};
|
||||||
|
|
||||||
|
use usdpl_core::socket;
|
||||||
|
use usdpl_core::serdes::{Dumpable, Loadable};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn send(packet: socket::Packet) -> bool {
|
||||||
|
let socket = match TcpSocket::new(socket::HOST_STR, socket::PORT) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
|
||||||
|
let (ok, len) = packet.dump(&mut buffer);
|
||||||
|
if !ok {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// copy to JS buffer
|
||||||
|
let array_buffer = ArrayBuffer::new(len as u32);
|
||||||
|
let dataview = DataView::new(&array_buffer, 0, len);
|
||||||
|
for i in 0..len {
|
||||||
|
dataview.set_uint8(i, buffer[i]);
|
||||||
|
}
|
||||||
|
match socket.send_with_array_buffer(&array_buffer) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(_) => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send_native(packet: socket::Packet) -> Option<socket::Packet> {
|
||||||
|
let mut socket = match TcpStream::connect(socket::socket_addr()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
|
||||||
|
let (ok, len) = packet.dump(&mut buffer);
|
||||||
|
if !ok {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match socket.write(&buffer[..len]) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(_) => return None
|
||||||
|
}
|
||||||
|
let len = match socket.read(&mut buffer) {
|
||||||
|
Ok(len) => len,
|
||||||
|
Err(_) => return None
|
||||||
|
};
|
||||||
|
socket::Packet::load(&buffer[..len]).0
|
||||||
|
}
|
85
usdpl-front/src/lib.rs
Normal file
85
usdpl-front/src/lib.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
//! Front-end library to be called from Javascript.
|
||||||
|
//! Targets WASM.
|
||||||
|
//!
|
||||||
|
//! In true Javascript tradition, this part of the library does not support error handling.
|
||||||
|
//!
|
||||||
|
|
||||||
|
mod connection;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use js_sys::JSON::{stringify, parse};
|
||||||
|
|
||||||
|
use usdpl_core::{socket::Packet, RemoteCall, serdes::Primitive};
|
||||||
|
const REMOTE_CALL_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||||
|
|
||||||
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global allocator.
|
||||||
|
#[cfg(feature = "wee_alloc")]
|
||||||
|
#[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() -> bool {
|
||||||
|
#[cfg(feature = "console_error_panic_hook")]
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the targeted plugin framework, or "any" if unknown
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn target() -> String {
|
||||||
|
"any".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a function on the back-end.
|
||||||
|
/// Returns null (None) if this fails for any reason.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn call_backend(name: String, parameters: Vec<JsValue>) -> Option<Vec<JsValue>> {
|
||||||
|
let next_id = REMOTE_CALL_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let mut params = Vec::with_capacity(parameters.len());
|
||||||
|
for val in parameters {
|
||||||
|
if let Some(b) = val.as_bool() {
|
||||||
|
params.push(Primitive::Bool(b));
|
||||||
|
} else if let Some(f) = val.as_f64() {
|
||||||
|
params.push(Primitive::F64(f));
|
||||||
|
} else if let Some(s) = val.as_string() {
|
||||||
|
params.push(Primitive::String(s));
|
||||||
|
} else if val.is_null() || val.is_undefined() {
|
||||||
|
params.push(Primitive::Empty);
|
||||||
|
} else if let Ok(s) = stringify(&val) {
|
||||||
|
params.push(Primitive::Json(s.as_string().unwrap()));
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let results = match connection::send_native(Packet::Call(RemoteCall {
|
||||||
|
id: next_id,
|
||||||
|
function: name,
|
||||||
|
parameters: params,
|
||||||
|
})) {
|
||||||
|
Some(Packet::CallResponse(resp)) => resp,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let mut js_results = Vec::with_capacity(results.response.len());
|
||||||
|
for val in results.response {
|
||||||
|
let js_val = match val {
|
||||||
|
Primitive::Empty => JsValue::null(),
|
||||||
|
Primitive::String(s) => JsValue::from_str(&s),
|
||||||
|
Primitive::F32(f)=> JsValue::from_f64(f as _),
|
||||||
|
Primitive::F64(f)=> JsValue::from_f64(f),
|
||||||
|
Primitive::U32(f)=> JsValue::from_f64(f as _),
|
||||||
|
Primitive::U64(f)=> JsValue::from_f64(f as _),
|
||||||
|
Primitive::I32(f)=> JsValue::from_f64(f as _),
|
||||||
|
Primitive::I64(f)=> JsValue::from_f64(f as _),
|
||||||
|
Primitive::Bool(b) => JsValue::from_bool(b),
|
||||||
|
Primitive::Json(s) => parse(&s).ok().unwrap_or(JsValue::from_str(&s)),
|
||||||
|
};
|
||||||
|
js_results.push(js_val);
|
||||||
|
}
|
||||||
|
Some(js_results)
|
||||||
|
}
|
Loading…
Reference in a new issue