Remove old APIs, improve/fix front logging
This commit is contained in:
parent
b7b42a8c6d
commit
44298f660f
20 changed files with 115 additions and 1353 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -196,17 +196,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_log"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1565,7 +1554,6 @@ name = "usdpl-front"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"console_log",
|
|
||||||
"futures",
|
"futures",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"gloo-net",
|
"gloo-net",
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
name = "usdpl-back"
|
name = "usdpl-back"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
authors = ["NGnius <ngniusness@gmail.com>"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/NGnius/usdpl-rs"
|
repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
description = "Universal Steam Deck Plugin Library back-end"
|
description = "Universal Steam Deck Plugin Library back-end"
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ description = "Universal Steam Deck Plugin Library back-end"
|
||||||
default = ["blocking"]
|
default = ["blocking"]
|
||||||
decky = ["usdpl-core/decky"]
|
decky = ["usdpl-core/decky"]
|
||||||
blocking = [] # synchronous API for async functionality, using tokio
|
blocking = [] # synchronous API for async functionality, using tokio
|
||||||
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
#encrypt = ["usdpl-core", "obfstr", "hex"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
usdpl-core = { version = "0.11", path = "../usdpl-core"}
|
usdpl-core = { version = "0.11", path = "../usdpl-core"}
|
||||||
|
@ -27,7 +28,6 @@ prost = "0.11"
|
||||||
ratchet_rs = { version = "0.4", features = [ "deflate" ] }
|
ratchet_rs = { version = "0.4", features = [ "deflate" ] }
|
||||||
|
|
||||||
# HTTP web framework
|
# HTTP web framework
|
||||||
#warp = { version = "0.3" }
|
|
||||||
bytes = { version = "1.1" }
|
bytes = { version = "1.1" }
|
||||||
tokio = { version = "1", features = [ "full" ]}
|
tokio = { version = "1", features = [ "full" ]}
|
||||||
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
//! Common low-level file operations
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
/// Write something to a file.
|
|
||||||
/// Useful for kernel configuration files.
|
|
||||||
#[inline]
|
|
||||||
pub fn write_single<P: AsRef<Path>, D: Display>(path: P, display: D) -> Result<(), io::Error> {
|
|
||||||
let mut file = File::create(path)?;
|
|
||||||
write!(file, "{}", display)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// read_single error
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ReadError<E> {
|
|
||||||
/// IO Error
|
|
||||||
Io(io::Error),
|
|
||||||
/// String parsing error
|
|
||||||
Parse(E),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: std::error::Error> std::fmt::Display for ReadError<E> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Io(io) => write!(f, "io: {}", io),
|
|
||||||
Self::Parse(e) => write!(f, "parse: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: std::error::Error> std::error::Error for ReadError<E> {}
|
|
||||||
|
|
||||||
/// Read something from a file.
|
|
||||||
/// Useful for kernel configuration files.
|
|
||||||
#[inline]
|
|
||||||
pub fn read_single<P: AsRef<Path>, D: FromStr<Err = E>, E>(path: P) -> Result<D, ReadError<E>> {
|
|
||||||
let mut file = File::open(path).map_err(ReadError::Io)?;
|
|
||||||
let mut string = String::new();
|
|
||||||
file.read_to_string(&mut string).map_err(ReadError::Io)?;
|
|
||||||
string.trim().parse().map_err(ReadError::Parse)
|
|
||||||
}
|
|
|
@ -1,2 +1 @@
|
||||||
pub mod dirs;
|
pub mod dirs;
|
||||||
pub mod files;
|
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
name = "usdpl-build"
|
name = "usdpl-build"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
authors = ["NGnius <ngniusness@gmail.com>"]
|
||||||
|
license = "GPL-3.0-only"
|
||||||
|
repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs"
|
||||||
|
readme = "../README.md"
|
||||||
|
description = "Universal Steam Deck Plugin Library core"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
|
@ -1026,6 +1026,7 @@ impl IServiceGenerator for WasmServiceGenerator {
|
||||||
.expect("FileDescriptorSet required for WASM service generator");
|
.expect("FileDescriptorSet required for WASM service generator");
|
||||||
let service_struct_name = quote::format_ident!("{}Client", service.name);
|
let service_struct_name = quote::format_ident!("{}Client", service.name);
|
||||||
let service_js_name = quote::format_ident!("{}", service.name);
|
let service_js_name = quote::format_ident!("{}", service.name);
|
||||||
|
let service_str_name = service.name.clone();
|
||||||
let service_methods = generate_service_methods(&service, fds);
|
let service_methods = generate_service_methods(&service, fds);
|
||||||
let service_types = generate_service_io_types(&service, fds);
|
let service_types = generate_service_io_types(&service, fds);
|
||||||
let mod_name = quote::format_ident!("js_{}", service.name.to_lowercase());
|
let mod_name = quote::format_ident!("js_{}", service.name.to_lowercase());
|
||||||
|
@ -1059,9 +1060,11 @@ impl IServiceGenerator for WasmServiceGenerator {
|
||||||
impl #service_js_name {
|
impl #service_js_name {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(port: u16) -> Self {
|
pub fn new(port: u16) -> Self {
|
||||||
|
usdpl_front::init_usdpl();
|
||||||
let implementation = super::#service_struct_name::new(
|
let implementation = super::#service_struct_name::new(
|
||||||
WebSocketHandler::new(port)
|
WebSocketHandler::new(port)
|
||||||
);
|
);
|
||||||
|
log::info!("Initialized ws service {} on port {}", #service_str_name, port);
|
||||||
Self {
|
Self {
|
||||||
service: implementation,
|
service: implementation,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "usdpl-core"
|
name = "usdpl-core"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
authors = ["NGnius <ngniusness@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/NGnius/usdpl-rs"
|
repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
description = "Universal Steam Deck Plugin Library core"
|
description = "Universal Steam Deck Plugin Library core designed for all architectures"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -2,19 +2,12 @@
|
||||||
//! This contains serialization functionality and networking datatypes.
|
//! This contains serialization functionality and networking datatypes.
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
mod remote_call;
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "decky")))]
|
#[cfg(not(any(feature = "decky")))]
|
||||||
mod api_any;
|
mod api_any;
|
||||||
mod api_common;
|
mod api_common;
|
||||||
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
|
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
|
||||||
mod api_decky;
|
mod api_decky;
|
||||||
|
|
||||||
pub mod serdes;
|
|
||||||
pub mod socket;
|
|
||||||
|
|
||||||
pub use remote_call::{RemoteCall, RemoteCallResponse};
|
|
||||||
|
|
||||||
/// USDPL core API.
|
/// USDPL core API.
|
||||||
/// This contains functionality used in both the back-end and front-end.
|
/// This contains functionality used in both the back-end and front-end.
|
||||||
pub mod api {
|
pub mod api {
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
use std::io::{Read, Write};
|
|
||||||
|
|
||||||
use crate::serdes::{DumpError, Dumpable, LoadError, Loadable, Primitive};
|
|
||||||
|
|
||||||
/// Remote call packet representing a function to call on the back-end, sent from the front-end
|
|
||||||
pub struct RemoteCall {
|
|
||||||
/// The call id assigned by the front-end
|
|
||||||
pub id: u64,
|
|
||||||
/// The function's name
|
|
||||||
pub function: String,
|
|
||||||
/// The function's input parameters
|
|
||||||
pub parameters: Vec<Primitive>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loadable for RemoteCall {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let (id_num, len0) = u64::load(buffer)?;
|
|
||||||
let (function_name, len1) = String::load(buffer)?;
|
|
||||||
let (params, len2) = Vec::<Primitive>::load(buffer)?;
|
|
||||||
Ok((
|
|
||||||
Self {
|
|
||||||
id: id_num,
|
|
||||||
function: function_name,
|
|
||||||
parameters: params,
|
|
||||||
},
|
|
||||||
len0 + len1 + len2,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dumpable for RemoteCall {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
let len0 = self.id.dump(buffer)?;
|
|
||||||
let len1 = self.function.dump(buffer)?;
|
|
||||||
let len2 = self.parameters.dump(buffer)?;
|
|
||||||
Ok(len0 + len1 + len2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remote call response packet representing the response from a remote call after the back-end has executed it.
|
|
||||||
pub struct RemoteCallResponse {
|
|
||||||
/// The call id from the RemoteCall
|
|
||||||
pub id: u64,
|
|
||||||
/// The function's result
|
|
||||||
pub response: Vec<Primitive>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loadable for RemoteCallResponse {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let (id_num, len0) = u64::load(buffer)?;
|
|
||||||
let (response_var, len1) = Vec::<Primitive>::load(buffer)?;
|
|
||||||
Ok((
|
|
||||||
Self {
|
|
||||||
id: id_num,
|
|
||||||
response: response_var,
|
|
||||||
},
|
|
||||||
len0 + len1,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dumpable for RemoteCallResponse {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
let len0 = self.id.dump(buffer)?;
|
|
||||||
let len1 = self.response.dump(buffer)?;
|
|
||||||
Ok(len0 + len1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn remote_call_idempotence_test() {
|
|
||||||
let call = RemoteCall {
|
|
||||||
id: 42,
|
|
||||||
function: "something very long just in case this causes unexpected issues".into(),
|
|
||||||
parameters: vec!["param1".into(), 42f64.into()],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buffer = String::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
|
||||||
let len = call.dump_base64(&mut buffer).unwrap();
|
|
||||||
|
|
||||||
println!("base64 dumped: `{}` (len: {})", buffer, len);
|
|
||||||
|
|
||||||
let (loaded_call, loaded_len) = RemoteCall::load_base64(buffer.as_bytes()).unwrap();
|
|
||||||
assert_eq!(len, loaded_len, "Expected load and dump lengths to match");
|
|
||||||
|
|
||||||
assert_eq!(loaded_call.id, call.id, "RemoteCall.id does not match");
|
|
||||||
assert_eq!(
|
|
||||||
loaded_call.function, call.function,
|
|
||||||
"RemoteCall.function does not match"
|
|
||||||
);
|
|
||||||
if let Primitive::String(loaded) = &loaded_call.parameters[0] {
|
|
||||||
if let Primitive::String(original) = &call.parameters[0] {
|
|
||||||
assert_eq!(loaded, original, "RemoteCall.parameters[0] does not match");
|
|
||||||
} else {
|
|
||||||
panic!("Original call parameter 0 is not String")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Loaded call parameter 0 is not String")
|
|
||||||
}
|
|
||||||
if let Primitive::F64(loaded) = &loaded_call.parameters[1] {
|
|
||||||
if let Primitive::F64(original) = &call.parameters[1] {
|
|
||||||
assert_eq!(loaded, original, "RemoteCall.parameters[1] does not match");
|
|
||||||
} else {
|
|
||||||
panic!("Original call parameter 1 is not f64")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Loaded call parameter 1 is not f64")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use super::{DumpError, Dumpable};
|
|
||||||
|
|
||||||
impl Dumpable for String {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
let str_bytes = self.as_bytes();
|
|
||||||
let len_bytes = (str_bytes.len() as u32).to_le_bytes();
|
|
||||||
let size1 = buffer.write(&len_bytes).map_err(DumpError::Io)?;
|
|
||||||
let size2 = buffer.write(&str_bytes).map_err(DumpError::Io)?;
|
|
||||||
Ok(size1 + size2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Dumpable> Dumpable for Vec<T> {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
let len_bytes = (self.len() as u32).to_le_bytes();
|
|
||||||
let mut total = buffer.write(&len_bytes).map_err(DumpError::Io)?;
|
|
||||||
for obj in self.iter() {
|
|
||||||
let len = obj.dump(buffer)?;
|
|
||||||
total += len;
|
|
||||||
}
|
|
||||||
Ok(total)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Dumpable, T1: Dumpable> Dumpable for (T0, T1) {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
Ok(self.0.dump(buffer)? + self.1.dump(buffer)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable> Dumpable for (T0, T1, T2) {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
Ok(self.0.dump(buffer)? + self.1.dump(buffer)? + self.2.dump(buffer)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable> Dumpable for (T0, T1, T2, T3) {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
Ok(self.0.dump(buffer)?
|
|
||||||
+ self.1.dump(buffer)?
|
|
||||||
+ self.2.dump(buffer)?
|
|
||||||
+ self.3.dump(buffer)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable, T4: Dumpable> Dumpable
|
|
||||||
for (T0, T1, T2, T3, T4)
|
|
||||||
{
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
Ok(self.0.dump(buffer)?
|
|
||||||
+ self.1.dump(buffer)?
|
|
||||||
+ self.2.dump(buffer)?
|
|
||||||
+ self.3.dump(buffer)?
|
|
||||||
+ self.4.dump(buffer)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dumpable for bool {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
buffer.write(&[*self as u8]).map_err(DumpError::Io)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dumpable for u8 {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
buffer.write(&[*self]).map_err(DumpError::Io)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*impl Dumpable for i8 {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
buffer.write(&self.to_le_bytes()).map_err(DumpError::Io)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
macro_rules! int_impl {
|
|
||||||
($type:ty) => {
|
|
||||||
impl Dumpable for $type {
|
|
||||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
buffer.write(&self.to_le_bytes()).map_err(DumpError::Io)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int_impl! {u16}
|
|
||||||
int_impl! {u32}
|
|
||||||
int_impl! {u64}
|
|
||||||
int_impl! {u128}
|
|
||||||
|
|
||||||
int_impl! {i8}
|
|
||||||
int_impl! {i16}
|
|
||||||
int_impl! {i32}
|
|
||||||
int_impl! {i64}
|
|
||||||
int_impl! {i128}
|
|
||||||
|
|
||||||
int_impl! {f32}
|
|
||||||
int_impl! {f64}
|
|
||||||
|
|
||||||
#[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 = Vec::with_capacity(128);
|
|
||||||
let write_len = data.dump(&mut buffer).expect("Dump not ok");
|
|
||||||
assert_eq!(write_len, $expected_len, "Wrong amount written");
|
|
||||||
assert_eq!(&buffer[..write_len], $expected_dump);
|
|
||||||
println!("Dumped {:?}", buffer.as_slice());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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! {tuple2_dump_test, (0u8, 1u8), 2, &[0, 1]}
|
|
||||||
test_impl! {tuple3_dump_test, (0u8, 1u8, 2u8), 3, &[0, 1, 2]}
|
|
||||||
test_impl! {tuple4_dump_test, (0u8, 1u8, 2u8, 3u8), 4, &[0, 1, 2, 3]}
|
|
||||||
test_impl! {tuple5_dump_test, (0u8, 1u8, 2u8, 3u8, 4u8), 5, &[0, 1, 2, 3, 4]}
|
|
||||||
|
|
||||||
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]}
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
use super::{LoadError, Loadable};
|
|
||||||
|
|
||||||
impl Loadable for String {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut u32_bytes: [u8; 4] = [u8::MAX; 4];
|
|
||||||
buffer.read_exact(&mut u32_bytes).map_err(LoadError::Io)?;
|
|
||||||
let str_size = u32::from_le_bytes(u32_bytes) as usize;
|
|
||||||
//let mut str_buf = String::with_capacity(str_size);
|
|
||||||
let mut str_buf = Vec::with_capacity(str_size);
|
|
||||||
let mut byte_buf = [u8::MAX; 1];
|
|
||||||
for _ in 0..str_size {
|
|
||||||
buffer.read_exact(&mut byte_buf).map_err(LoadError::Io)?;
|
|
||||||
str_buf.push(byte_buf[0]);
|
|
||||||
}
|
|
||||||
//let size2 = buffer.read_to_string(&mut str_buf).map_err(LoadError::Io)?;
|
|
||||||
Ok((
|
|
||||||
String::from_utf8(str_buf).map_err(|_| LoadError::InvalidData)?,
|
|
||||||
str_size + 4,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Loadable> Loadable for Vec<T> {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut u32_bytes: [u8; 4] = [u8::MAX; 4];
|
|
||||||
buffer.read_exact(&mut u32_bytes).map_err(LoadError::Io)?;
|
|
||||||
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 += len;
|
|
||||||
items.push(obj);
|
|
||||||
}
|
|
||||||
Ok((items, cursor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Loadable, T1: Loadable> Loadable for (T0, T1) {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let (t0, len0) = T0::load(buffer)?;
|
|
||||||
let (t1, len1) = T1::load(buffer)?;
|
|
||||||
Ok(((t0, t1), len0 + len1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Loadable, T1: Loadable, T2: Loadable> Loadable for (T0, T1, T2) {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let (t0, len0) = T0::load(buffer)?;
|
|
||||||
let (t1, len1) = T1::load(buffer)?;
|
|
||||||
let (t2, len2) = T2::load(buffer)?;
|
|
||||||
Ok(((t0, t1, t2), len0 + len1 + len2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable> Loadable for (T0, T1, T2, T3) {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let (t0, len0) = T0::load(buffer)?;
|
|
||||||
let (t1, len1) = T1::load(buffer)?;
|
|
||||||
let (t2, len2) = T2::load(buffer)?;
|
|
||||||
let (t3, len3) = T3::load(buffer)?;
|
|
||||||
Ok(((t0, t1, t2, t3), len0 + len1 + len2 + len3))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable, T4: Loadable> Loadable
|
|
||||||
for (T0, T1, T2, T3, T4)
|
|
||||||
{
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let (t0, len0) = T0::load(buffer)?;
|
|
||||||
let (t1, len1) = T1::load(buffer)?;
|
|
||||||
let (t2, len2) = T2::load(buffer)?;
|
|
||||||
let (t3, len3) = T3::load(buffer)?;
|
|
||||||
let (t4, len4) = T4::load(buffer)?;
|
|
||||||
Ok(((t0, t1, t2, t3, t4), len0 + len1 + len2 + len3 + len4))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loadable for bool {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut byte = [u8::MAX; 1];
|
|
||||||
buffer.read_exact(&mut byte).map_err(LoadError::Io)?;
|
|
||||||
Ok((byte[0] != 0, 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loadable for u8 {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut byte = [u8::MAX; 1];
|
|
||||||
buffer.read_exact(&mut byte).map_err(LoadError::Io)?;
|
|
||||||
Ok((byte[0], 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loadable for i8 {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut byte = [u8::MAX; 1];
|
|
||||||
buffer.read_exact(&mut byte).map_err(LoadError::Io)?;
|
|
||||||
Ok((i8::from_le_bytes(byte), 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! int_impl {
|
|
||||||
($type:ty, $size:literal) => {
|
|
||||||
impl Loadable for $type {
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut bytes: [u8; $size] = [u8::MAX; $size];
|
|
||||||
buffer.read_exact(&mut bytes).map_err(LoadError::Io)?;
|
|
||||||
let i = <$type>::from_le_bytes(bytes);
|
|
||||||
Ok((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::*;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
macro_rules! test_impl {
|
|
||||||
($fn_name:ident, $data:expr, $type:ty, $expected_len:literal, $expected_load:expr) => {
|
|
||||||
#[test]
|
|
||||||
fn $fn_name() {
|
|
||||||
let buffer_data = $data;
|
|
||||||
let mut buffer = Vec::with_capacity(buffer_data.len());
|
|
||||||
buffer.extend_from_slice(&buffer_data);
|
|
||||||
let (obj, read_len) = <$type>::load(&mut Cursor::new(buffer)).expect("Load not ok");
|
|
||||||
assert_eq!(read_len, $expected_len, "Wrong amount read");
|
|
||||||
assert_eq!(obj, $expected_load, "Loaded value not as expected");
|
|
||||||
println!("Loaded {:?}", obj);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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! {tuple2_load_test, [0, 1], (u8, u8), 2, (0, 1)}
|
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
//! Serialization and deserialization functionality.
|
|
||||||
//! Little endian is preferred.
|
|
||||||
|
|
||||||
mod dump_impl;
|
|
||||||
mod load_impl;
|
|
||||||
mod primitive;
|
|
||||||
mod traits;
|
|
||||||
|
|
||||||
pub use primitive::Primitive;
|
|
||||||
pub use traits::{DumpError, Dumpable, LoadError, Loadable};
|
|
|
@ -1,163 +0,0 @@
|
||||||
use super::{DumpError, Dumpable, LoadError, Loadable};
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
|
|
||||||
/// Primitive types supported for communication between the USDPL back- and front-end.
|
|
||||||
/// These are used for sending over the TCP connection.
|
|
||||||
pub enum Primitive {
|
|
||||||
/// Null or unsupported object
|
|
||||||
Empty,
|
|
||||||
/// String-like
|
|
||||||
String(String),
|
|
||||||
/// f32
|
|
||||||
F32(f32),
|
|
||||||
/// f64
|
|
||||||
F64(f64),
|
|
||||||
/// u32
|
|
||||||
U32(u32),
|
|
||||||
/// u64
|
|
||||||
U64(u64),
|
|
||||||
/// i32
|
|
||||||
I32(i32),
|
|
||||||
/// i64
|
|
||||||
I64(i64),
|
|
||||||
/// boolean
|
|
||||||
Bool(bool),
|
|
||||||
/// Non-primitive in Json format
|
|
||||||
Json(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Primitive {
|
|
||||||
/// Discriminant -- first byte of a dumped 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: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut discriminant_buf = [u8::MAX; 1];
|
|
||||||
buf.read_exact(&mut discriminant_buf)
|
|
||||||
.map_err(LoadError::Io)?;
|
|
||||||
let mut result: (Self, usize) = match discriminant_buf[0] {
|
|
||||||
//0 => (None, 0),
|
|
||||||
1 => (Self::Empty, 0),
|
|
||||||
2 => String::load(buf).map(|(obj, len)| (Self::String(obj), len))?,
|
|
||||||
3 => f32::load(buf).map(|(obj, len)| (Self::F32(obj), len))?,
|
|
||||||
4 => f64::load(buf).map(|(obj, len)| (Self::F64(obj), len))?,
|
|
||||||
5 => u32::load(buf).map(|(obj, len)| (Self::U32(obj), len))?,
|
|
||||||
6 => u64::load(buf).map(|(obj, len)| (Self::U64(obj), len))?,
|
|
||||||
7 => i32::load(buf).map(|(obj, len)| (Self::I32(obj), len))?,
|
|
||||||
8 => i64::load(buf).map(|(obj, len)| (Self::I64(obj), len))?,
|
|
||||||
9 => bool::load(buf).map(|(obj, len)| (Self::Bool(obj), len))?,
|
|
||||||
10 => String::load(buf).map(|(obj, len)| (Self::Json(obj), len))?,
|
|
||||||
_ => return Err(LoadError::InvalidData),
|
|
||||||
};
|
|
||||||
result.1 += 1;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dumpable for Primitive {
|
|
||||||
fn dump(&self, buf: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
let size1 = buf.write(&[self.discriminant()]).map_err(DumpError::Io)?;
|
|
||||||
let result = match self {
|
|
||||||
Self::Empty => Ok(0),
|
|
||||||
Self::String(s) => s.dump(buf),
|
|
||||||
Self::F32(x) => x.dump(buf),
|
|
||||||
Self::F64(x) => x.dump(buf),
|
|
||||||
Self::U32(x) => x.dump(buf),
|
|
||||||
Self::U64(x) => x.dump(buf),
|
|
||||||
Self::I32(x) => x.dump(buf),
|
|
||||||
Self::I64(x) => x.dump(buf),
|
|
||||||
Self::Bool(x) => x.dump(buf),
|
|
||||||
Self::Json(x) => x.dump(buf),
|
|
||||||
}?;
|
|
||||||
Ok(size1 + result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::convert::Into<Primitive> for &str {
|
|
||||||
fn into(self) -> Primitive {
|
|
||||||
Primitive::String(self.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::convert::Into<Primitive> for () {
|
|
||||||
fn into(self) -> Primitive {
|
|
||||||
Primitive::Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! into_impl {
|
|
||||||
($type:ty, $variant:ident) => {
|
|
||||||
impl std::convert::Into<Primitive> for $type {
|
|
||||||
fn into(self) -> Primitive {
|
|
||||||
Primitive::$variant(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
into_impl! {String, String}
|
|
||||||
into_impl! {bool, Bool}
|
|
||||||
|
|
||||||
into_impl! {u32, U32}
|
|
||||||
into_impl! {u64, U64}
|
|
||||||
|
|
||||||
into_impl! {i32, I32}
|
|
||||||
into_impl! {i64, I64}
|
|
||||||
|
|
||||||
into_impl! {f32, F32}
|
|
||||||
into_impl! {f64, F64}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_idempotence_test() {
|
|
||||||
let data = "Test";
|
|
||||||
let primitive = Primitive::String(data.to_string());
|
|
||||||
let mut buffer = Vec::with_capacity(128);
|
|
||||||
let write_len = primitive.dump(&mut buffer).expect("Dump not ok");
|
|
||||||
let (obj, read_len) = Primitive::load(&mut Cursor::new(buffer)).expect("Load not ok");
|
|
||||||
assert_eq!(
|
|
||||||
write_len, read_len,
|
|
||||||
"Amount written and amount read do not match"
|
|
||||||
);
|
|
||||||
if let 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 = Vec::with_capacity(128);
|
|
||||||
let write_len = primitive.dump(&mut buffer).expect("Dump not ok");
|
|
||||||
let (obj, read_len) = Primitive::load(&mut Cursor::new(buffer)).expect("Load not ok");
|
|
||||||
assert_eq!(
|
|
||||||
write_len, read_len,
|
|
||||||
"Amount written and amount read do not match"
|
|
||||||
);
|
|
||||||
if let Primitive::Empty = obj {
|
|
||||||
//assert_eq!(data, result, "Data written and read does not match");
|
|
||||||
} else {
|
|
||||||
panic!("Read non-string primitive");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
use base64::{decode_config_buf, encode_config_buf, Config};
|
|
||||||
use std::io::{Cursor, Read, Write};
|
|
||||||
|
|
||||||
const B64_CONF: Config = Config::new(base64::CharacterSet::Standard, true);
|
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
const ASSOCIATED_DATA: &[u8] = b"usdpl-core-data";
|
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
use aes_gcm_siv::aead::{AeadInPlace, NewAead};
|
|
||||||
|
|
||||||
/// Errors from Loadable::load
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum LoadError {
|
|
||||||
/// Buffer smaller than expected
|
|
||||||
TooSmallBuffer,
|
|
||||||
/// Unexpected/corrupted data encountered
|
|
||||||
InvalidData,
|
|
||||||
/// Encrypted data cannot be decrypted
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
DecryptionError,
|
|
||||||
/// Read error
|
|
||||||
Io(std::io::Error),
|
|
||||||
/// Unimplemented
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
Todo,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for LoadError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::TooSmallBuffer => write!(f, "LoadError: TooSmallBuffer"),
|
|
||||||
Self::InvalidData => write!(f, "LoadError: InvalidData"),
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
Self::DecryptionError => write!(f, "LoadError: DecryptionError"),
|
|
||||||
Self::Io(err) => write!(f, "LoadError: Io({})", err),
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
Self::Todo => write!(f, "LoadError: TODO!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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, Err should be returned.
|
|
||||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError>;
|
|
||||||
|
|
||||||
/// Load data from a base64-encoded buffer
|
|
||||||
fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
|
||||||
decode_config_buf(buffer, B64_CONF, &mut buffer2).map_err(|_| LoadError::InvalidData)?;
|
|
||||||
let mut cursor = Cursor::new(buffer2);
|
|
||||||
Self::load(&mut cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load data from an encrypted base64-encoded buffer
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
fn load_encrypted(buffer: &[u8], key: &[u8], nonce: &[u8]) -> Result<(Self, usize), LoadError> {
|
|
||||||
//println!("encrypted buffer: {}", String::from_utf8(buffer.to_vec()).unwrap());
|
|
||||||
let key = aes_gcm_siv::Key::from_slice(key);
|
|
||||||
let cipher = aes_gcm_siv::Aes256GcmSiv::new(key);
|
|
||||||
let nonce = aes_gcm_siv::Nonce::from_slice(nonce);
|
|
||||||
let mut decoded_buf = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
|
||||||
base64::decode_config_buf(buffer, B64_CONF, &mut decoded_buf)
|
|
||||||
.map_err(|_| LoadError::InvalidData)?;
|
|
||||||
//println!("Decoded buf: {:?}", decoded_buf);
|
|
||||||
cipher
|
|
||||||
.decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf)
|
|
||||||
.map_err(|_| LoadError::DecryptionError)?;
|
|
||||||
//println!("Decrypted buf: {:?}", decoded_buf);
|
|
||||||
let mut cursor = Cursor::new(decoded_buf);
|
|
||||||
Self::load(&mut cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors from Dumpable::dump
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum DumpError {
|
|
||||||
/// Buffer not big enough to dump data into
|
|
||||||
TooSmallBuffer,
|
|
||||||
/// Data cannot be dumped
|
|
||||||
Unsupported,
|
|
||||||
/// Data cannot be encrypted
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
EncryptionError,
|
|
||||||
/// Write error
|
|
||||||
Io(std::io::Error),
|
|
||||||
/// Unimplemented
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
Todo,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for DumpError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::TooSmallBuffer => write!(f, "DumpError: TooSmallBuffer"),
|
|
||||||
Self::Unsupported => write!(f, "DumpError: Unsupported"),
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
Self::EncryptionError => write!(f, "DumpError: EncryptionError"),
|
|
||||||
Self::Io(err) => write!(f, "DumpError: Io({})", err),
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
Self::Todo => write!(f, "DumpError: TODO!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 dyn Write) -> Result<usize, DumpError>;
|
|
||||||
|
|
||||||
/// Dump data as base64-encoded.
|
|
||||||
/// Useful for transmitting data as text.
|
|
||||||
fn dump_base64(&self, buffer: &mut String) -> Result<usize, DumpError> {
|
|
||||||
let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
|
||||||
let len = self.dump(&mut buffer2)?;
|
|
||||||
encode_config_buf(&buffer2[..len], B64_CONF, buffer);
|
|
||||||
Ok(len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump data as an encrypted base64-encoded buffer
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
fn dump_encrypted(
|
|
||||||
&self,
|
|
||||||
buffer: &mut Vec<u8>,
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
) -> Result<usize, DumpError> {
|
|
||||||
let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
|
||||||
let size = self.dump(&mut buffer2)?;
|
|
||||||
buffer2.truncate(size);
|
|
||||||
//println!("Buf: {:?}", buffer2);
|
|
||||||
let key = aes_gcm_siv::Key::from_slice(key);
|
|
||||||
let cipher = aes_gcm_siv::Aes256GcmSiv::new(key);
|
|
||||||
let nonce = aes_gcm_siv::Nonce::from_slice(nonce);
|
|
||||||
cipher
|
|
||||||
.encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2)
|
|
||||||
.map_err(|_| DumpError::EncryptionError)?;
|
|
||||||
//println!("Encrypted slice: {:?}", &buffer2);
|
|
||||||
let mut base64_buf = String::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
|
||||||
encode_config_buf(buffer2.as_slice(), B64_CONF, &mut base64_buf);
|
|
||||||
//println!("base64 len: {}", base64_buf.as_bytes().len());
|
|
||||||
buffer.extend_from_slice(base64_buf.as_bytes());
|
|
||||||
//let string = String::from_utf8(buffer.as_slice().to_vec()).unwrap();
|
|
||||||
//println!("Encoded slice: {}", string);
|
|
||||||
Ok(base64_buf.len())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
//! Web messaging
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
|
||||||
|
|
||||||
use crate::serdes::{DumpError, Dumpable, LoadError, Loadable};
|
|
||||||
use crate::{RemoteCall, RemoteCallResponse};
|
|
||||||
|
|
||||||
/// Host IP address for web browsers
|
|
||||||
pub const HOST_STR: &str = "localhost";
|
|
||||||
/// Host IP address
|
|
||||||
pub const HOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
|
||||||
|
|
||||||
/// Standard max packet size
|
|
||||||
pub const PACKET_BUFFER_SIZE: usize = 1024;
|
|
||||||
/// Encryption nonce size
|
|
||||||
pub const NONCE_SIZE: usize = 12;
|
|
||||||
|
|
||||||
/// Address and port
|
|
||||||
#[inline]
|
|
||||||
pub fn socket_addr(port: u16) -> SocketAddr {
|
|
||||||
SocketAddr::V4(SocketAddrV4::new(HOST, port))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Accepted Packet types and the data they contain
|
|
||||||
pub enum Packet {
|
|
||||||
/// A remote call
|
|
||||||
Call(RemoteCall),
|
|
||||||
/// A reponse to a remote call
|
|
||||||
CallResponse(RemoteCallResponse),
|
|
||||||
/// Unused
|
|
||||||
KeepAlive,
|
|
||||||
/// Invalid
|
|
||||||
Invalid,
|
|
||||||
/// General message
|
|
||||||
Message(String),
|
|
||||||
/// Response to an unsupported packet
|
|
||||||
Unsupported,
|
|
||||||
/// Broken packet type, useful for testing
|
|
||||||
Bad,
|
|
||||||
/// Many packets merged into one
|
|
||||||
Many(Vec<Packet>),
|
|
||||||
/// Translation data dump
|
|
||||||
Translations(Vec<(String, Vec<String>)>),
|
|
||||||
/// Request translations for language
|
|
||||||
Language(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Packet {
|
|
||||||
/// Byte representing the packet type -- the first byte of any packet in USDPL
|
|
||||||
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,
|
|
||||||
Self::Many(_) => 8,
|
|
||||||
Self::Translations(_) => 9,
|
|
||||||
Self::Language(_) => 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loadable for Packet {
|
|
||||||
fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
|
||||||
let mut discriminant_buf = [u8::MAX; 1];
|
|
||||||
buf.read_exact(&mut discriminant_buf)
|
|
||||||
.map_err(LoadError::Io)?;
|
|
||||||
let mut result: (Self, usize) = match discriminant_buf[0] {
|
|
||||||
//0 => (None, 0),
|
|
||||||
1 => {
|
|
||||||
let (obj, len) = RemoteCall::load(buf)?;
|
|
||||||
(Self::Call(obj), len)
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
let (obj, len) = RemoteCallResponse::load(buf)?;
|
|
||||||
(Self::CallResponse(obj), len)
|
|
||||||
}
|
|
||||||
3 => (Self::KeepAlive, 0),
|
|
||||||
4 => (Self::Invalid, 0),
|
|
||||||
5 => {
|
|
||||||
let (obj, len) = String::load(buf)?;
|
|
||||||
(Self::Message(obj), len)
|
|
||||||
}
|
|
||||||
6 => (Self::Unsupported, 0),
|
|
||||||
7 => return Err(LoadError::InvalidData),
|
|
||||||
8 => {
|
|
||||||
let (obj, len) = <_>::load(buf)?;
|
|
||||||
(Self::Many(obj), len)
|
|
||||||
}
|
|
||||||
9 => {
|
|
||||||
let (obj, len) = <_>::load(buf)?;
|
|
||||||
(Self::Translations(obj), len)
|
|
||||||
}
|
|
||||||
10 => {
|
|
||||||
let (obj, len) = <_>::load(buf)?;
|
|
||||||
(Self::Language(obj), len)
|
|
||||||
}
|
|
||||||
_ => return Err(LoadError::InvalidData),
|
|
||||||
};
|
|
||||||
result.1 += 1;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dumpable for Packet {
|
|
||||||
fn dump(&self, buf: &mut dyn Write) -> Result<usize, DumpError> {
|
|
||||||
let size1 = buf.write(&[self.discriminant()]).map_err(DumpError::Io)?;
|
|
||||||
let result = match self {
|
|
||||||
Self::Call(c) => c.dump(buf),
|
|
||||||
Self::CallResponse(c) => c.dump(buf),
|
|
||||||
Self::KeepAlive => Ok(0),
|
|
||||||
Self::Invalid => Ok(0),
|
|
||||||
Self::Message(s) => s.dump(buf),
|
|
||||||
Self::Unsupported => Ok(0),
|
|
||||||
Self::Bad => return Err(DumpError::Unsupported),
|
|
||||||
Self::Many(v) => v.dump(buf),
|
|
||||||
Self::Translations(tr) => tr.dump(buf),
|
|
||||||
Self::Language(l) => l.dump(buf),
|
|
||||||
}?;
|
|
||||||
Ok(size1 + result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
#[test]
|
|
||||||
fn encryption_integration_test() {
|
|
||||||
let key =
|
|
||||||
hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265");
|
|
||||||
let nonce = [0u8; NONCE_SIZE];
|
|
||||||
let packet = Packet::Call(RemoteCall {
|
|
||||||
id: 42,
|
|
||||||
function: "test".into(),
|
|
||||||
parameters: Vec::new(),
|
|
||||||
});
|
|
||||||
let mut buffer = Vec::with_capacity(PACKET_BUFFER_SIZE);
|
|
||||||
let len = packet.dump_encrypted(&mut buffer, &key, &nonce).unwrap();
|
|
||||||
println!(
|
|
||||||
"buffer: {}",
|
|
||||||
String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let (packet_out, _len) =
|
|
||||||
Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap();
|
|
||||||
|
|
||||||
if let Packet::Call(call_out) = packet_out {
|
|
||||||
if let Packet::Call(call_in) = packet {
|
|
||||||
assert_eq!(
|
|
||||||
call_in.id, call_out.id,
|
|
||||||
"Input and output packets do not match"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
call_in.function, call_out.function,
|
|
||||||
"Input and output packets do not match"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
call_in.parameters.len(),
|
|
||||||
call_out.parameters.len(),
|
|
||||||
"Input and output packets do not match"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
panic!("Packet in not a Call");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Packet out not a Call!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "usdpl-front"
|
name = "usdpl-front"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
authors = ["NGnius <ngniusness@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/NGnius/usdpl-rs"
|
repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
description = "Universal Steam Deck Plugin Library front-end designed for WASM"
|
description = "Universal Steam Deck Plugin Library front-end designed for WASM"
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ crate-type = ["cdylib", "rlib"]
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
decky = ["usdpl-core/decky"]
|
decky = ["usdpl-core/decky"]
|
||||||
debug = ["console_error_panic_hook", "console_log"]
|
debug = ["console_error_panic_hook"]
|
||||||
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
#encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
|
@ -23,7 +23,6 @@ wasm-bindgen-futures = "0.4"
|
||||||
gloo-net = { version = "0.4", features = ["websocket"] }
|
gloo-net = { version = "0.4", features = ["websocket"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-channel = "0.3"
|
futures-channel = "0.3"
|
||||||
console_log = { version = "1.0", optional = true, features = ["color"] }
|
|
||||||
|
|
||||||
# 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,23 +32,19 @@ async fn send_recv_ws<'a>(mut tx: futures_channel::mpsc::Sender<Result<bytes::By
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("ws opened successfully with url `{}`", url);
|
||||||
web_sys::console::log_1(&format!("ws opened successfully with url `{}`", url).into());
|
|
||||||
|
|
||||||
let (mut input_done, mut output_done) = (false, false);
|
let (mut input_done, mut output_done) = (false, false);
|
||||||
let mut last_ws_state = ws.state();
|
let mut last_ws_state = ws.state();
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("ws with url `{}` initial state: {:?}", url, last_ws_state);
|
||||||
web_sys::console::log_1(&format!("ws with url `{}` initial state: {:?}", url, last_ws_state).into());
|
|
||||||
let (mut ws_sink, mut ws_stream) = ws.split();
|
let (mut ws_sink, mut ws_stream) = ws.split();
|
||||||
let (mut left, mut right) = (input.next(), ws_stream.next());
|
let (mut left, mut right) = (input.next(), ws_stream.next());
|
||||||
while ws_is_alive(&last_ws_state) {
|
while ws_is_alive(&last_ws_state) {
|
||||||
if !input_done && !output_done {
|
if !input_done && !output_done {
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("Input and output streams are both alive");
|
||||||
web_sys::console::debug_1(&format!("Input and output streams are both alive").into());
|
|
||||||
match select(left, right).await {
|
match select(left, right).await {
|
||||||
Either::Left((next, outstanding)) => {
|
Either::Left((next, outstanding)) => {
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("Got message to send over websocket");
|
||||||
web_sys::console::debug_1(&format!("Got message to send over websocket").into());
|
|
||||||
if let Some(next) = next {
|
if let Some(next) = next {
|
||||||
match next {
|
match next {
|
||||||
Ok(next) => {
|
Ok(next) => {
|
||||||
|
@ -65,8 +61,7 @@ async fn send_recv_ws<'a>(mut tx: futures_channel::mpsc::Sender<Result<bytes::By
|
||||||
left = input.next();
|
left = input.next();
|
||||||
},
|
},
|
||||||
Either::Right((response, outstanding)) => {
|
Either::Right((response, outstanding)) => {
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("Received message from websocket");
|
||||||
web_sys::console::debug_1(&format!("Received message from websocket").into());
|
|
||||||
if let Some(next) = response {
|
if let Some(next) = response {
|
||||||
match next {
|
match next {
|
||||||
Ok(Message::Bytes(b)) => tx.send(Ok(b.into())).await.unwrap_or(()),
|
Ok(Message::Bytes(b)) => tx.send(Ok(b.into())).await.unwrap_or(()),
|
||||||
|
@ -84,11 +79,9 @@ async fn send_recv_ws<'a>(mut tx: futures_channel::mpsc::Sender<Result<bytes::By
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if input_done {
|
} else if input_done {
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("Input stream is complete");
|
||||||
web_sys::console::debug_1(&format!("Input stream is complete").into());
|
|
||||||
if let Some(next) = right.await {
|
if let Some(next) = right.await {
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("Received message from websocket");
|
||||||
web_sys::console::debug_1(&format!("Received message from websocket").into());
|
|
||||||
match next {
|
match next {
|
||||||
Ok(Message::Bytes(b)) => tx.send(Ok(b.into())).await.unwrap_or(()),
|
Ok(Message::Bytes(b)) => tx.send(Ok(b.into())).await.unwrap_or(()),
|
||||||
Ok(_) => tx.send(Err("Message::Text not allowed".into())).await.unwrap_or(()),
|
Ok(_) => tx.send(Err("Message::Text not allowed".into())).await.unwrap_or(()),
|
||||||
|
@ -104,11 +97,9 @@ async fn send_recv_ws<'a>(mut tx: futures_channel::mpsc::Sender<Result<bytes::By
|
||||||
right = ws_stream.next();
|
right = ws_stream.next();
|
||||||
} else {
|
} else {
|
||||||
// output_done is true
|
// output_done is true
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("Output stream is complete");
|
||||||
web_sys::console::debug_1(&format!("Output stream is complete").into());
|
|
||||||
if let Some(next) = left.await {
|
if let Some(next) = left.await {
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("Got message to send over websocket");
|
||||||
web_sys::console::debug_1(&format!("Got message to send over websocket").into());
|
|
||||||
match next {
|
match next {
|
||||||
Ok(next) => {
|
Ok(next) => {
|
||||||
if let Err(e) = ws_sink.send(Message::Bytes(next.into())).await {
|
if let Err(e) = ws_sink.send(Message::Bytes(next.into())).await {
|
||||||
|
@ -129,38 +120,7 @@ async fn send_recv_ws<'a>(mut tx: futures_channel::mpsc::Sender<Result<bytes::By
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("ws with url `{}` has closed", url);
|
||||||
web_sys::console::debug_1(&format!("ws with url `{}` has closed", url).into());
|
|
||||||
/*spawn_local(async move {
|
|
||||||
while let State::Open = ws.state() {
|
|
||||||
if let Some(next) = input.next().await {
|
|
||||||
match next {
|
|
||||||
Ok(next) => {
|
|
||||||
if let Err(e) = ws.send(Message::Bytes(next.into())).await {
|
|
||||||
tx2.send(Err(e.to_string())).await.unwrap_or(());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => tx2.send(Err(e.to_string())).await.unwrap_or(())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
spawn_local(async move {
|
|
||||||
while let State::Open = ws.state() {
|
|
||||||
if let Some(next) = ws.next().await {
|
|
||||||
match next {
|
|
||||||
Ok(Message::Bytes(b)) => tx.send(Ok(b.into())).await.unwrap_or(()),
|
|
||||||
Ok(_) => tx.send(Err("Message::Text not allowed".into())).await.unwrap_or(()),
|
|
||||||
Err(e) => tx.send(Err(e.to_string())).await.unwrap_or(()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -197,8 +157,7 @@ impl ClientHandler<'static> for WebSocketHandler {
|
||||||
"ws://usdpl-ws-{}.localhost:{}/{}.{}/{}",
|
"ws://usdpl-ws-{}.localhost:{}/{}.{}/{}",
|
||||||
id, self.port, package, service, method,
|
id, self.port, package, service, method,
|
||||||
);
|
);
|
||||||
#[cfg(feature = "debug")]
|
log::debug!("doing send/receive on ws url `{}`", url);
|
||||||
web_sys::console::log_1(&format!("doing send/receive on ws url `{}`", url).into());
|
|
||||||
let (tx, rx) = futures_channel::mpsc::channel(CHANNEL_BOUND);
|
let (tx, rx) = futures_channel::mpsc::channel(CHANNEL_BOUND);
|
||||||
spawn_local(send_recv_ws(tx, url, input));
|
spawn_local(send_recv_ws(tx, url, input));
|
||||||
|
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
//use std::net::TcpStream;
|
|
||||||
//use std::io::{Read, Write};
|
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen_futures::JsFuture;
|
|
||||||
|
|
||||||
//use web_sys::{WebSocket, MessageEvent, ErrorEvent};
|
|
||||||
use js_sys::JsString;
|
|
||||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
|
||||||
//use wasm_rs_shared_channel::{Expects, spsc::{Receiver, Sender}};
|
|
||||||
|
|
||||||
use usdpl_core::serdes::{Dumpable, Loadable, Primitive};
|
|
||||||
use usdpl_core::socket;
|
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
const NONCE: [u8; socket::NONCE_SIZE] = [0u8; socket::NONCE_SIZE];
|
|
||||||
|
|
||||||
pub async fn send_recv_packet(
|
|
||||||
id: u64,
|
|
||||||
packet: socket::Packet,
|
|
||||||
port: u16,
|
|
||||||
#[cfg(feature = "encrypt")] key: Vec<u8>,
|
|
||||||
) -> Result<socket::Packet, JsValue> {
|
|
||||||
let mut opts = RequestInit::new();
|
|
||||||
opts.method("POST");
|
|
||||||
opts.mode(RequestMode::Cors);
|
|
||||||
|
|
||||||
let url = format!(
|
|
||||||
"http://usdpl{}.{}:{}/usdpl/call",
|
|
||||||
id,
|
|
||||||
socket::HOST_STR,
|
|
||||||
port
|
|
||||||
);
|
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
let (buffer, len) = dump_to_buffer(
|
|
||||||
packet,
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
key.as_slice(),
|
|
||||||
)?;
|
|
||||||
let string: String = String::from_utf8_lossy(buffer.as_slice()).into();
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
crate::imports::console_log(&format!("Dumped base64 `{}` len:{}", string, len));
|
|
||||||
opts.body(Some(&string.into()));
|
|
||||||
|
|
||||||
let request = Request::new_with_str_and_init(&url, &opts)?;
|
|
||||||
|
|
||||||
//request.headers().set("Accept", "text/base64")?;
|
|
||||||
//.set("Authorization", "wasm TODO_KEY")?;
|
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
|
||||||
|
|
||||||
let resp: Response = resp_value.dyn_into()?;
|
|
||||||
let text = JsFuture::from(resp.text()?).await?;
|
|
||||||
let string: JsString = text.dyn_into()?;
|
|
||||||
|
|
||||||
let rust_str = string.as_string().unwrap();
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
crate::imports::console_log(&format!(
|
|
||||||
"Received base64 `{}` len:{}",
|
|
||||||
rust_str,
|
|
||||||
rust_str.len()
|
|
||||||
));
|
|
||||||
|
|
||||||
#[cfg(not(feature = "encrypt"))]
|
|
||||||
{
|
|
||||||
Ok(socket::Packet::load_base64(rust_str.as_bytes())
|
|
||||||
.map_err(super::convert::str_to_js)?
|
|
||||||
.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
{
|
|
||||||
Ok(
|
|
||||||
socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE)
|
|
||||||
.map_err(super::convert::str_to_js)?
|
|
||||||
.0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_call(
|
|
||||||
id: u64,
|
|
||||||
packet: socket::Packet,
|
|
||||||
port: u16,
|
|
||||||
#[cfg(feature = "encrypt")] key: Vec<u8>,
|
|
||||||
) -> Result<Vec<Primitive>, JsValue> {
|
|
||||||
let packet = send_recv_packet(
|
|
||||||
id,
|
|
||||||
packet,
|
|
||||||
port,
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
key,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
match packet {
|
|
||||||
socket::Packet::CallResponse(resp) => Ok(resp.response),
|
|
||||||
_ => {
|
|
||||||
//imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url()));
|
|
||||||
Err("Expected call response message, got something else".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
fn dump_to_buffer(packet: socket::Packet, key: &[u8]) -> Result<(Vec<u8>, usize), JsValue> {
|
|
||||||
let mut buffer = Vec::with_capacity(socket::PACKET_BUFFER_SIZE);
|
|
||||||
//buffer.extend_from_slice(&[0u8; socket::PACKET_BUFFER_SIZE]);
|
|
||||||
let len = packet
|
|
||||||
.dump_encrypted(&mut buffer, key, &NONCE)
|
|
||||||
.map_err(super::convert::str_to_js)?;
|
|
||||||
Ok((buffer, len))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "encrypt"))]
|
|
||||||
fn dump_to_buffer(packet: socket::Packet) -> Result<(Vec<u8>, usize), JsValue> {
|
|
||||||
let mut buffer = String::with_capacity(socket::PACKET_BUFFER_SIZE);
|
|
||||||
//buffer.extend_from_slice(&[0u8; socket::PACKET_BUFFER_SIZE]);
|
|
||||||
let len = packet
|
|
||||||
.dump_base64(&mut buffer)
|
|
||||||
.map_err(super::convert::str_to_js)?;
|
|
||||||
Ok((buffer.as_bytes().to_vec(), len))
|
|
||||||
}
|
|
52
usdpl-front/src/console_logs.rs
Normal file
52
usdpl-front/src/console_logs.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
pub(crate) struct BuiltInLogger {
|
||||||
|
min_level: log::Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuiltInLogger {
|
||||||
|
pub const fn new(min: log::Level) -> Self {
|
||||||
|
Self {
|
||||||
|
min_level: min,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for BuiltInLogger {
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
metadata.level() <= self.min_level
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
if self.enabled(record.metadata()) {
|
||||||
|
match record.level() {
|
||||||
|
log::Level::Error => web_sys::console::error_1(&fmt_msg(record).into()),
|
||||||
|
log::Level::Warn => web_sys::console::warn_1(&fmt_msg(record).into()),
|
||||||
|
log::Level::Info => web_sys::console::log_1(&fmt_msg(record).into()),
|
||||||
|
log::Level::Debug => web_sys::console::debug_1(&fmt_msg(record).into()),
|
||||||
|
log::Level::Trace => web_sys::console::debug_1(&fmt_msg(record).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_msg(record: &log::Record) -> String {
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
{ format!("[{}]({}) {}", record.level(), file_line_info(record), record.args()) }
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
{ format!("[{}]({}) {}", record.level(), module_line_info(record), record.args()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
fn file_line_info(record: &log::Record) -> String {
|
||||||
|
let filepath = record.file().unwrap_or("<unknown file>");
|
||||||
|
let line = record.line().map(|l| l.to_string()).unwrap_or_else(|| "line?".to_string());
|
||||||
|
format!("{}:{}", filepath, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
fn module_line_info(record: &log::Record) -> String {
|
||||||
|
let target = record.target();
|
||||||
|
let line = record.line().map(|l| l.to_string()).unwrap_or_else(|| "line?".to_string());
|
||||||
|
format!("{}:{}", target, line)
|
||||||
|
}
|
|
@ -7,15 +7,10 @@
|
||||||
|
|
||||||
mod client_handler;
|
mod client_handler;
|
||||||
pub use client_handler::WebSocketHandler;
|
pub use client_handler::WebSocketHandler;
|
||||||
//mod connection;
|
mod console_logs;
|
||||||
mod convert;
|
mod convert;
|
||||||
pub mod wasm;
|
pub mod wasm;
|
||||||
|
|
||||||
/*#[allow(missing_docs)] // existence is pain otherwise
|
|
||||||
pub mod _nrpc_js_interop {
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub mod _helpers {
|
pub mod _helpers {
|
||||||
pub use js_sys;
|
pub use js_sys;
|
||||||
|
@ -26,21 +21,14 @@ pub mod _helpers {
|
||||||
pub use nrpc;
|
pub use nrpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
//use std::sync::atomic::{AtomicU64, Ordering};
|
|
||||||
|
|
||||||
//use js_sys::Array;
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
//use usdpl_core::{socket::Packet, RemoteCall};
|
#[cfg(feature = "debug")]
|
||||||
//const REMOTE_CALL_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
const DEFAULT_MIN_LEVEL: log::Level = log::Level::Trace;
|
||||||
//const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(31337);
|
#[cfg(not(feature = "debug"))]
|
||||||
|
const DEFAULT_MIN_LEVEL: log::Level = log::Level::Info;
|
||||||
|
|
||||||
/*static mut CTX: UsdplContext = UsdplContext {
|
const DEFAULT_LOGGER: console_logs::BuiltInLogger = console_logs::BuiltInLogger::new(DEFAULT_MIN_LEVEL);
|
||||||
port: 0,
|
|
||||||
//id: AtomicU64::new(0),
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
key: Vec::new(),
|
|
||||||
};*/
|
|
||||||
|
|
||||||
static mut CACHE: Option<std::collections::HashMap<String, JsValue>> = None;
|
static mut CACHE: Option<std::collections::HashMap<String, JsValue>> = None;
|
||||||
|
|
||||||
|
@ -51,56 +39,40 @@ fn encryption_key() -> Vec<u8> {
|
||||||
hex::decode(obfstr::obfstr!(env!("USDPL_ENCRYPTION_KEY"))).unwrap()
|
hex::decode(obfstr::obfstr!(env!("USDPL_ENCRYPTION_KEY"))).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static INIT_DONE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||||
//#[wasm_bindgen]
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct UsdplContext {
|
|
||||||
port: u16,
|
|
||||||
id: AtomicU64,
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
key: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_port() -> u16 {
|
|
||||||
unsafe { CTX.port }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
fn get_key() -> Vec<u8> {
|
|
||||||
unsafe { CTX.key.clone() }
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*fn increment_id() -> u64 {
|
|
||||||
let atomic = unsafe { &CTX.id };
|
|
||||||
atomic.fetch_add(1, Ordering::SeqCst)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/// Initialize the front-end library
|
/// Initialize the front-end library
|
||||||
#[wasm_bindgen]
|
//#[wasm_bindgen]
|
||||||
pub fn init_usdpl(port: u16) {
|
pub fn init_usdpl() {
|
||||||
#[cfg(feature = "debug")]
|
if !INIT_DONE.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||||
web_sys::console::log_1(&format!("init_usdpl(port={})", port).into());
|
#[cfg(feature = "console_error_panic_hook")]
|
||||||
#[cfg(feature = "console_error_panic_hook")]
|
console_error_panic_hook::set_once();
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
#[cfg(feature = "console_log")]
|
|
||||||
console_log::init_with_level(log::Level::Debug).expect("USDPL: error initializing console log");
|
|
||||||
|
|
||||||
/*unsafe {
|
log::set_logger(&DEFAULT_LOGGER)
|
||||||
CTX = UsdplContext {
|
.map_err(|e| web_sys::console::error_1(&format!("Failed to setup USDPL logger: {}", e).into()))
|
||||||
port: port,
|
.unwrap_or(());
|
||||||
//id: AtomicU64::new(0),
|
log::set_max_level(log::LevelFilter::Trace);
|
||||||
#[cfg(feature = "encrypt")]
|
log::debug!("init_usdpl() log configured");
|
||||||
key: encryption_key(),
|
|
||||||
};
|
|
||||||
}*/
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
CACHE = Some(std::collections::HashMap::new());
|
CACHE = Some(std::collections::HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("USDPL init succeeded: {}", build_info());
|
||||||
|
} else {
|
||||||
|
log::info!("USDPL init was re-attempted");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
fn build_info() -> String {
|
||||||
web_sys::console::log_1(&format!("USDPL:{} init succeeded", port).into());
|
format!("{} v{} ({}) for {} by {}, more: {}",
|
||||||
log::info!("USDPL:{} init succeeded", port);
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
env!("CARGO_PKG_LICENSE"),
|
||||||
|
target_usdpl(),
|
||||||
|
env!("CARGO_PKG_AUTHORS"),
|
||||||
|
env!("CARGO_PKG_REPOSITORY"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the targeted plugin framework, or "any" if unknown
|
/// Get the targeted plugin framework, or "any" if unknown
|
||||||
|
@ -140,92 +112,6 @@ pub fn get_value(key: String) -> JsValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Call a function on the back-end.
|
|
||||||
/// Returns null (None) if this fails for any reason.
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue {
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
web_sys::console::log_1(&format!(
|
|
||||||
"call_backend({}, [params; {}])",
|
|
||||||
name,
|
|
||||||
parameters.len()
|
|
||||||
).into());
|
|
||||||
let next_id = increment_id();
|
|
||||||
let mut params = Vec::with_capacity(parameters.len());
|
|
||||||
for val in parameters {
|
|
||||||
params.push(convert::js_to_primitive(val));
|
|
||||||
}
|
|
||||||
let port = get_port();
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
web_sys::console::log_1(&format!("USDPL: Got port {}", port).into());
|
|
||||||
let results = connection::send_call(
|
|
||||||
next_id,
|
|
||||||
Packet::Call(RemoteCall {
|
|
||||||
id: next_id,
|
|
||||||
function: name.clone(),
|
|
||||||
parameters: params,
|
|
||||||
}),
|
|
||||||
port,
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
get_key(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let results = match results {
|
|
||||||
Ok(x) => x,
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
Err(e) => {
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
web_sys::console::error_1(&format!("USDPL: Got error while calling {}: {:?}", name, e).into());
|
|
||||||
return JsValue::NULL;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let results_js = Array::new_with_length(results.len() as _);
|
|
||||||
let mut i = 0;
|
|
||||||
for item in results {
|
|
||||||
results_js.set(i as _, convert::primitive_to_js(item));
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
results_js.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialize translation strings for the front-end
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub async fn init_tr(locale: String) {
|
|
||||||
let next_id = increment_id();
|
|
||||||
match connection::send_recv_packet(
|
|
||||||
next_id,
|
|
||||||
Packet::Language(locale.clone()),
|
|
||||||
get_port(),
|
|
||||||
#[cfg(feature = "encrypt")]
|
|
||||||
get_key(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Packet::Translations(translations)) => {
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
web_sys::console::log_1(&format!("USDPL: Got translations for {}", locale).into());
|
|
||||||
// convert translations into map
|
|
||||||
let mut tr_map = std::collections::HashMap::with_capacity(translations.len());
|
|
||||||
for (key, val) in translations {
|
|
||||||
tr_map.insert(key, val);
|
|
||||||
}
|
|
||||||
unsafe { TRANSLATIONS = Some(tr_map) }
|
|
||||||
}
|
|
||||||
Ok(_) => {
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
web_sys::console::error_1(&format!("USDPL: Got wrong packet response for init_tr").into());
|
|
||||||
unsafe { TRANSLATIONS = None }
|
|
||||||
}
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
Err(e) => {
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
web_sys::console::error_1(&format!("USDPL: Got wrong error for init_tr: {:#?}", e).into());
|
|
||||||
unsafe { TRANSLATIONS = None }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/// Translate a phrase, equivalent to tr_n(msg_id, 0)
|
/// Translate a phrase, equivalent to tr_n(msg_id, 0)
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn tr(msg_id: String) -> String {
|
pub fn tr(msg_id: String) -> String {
|
||||||
|
|
Loading…
Reference in a new issue