Remove old APIs, improve/fix front logging

This commit is contained in:
NGnius (Graham) 2023-09-03 17:33:30 -04:00
parent b7b42a8c6d
commit 44298f660f
20 changed files with 115 additions and 1353 deletions

12
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -1,2 +1 @@
pub mod dirs; pub mod dirs;
pub mod files;

View file

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

View file

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

View file

@ -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 = []

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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)
}

View file

@ -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());
} }
#[cfg(feature = "debug")] log::info!("USDPL init succeeded: {}", build_info());
web_sys::console::log_1(&format!("USDPL:{} init succeeded", port).into()); } else {
log::info!("USDPL:{} init succeeded", port); log::info!("USDPL init was re-attempted");
}
}
fn build_info() -> String {
format!("{} v{} ({}) for {} by {}, more: {}",
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 {