From 0a5323c927f2e7e86eebd291b69cabc07037cf4a Mon Sep 17 00:00:00 2001 From: NGnius Date: Wed, 15 Jun 2022 21:18:24 -0400 Subject: [PATCH] Minor refactor and code cleanup to improve ergonomics --- Cargo.lock | 7 +- build.sh | 6 +- usdpl-back/Cargo.toml | 5 +- usdpl-back/src/callable.rs | 5 + usdpl-back/src/instance.rs | 291 +++++++-------------------- usdpl-back/src/lib.rs | 6 +- usdpl-core/Cargo.toml | 2 +- usdpl-core/src/api_any/mod.rs | 1 + usdpl-core/src/api_common/mod.rs | 3 + usdpl-core/src/api_common/target.rs | 32 +++ usdpl-core/src/api_crankshaft/mod.rs | 1 + usdpl-core/src/api_decky/mod.rs | 1 + usdpl-core/src/lib.rs | 20 +- usdpl-core/src/remote_call.rs | 89 +++----- usdpl-core/src/serdes/dump_impl.rs | 100 +++++---- usdpl-core/src/serdes/load_impl.rs | 110 +++++----- usdpl-core/src/serdes/mod.rs | 2 +- usdpl-core/src/serdes/primitive.rs | 124 +++++------- usdpl-core/src/serdes/traits.rs | 63 ++++-- usdpl-core/src/socket.rs | 68 +++---- usdpl-front/Cargo.toml | 4 +- usdpl-front/src/connection.rs | 172 ++-------------- usdpl-front/src/convert.rs | 19 +- usdpl-front/src/imports.rs | 2 +- usdpl-front/src/lib.rs | 47 +++-- 25 files changed, 467 insertions(+), 713 deletions(-) create mode 100644 usdpl-back/src/callable.rs create mode 100644 usdpl-core/src/api_any/mod.rs create mode 100644 usdpl-core/src/api_common/mod.rs create mode 100644 usdpl-core/src/api_common/target.rs create mode 100644 usdpl-core/src/api_crankshaft/mod.rs create mode 100644 usdpl-core/src/api_decky/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 68525a2..a6ccd7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -926,7 +926,7 @@ dependencies = [ [[package]] name = "usdpl" -version = "0.3.0" +version = "0.4.0" dependencies = [ "console_error_panic_hook", "js-sys", @@ -940,10 +940,9 @@ dependencies = [ [[package]] name = "usdpl-back" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bytes", - "lazy_static", "tokio", "usdpl-core", "warp", @@ -951,7 +950,7 @@ dependencies = [ [[package]] name = "usdpl-core" -version = "0.1.0" +version = "0.4.0" dependencies = [ "base64", ] diff --git a/build.sh b/build.sh index 63cf029..5136e12 100755 --- a/build.sh +++ b/build.sh @@ -7,9 +7,11 @@ $0 [decky|crankshaft|]" elif [ "$1" == "decky" ]; then echo "Building back & front for decky framework" # usdpl-back + echo "...Running usdpl-back build..." cd ./usdpl-back ./build.sh decky # usdpl-front + echo "...Running usdpl-front build..." cd ../usdpl-front ./build.sh decky cd .. @@ -35,11 +37,11 @@ else # usdpl-back echo "...Running usdpl-back build..." cd ./usdpl-back - cargo build --release + ./build.sh # usdpl-front echo "...Running usdpl-front build..." cd ../usdpl-front - ./build.sh crankshaft + ./build.sh cd .. echo "Built usdpl back & front for any" fi diff --git a/usdpl-back/Cargo.toml b/usdpl-back/Cargo.toml index ad91dca..47a9084 100644 --- a/usdpl-back/Cargo.toml +++ b/usdpl-back/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "usdpl-back" -version = "0.3.0" +version = "0.4.0" edition = "2021" license = "GPL-3.0-only" repository = "https://github.com/NGnius/usdpl-rs" @@ -13,10 +13,9 @@ decky = [] crankshaft = [] [dependencies] -usdpl-core = { version = "0.3.0", path = "../usdpl-core" } +usdpl-core = { version = "0.4.0", path = "../usdpl-core" } # HTTP web framework warp = { version = "0.3" } bytes = { version = "1.1" } tokio = { version = "1.19", features = ["rt", "rt-multi-thread"] } -lazy_static = { version = "1.4" } diff --git a/usdpl-back/src/callable.rs b/usdpl-back/src/callable.rs new file mode 100644 index 0000000..1bee16c --- /dev/null +++ b/usdpl-back/src/callable.rs @@ -0,0 +1,5 @@ +use usdpl_core::serdes::Primitive; + +pub trait Callable: Send + Sync { + fn call(&mut self, params: Vec) -> Vec; +} diff --git a/usdpl-back/src/instance.rs b/usdpl-back/src/instance.rs index b161911..4799a30 100644 --- a/usdpl-back/src/instance.rs +++ b/usdpl-back/src/instance.rs @@ -1,24 +1,19 @@ -//use std::net::{TcpListener, TcpStream, SocketAddr}; use std::collections::HashMap; -//use std::sync::Arc; -//use std::io::{Read, Write}; - -use lazy_static::lazy_static; +use std::sync::Arc; +use std::sync::Mutex; use warp::Filter; -use usdpl_core::serdes::{Dumpable, Loadable, Primitive}; -use usdpl_core::{RemoteCallResponse, socket}; +use usdpl_core::serdes::{Dumpable, Loadable}; +use usdpl_core::{socket, RemoteCallResponse}; -type Callable = Box<(dyn (Fn(Vec) -> Vec) + Send + Sync)>; +use super::Callable; -lazy_static! { - static ref CALLS: std::sync::Mutex> = std::sync::Mutex::new(HashMap::new()); -} +type WrappedCallable = Arc>>; // thread-safe, cloneable Callable /// Back-end instance for interacting with the front-end pub struct Instance { - //calls: HashMap, + calls: HashMap, port: u16, } @@ -27,86 +22,25 @@ impl Instance { #[inline] pub fn new(port_usdpl: u16) -> Self { Instance { - //calls: HashMap::new(), + calls: HashMap::new(), port: port_usdpl, } } /// Register a function which can be invoked by the front-end - pub fn register, F: (Fn(Vec) -> Vec) + Send + Sync + 'static>(&mut self, name: S, f: F) -> &mut Self { - CALLS.lock().unwrap().insert(name.into(), Box::new(f)); - //self.calls.insert(name.into(), Box::new(f)); + pub fn register, F: Callable + 'static>( + &mut self, + name: S, + f: F, + ) -> &mut Self { + //CALLS.lock().unwrap().insert(name.into(), Mutex::new(Box::new(f))); + self.calls + .insert(name.into(), Arc::new(Mutex::new(Box::new(f)))); self } - /*fn handle_packet(&mut self, packet: socket::Packet, buffer: &mut [u8], peer_addr: &SocketAddr) -> Result { - match packet { - socket::Packet::Call(obj) => { - if let Some(target_func) = self.calls.get_mut(&obj.function) { - // TODO: multithread this - let result = target_func(obj.parameters); - let response = socket::Packet::CallResponse(RemoteCallResponse { - id: obj.id, - response: result, - }); - let (ok, len) = response.dump(buffer); - if !ok && ERROR { - return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump return value of function `{}`", &obj.function)))); - } - if ERROR { - let mut vec = Vec::with_capacity(len); - vec.extend_from_slice(&buffer[..len]); - incoming.write_message(Message::Binary(vec)).map_err(super::ServerError::Tungstenite)?; - } else { - let mut vec = Vec::with_capacity(len); - vec.extend_from_slice(&buffer[..len]); - incoming.write_message(Message::Binary(vec)).unwrap_or_default(); - } - } else { - if ERROR { - return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid remote call `{}` received from {}", obj.function, peer_addr)))); - } else { - eprintln!("Invalid remote call `{}` received from {}", obj.function, peer_addr); - } - - } - }, - socket::Packet::Many(many) => { - for packet in many { - if let socket::Packet::Many(_) = packet { - // drop nested socket packets (prevents DoS and bad practices) - if ERROR { - return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Invalid nested Many packet received from {}", peer_addr)))); - } else { - eprintln!("Invalid nested Many packet received from {}", peer_addr); - } - continue; - } - self.handle_packet::(packet, buffer, incoming, peer_addr)?; - } - }, - _ => { - let (ok, len) = socket::Packet::Unsupported.dump(buffer); - if !ok && ERROR { - return Err(super::ServerError::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, format!("Cannot dump unsupported packet")))); - } - if ERROR { - let mut vec = Vec::with_capacity(len); - vec.extend_from_slice(&buffer[..len]); - incoming.write_message(Message::Binary(vec)).map_err(super::ServerError::Tungstenite)?; - } else { - let mut vec = Vec::with_capacity(len); - vec.extend_from_slice(&buffer[..len]); - incoming.write_message(Message::Binary(vec)).unwrap_or_default(); - } - } - } - Ok(()) - }*/ - - pub fn serve(self) -> super::ServerResult { + pub fn serve(&self) -> Result<(), ()> { let result = self.serve_internal(); - //println!("Stopping server due to serve_internal returning a result"); tokio::runtime::Builder::new_multi_thread() .enable_all() .build() @@ -114,13 +48,18 @@ impl Instance { .block_on(result) } - fn handle_call(packet: socket::Packet) -> socket::Packet { - println!("Got packet"); + fn handle_call( + packet: socket::Packet, + handlers: &HashMap, + ) -> socket::Packet { match packet { socket::Packet::Call(call) => { - let handlers = CALLS.lock().expect("Failed to acquite CALLS lock"); + //let handlers = CALLS.lock().expect("Failed to acquire CALLS lock"); if let Some(target) = handlers.get(&call.function) { - let result = target(call.parameters); + let result = target + .lock() + .expect("Failed to acquire CALLS.function lock") + .call(call.parameters); socket::Packet::CallResponse(RemoteCallResponse { id: call.id, response: result, @@ -128,51 +67,61 @@ impl Instance { } else { socket::Packet::Invalid } - }, + } socket::Packet::Many(packets) => { let mut result = Vec::with_capacity(packets.len()); for packet in packets { - result.push(Self::handle_call(packet)); + result.push(Self::handle_call(packet, handlers)); } socket::Packet::Many(result) - }, + } _ => socket::Packet::Invalid, } } /// Receive and execute callbacks forever - pub async fn serve_internal(self) -> super::ServerResult { - //let handlers = self.calls; + pub async fn serve_internal(&self) -> Result<(), ()> { + let handlers = self.calls.clone(); //self.calls = HashMap::new(); let calls = warp::post() - .and(warp::path("usdpl/call")) - .and(warp::body::content_length_limit((socket::PACKET_BUFFER_SIZE * 2) as _)) + .and(warp::path!("usdpl" / "call")) + .and(warp::body::content_length_limit( + (socket::PACKET_BUFFER_SIZE * 2) as _, + )) .and(warp::body::bytes()) - .map(|data: bytes::Bytes| { - let (obj_maybe, _) = socket::Packet::load_base64(&data); - let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - if let Some(packet) = obj_maybe { - let response = Self::handle_call(packet); - let (ok, len) = response.dump_base64(&mut buffer); - if !ok { - eprintln!("Failed to dump response packet"); - warp::reply::with_status(warp::http::Response::builder() - .body("Failed to dump response packet".to_string()), warp::http::StatusCode::from_u16(400).unwrap()) - } else { - let string: String = String::from_utf8_lossy(&buffer[..len]).into(); - warp::reply::with_status(warp::http::Response::builder() - .body(string), warp::http::StatusCode::from_u16(200).unwrap()) + .map(move |data: bytes::Bytes| { + let (packet, _) = match socket::Packet::load_base64(&data) { + Ok(x) => x, + Err(_) => { + return warp::reply::with_status( + warp::http::Response::builder() + .body("Failed to load packet".to_string()), + warp::http::StatusCode::from_u16(400).unwrap(), + ) } - } else { - eprintln!("Failed to load packet"); - warp::reply::with_status(warp::http::Response::builder() - .body("Failed to load packet".to_string()), warp::http::StatusCode::from_u16(400).unwrap()) - } + }; + let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; + let response = Self::handle_call(packet, &handlers); + let len = match response.dump_base64(&mut buffer) { + Ok(x) => x, + Err(_) => { + return warp::reply::with_status( + warp::http::Response::builder() + .body("Failed to dump response packet".to_string()), + warp::http::StatusCode::from_u16(500).unwrap(), + ) + } + }; + let string: String = String::from_utf8_lossy(&buffer[..len]).into(); + warp::reply::with_status( + warp::http::Response::builder().body(string), + warp::http::StatusCode::from_u16(200).unwrap(), + ) }) - .map(|reply| { - warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*") - }); - + .map(|reply| warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*")); + #[cfg(debug_assertions)] + warp::serve(calls).run(([0, 0, 0, 0], self.port)).await; + #[cfg(not(debug_assertions))] warp::serve(calls).run(([127, 0, 0, 1], self.port)).await; Ok(()) } @@ -180,110 +129,6 @@ impl Instance { #[cfg(test)] mod tests { - //use std::net::TcpStream; - //use super::*; - - //const PORT: u16 = 31337; - - /*#[test] - fn serve_full_test() -> std::io::Result<()> { - let _server = std::thread::spawn(|| { - Instance::new(PORT) - .register("echo".to_string(), &mut |params| params) - .register("hello".to_string(), &mut |params| { - if let Some(Primitive::String(name)) = params.get(0) { - vec![Primitive::String(format!("Hello {}", name))] - } else { - vec![] - } - }) - .serve::() - }); - std::thread::sleep(std::time::Duration::from_millis(10)); - let mut front = TcpStream::connect(socket::socket_addr(PORT)).unwrap(); - let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let call = socket::Packet::Call(usdpl_core::RemoteCall { - id: 42, - function: "hello".to_string(), - parameters: vec![Primitive::String("USDPL".to_string())] - }); - let (ok, len) = call.dump(&mut buffer); - assert!(ok, "Packet dump failed"); - assert_eq!(len, 32, "Packet dumped wrong amount of data"); - front.write(&buffer[..len])?; - let len = front.read(&mut buffer)?; - let (response, len) = socket::Packet::load(&buffer[..len]); - assert!(response.is_some(), "Response load failed"); - assert_eq!(len, 29, "Response loaded wrong amount of data"); - let response = response.unwrap(); - if let socket::Packet::CallResponse(resp) = response { - assert_eq!(resp.id, 42); - if let Some(Primitive::String(s)) = resp.response.get(0) { - assert_eq!(s, "Hello USDPL"); - } else { - panic!("Wrong response data"); - } - } else { - panic!("Wrong response packet type"); - } - - Ok(()) - } - - #[test] - #[should_panic] - fn serve_err_test() { - let _client = std::thread::spawn(|| { - std::thread::sleep(std::time::Duration::from_millis(100)); - let mut front = TcpStream::connect(socket::socket_addr(PORT+1)).unwrap(); - let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let (_, len) = socket::Packet::Bad.dump(&mut buffer); - front.write(&buffer[..len]).unwrap(); - let _ = front.read(&mut buffer).unwrap(); - }); - Instance::new(PORT+1) - .register("echo".to_string(), &mut |params| params) - .register("hello".to_string(), &mut |params| { - if let Some(Primitive::String(name)) = params.get(0) { - vec![Primitive::String(format!("Hello {}", name))] - } else { - vec![] - } - }) - .serve::() - .unwrap(); - } - - #[test] - #[should_panic] - fn serve_unsupported_test() { - let _server = std::thread::spawn(|| { - Instance::new(PORT+2) - .register("echo".to_string(), &mut |params| params) - .register("hello".to_string(), &mut |params| { - if let Some(Primitive::String(name)) = params.get(0) { - vec![Primitive::String(format!("Hello {}", name))] - } else { - vec![] - } - }) - .serve::() - }); - std::thread::sleep(std::time::Duration::from_millis(10)); - let mut front = TcpStream::connect(socket::socket_addr(PORT+2)).unwrap(); - let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let (ok, len) = socket::Packet::Unsupported.dump(&mut buffer); - assert!(ok, "Packet dump failed"); - assert_eq!(len, 32, "Packet dumped wrong amount of data"); - front.write(&buffer[..len]).unwrap(); - let len = front.read(&mut buffer).unwrap(); - let (response, len) = socket::Packet::load(&buffer[..len]); - assert!(response.is_some(), "Response load failed"); - assert_eq!(len, 29, "Response loaded wrong amount of data"); - let response = response.unwrap(); - if let socket::Packet::Unsupported = response { - } else { - panic!("Wrong response packet type"); - } - }*/ + #[allow(unused_imports)] + use super::*; } diff --git a/usdpl-back/src/lib.rs b/usdpl-back/src/lib.rs index bed0ee6..3a85a4e 100644 --- a/usdpl-back/src/lib.rs +++ b/usdpl-back/src/lib.rs @@ -4,11 +4,13 @@ //! This is a minimalist TCP server for handling events from the front-end. //! -mod errors; +mod callable; +//mod errors; mod instance; +pub use callable::Callable; pub use instance::Instance; -pub use errors::{ServerError, ServerResult}; +//pub use errors::{ServerError, ServerResult}; pub mod core { pub use usdpl_core::*; diff --git a/usdpl-core/Cargo.toml b/usdpl-core/Cargo.toml index e235b1d..e46f41d 100644 --- a/usdpl-core/Cargo.toml +++ b/usdpl-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "usdpl-core" -version = "0.3.0" +version = "0.4.0" edition = "2021" license = "GPL-3.0-only" repository = "https://github.com/NGnius/usdpl-rs" diff --git a/usdpl-core/src/api_any/mod.rs b/usdpl-core/src/api_any/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/usdpl-core/src/api_any/mod.rs @@ -0,0 +1 @@ + diff --git a/usdpl-core/src/api_common/mod.rs b/usdpl-core/src/api_common/mod.rs new file mode 100644 index 0000000..40e9267 --- /dev/null +++ b/usdpl-core/src/api_common/mod.rs @@ -0,0 +1,3 @@ +mod target; + +pub use target::Platform; diff --git a/usdpl-core/src/api_common/target.rs b/usdpl-core/src/api_common/target.rs new file mode 100644 index 0000000..61ef1c6 --- /dev/null +++ b/usdpl-core/src/api_common/target.rs @@ -0,0 +1,32 @@ +pub enum Platform { + Any, + Decky, + Crankshaft, +} + +impl Platform { + pub fn current() -> Self { + #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] + { + Self::Decky + } + #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] + { + Self::Crankshaft + } + #[cfg(not(any(feature = "decky", feature = "crankshaft")))] + { + Self::Any + } + } +} + +impl std::fmt::Display for Platform { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Any => write!(f, "any"), + Self::Decky => write!(f, "decky"), + Self::Crankshaft => write!(f, "crankshaft"), + } + } +} diff --git a/usdpl-core/src/api_crankshaft/mod.rs b/usdpl-core/src/api_crankshaft/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/usdpl-core/src/api_crankshaft/mod.rs @@ -0,0 +1 @@ + diff --git a/usdpl-core/src/api_decky/mod.rs b/usdpl-core/src/api_decky/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/usdpl-core/src/api_decky/mod.rs @@ -0,0 +1 @@ + diff --git a/usdpl-core/src/lib.rs b/usdpl-core/src/lib.rs index 8769231..a01d4a7 100644 --- a/usdpl-core/src/lib.rs +++ b/usdpl-core/src/lib.rs @@ -2,7 +2,25 @@ //! This contains serialization functionality and networking datatypes. mod remote_call; -pub mod socket; +#[cfg(not(any(feature = "decky", feature = "crankshaft")))] +mod api_any; +mod api_common; +#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] +mod api_crankshaft; +#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] +mod api_decky; + pub mod serdes; +pub mod socket; pub use remote_call::{RemoteCall, RemoteCallResponse}; + +pub mod api { + #[cfg(not(any(feature = "decky", feature = "crankshaft")))] + pub use super::api_any::*; + pub use super::api_common::*; + #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] + pub use super::api_crankshaft::*; + #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] + pub use super::api_decky::*; +} diff --git a/usdpl-core/src/remote_call.rs b/usdpl-core/src/remote_call.rs index afbba21..4b7f052 100644 --- a/usdpl-core/src/remote_call.rs +++ b/usdpl-core/src/remote_call.rs @@ -1,4 +1,4 @@ -use crate::serdes::{Primitive, Loadable, Dumpable}; +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 { @@ -8,42 +8,27 @@ pub struct RemoteCall { } impl Loadable for RemoteCall { - fn load(buffer: &[u8]) -> (Option, usize) { - let (id_num, len0) = u64::load(buffer); - if id_num.is_none() { - return (None, len0); - } - let (function_name, len1) = String::load(&buffer[len0..]); - if function_name.is_none() { - return (None, len1); - } - let (params, len2) = Vec::::load(&buffer[len0+len1..]); - if params.is_none() { - return (None, len1 + len2); - } - ( - Some(Self { - id: id_num.unwrap(), - function: function_name.unwrap(), - parameters: params.unwrap(), - }), - len0 + len1 + len2 - ) + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { + let (id_num, len0) = u64::load(buffer)?; + let (function_name, len1) = String::load(&buffer[len0..])?; + let (params, len2) = Vec::::load(&buffer[len0 + len1..])?; + Ok(( + Self { + id: id_num, + function: function_name, + parameters: params, + }, + len0 + len1 + len2, + )) } } impl Dumpable for RemoteCall { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { - let (ok0, len0) = self.id.dump(buffer); - if !ok0 { - return (ok0, len0); - } - let (ok1, len1) = self.function.dump(&mut buffer[len0..]); - if !ok1 { - return (ok1, len1); - } - let (ok2, len2) = self.parameters.dump(&mut buffer[len0+len1..]); - (ok2, len0 + len1 + len2) + fn dump(&self, buffer: &mut [u8]) -> Result { + let len0 = self.id.dump(buffer)?; + let len1 = self.function.dump(&mut buffer[len0..])?; + let len2 = self.parameters.dump(&mut buffer[len0 + len1..])?; + Ok(len0 + len1 + len2) } } @@ -54,33 +39,23 @@ pub struct RemoteCallResponse { } impl Loadable for RemoteCallResponse { - fn load(buffer: &[u8]) -> (Option, usize) { - let (id_num, len0) = u64::load(buffer); - if id_num.is_none() { - return (None, len0); - } - let (response_var, len1) = Vec::::load(&buffer[len0..]); - if response_var.is_none() { - return (None, len1); - } - ( - Some(Self { - id: id_num.unwrap(), - response: response_var.unwrap(), - }), - len0 + len1 - ) + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { + let (id_num, len0) = u64::load(buffer)?; + let (response_var, len1) = Vec::::load(&buffer[len0..])?; + Ok(( + Self { + id: id_num, + response: response_var, + }, + len0 + len1, + )) } } impl Dumpable for RemoteCallResponse { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { - let (ok0, len0) = self.id.dump(buffer); - if !ok0 { - return (ok0, len0); - } - let (ok1, len1) = self.response.dump(&mut buffer[len0..]); - (ok1, len0 + len1) + fn dump(&self, buffer: &mut [u8]) -> Result { + let len0 = self.id.dump(buffer)?; + let len1 = self.response.dump(&mut buffer[len0..])?; + Ok(len0 + len1) } } - diff --git a/usdpl-core/src/serdes/dump_impl.rs b/usdpl-core/src/serdes/dump_impl.rs index bb6b96e..b90f022 100644 --- a/usdpl-core/src/serdes/dump_impl.rs +++ b/usdpl-core/src/serdes/dump_impl.rs @@ -1,91 +1,88 @@ -use super::Dumpable; +use super::{DumpError, Dumpable}; impl Dumpable for String { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { + fn dump(&self, buffer: &mut [u8]) -> Result { let str_bytes = self.as_bytes(); let len_bytes = (str_bytes.len() as u32).to_le_bytes(); let total_len = str_bytes.len() + 4; if buffer.len() < total_len { - return (false, 0); + return Err(DumpError::TooSmallBuffer); } (&mut buffer[..4]).copy_from_slice(&len_bytes); (&mut buffer[4..total_len]).copy_from_slice(str_bytes); - (true, total_len) + Ok(total_len) } } impl Dumpable for Vec { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { + fn dump(&self, buffer: &mut [u8]) -> Result { let len_bytes = (self.len() as u32).to_le_bytes(); (&mut buffer[..4]).copy_from_slice(&len_bytes); let mut cursor = 4; for obj in self.iter() { - let (ok, len) = obj.dump(&mut buffer[cursor..]); + let len = obj.dump(&mut buffer[cursor..])?; cursor += len; - if !ok { - return (false, cursor); - } } - (true, cursor) + Ok(cursor) } } impl Dumpable for bool { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { + fn dump(&self, buffer: &mut [u8]) -> Result { if buffer.len() < 1 { - return (false, 0); + return Err(DumpError::TooSmallBuffer); } buffer[0] = *self as u8; - (true, 1) + Ok(1) } } impl Dumpable for u8 { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { + fn dump(&self, buffer: &mut [u8]) -> Result { if buffer.len() < 1 { - return (false, 0); + return Err(DumpError::TooSmallBuffer); } buffer[0] = *self; - (true, 1) + Ok(1) } } impl Dumpable for i8 { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { + fn dump(&self, buffer: &mut [u8]) -> Result { if buffer.len() < 1 { - return (false, 0); + return Err(DumpError::TooSmallBuffer); } buffer[0] = self.to_le_bytes()[0]; - (true, 1) + Ok(1) } } macro_rules! int_impl { ($type:ty, $size:literal) => { impl Dumpable for $type { - fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { + fn dump(&self, buffer: &mut [u8]) -> Result { if buffer.len() < $size { - return (false, 0); + return Err(DumpError::TooSmallBuffer); } (&mut buffer[..$size]).copy_from_slice(&self.to_le_bytes()); - (true, $size) + Ok($size) } } - } + }; } -int_impl!{u16, 2} -int_impl!{u32, 4} -int_impl!{u64, 8} -int_impl!{u128, 16} +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! {i16, 2} +int_impl! {i32, 4} +int_impl! {i64, 8} +int_impl! {i128, 16} -int_impl!{f32, 4} -int_impl!{f64, 8} +int_impl! {f32, 4} +int_impl! {f64, 8} #[cfg(test)] mod tests { @@ -97,17 +94,16 @@ mod tests { fn $fn_name() { let data = $data; let mut buffer = [0u8; 128]; - let (ok, write_len) = data.dump(&mut buffer); - assert!(ok, "Dump not ok"); + 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); } - } + }; } - test_impl!{string_dump_test, "test".to_string(), 8, &[4, 0, 0, 0, 116, 101, 115, 116]} + test_impl! {string_dump_test, "test".to_string(), 8, &[4, 0, 0, 0, 116, 101, 115, 116]} - test_impl!{ + test_impl! { vec_dump_test, vec![ "".to_string(), @@ -118,23 +114,23 @@ mod tests { &[3, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 5, 0, 0, 0, 116, 101, 115, 116, 50] } - test_impl!{bool_true_dump_test, true, 1, &[1]} - test_impl!{bool_false_dump_test, false, 1, &[0]} + 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! {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! {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]} + 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]} } diff --git a/usdpl-core/src/serdes/load_impl.rs b/usdpl-core/src/serdes/load_impl.rs index 2428b99..8ca43e8 100644 --- a/usdpl-core/src/serdes/load_impl.rs +++ b/usdpl-core/src/serdes/load_impl.rs @@ -1,21 +1,24 @@ -use super::Loadable; +use super::{LoadError, Loadable}; impl Loadable for String { - fn load(buffer: &[u8]) -> (Option, usize) { + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { if buffer.len() < 4 { - return (None, 0); + return Err(LoadError::TooSmallBuffer); } let mut u32_bytes: [u8; 4] = [u8::MAX; 4]; u32_bytes.copy_from_slice(&buffer[..4]); let str_size = u32::from_le_bytes(u32_bytes) as usize; - (Some(Self::from_utf8_lossy(&buffer[4..str_size + 4]).into_owned()), str_size + 4) + Ok(( + Self::from_utf8_lossy(&buffer[4..str_size + 4]).into_owned(), + str_size + 4, + )) } } impl Loadable for Vec { - fn load(buffer: &[u8]) -> (Option, usize) { + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { if buffer.len() < 4 { - return (None, 0); + return Err(LoadError::TooSmallBuffer); } let mut u32_bytes: [u8; 4] = [u8::MAX; 4]; u32_bytes.copy_from_slice(&buffer[..4]); @@ -23,73 +26,69 @@ impl Loadable for Vec { let mut cursor = 4; let mut items = Vec::with_capacity(count); for _ in 0..count { - let (obj, len) = T::load(&buffer[cursor..]); + let (obj, len) = T::load(&buffer[cursor..])?; cursor += len; - if let Some(obj) = obj { - items.push(obj); - } else { - return (None, cursor); - } + items.push(obj); } - (Some(items), cursor) + Ok((items, cursor)) } } impl Loadable for bool { - fn load(buffer: &[u8]) -> (Option, usize) { + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { if buffer.len() < 1 { - return (None, 0); + return Err(LoadError::TooSmallBuffer); } - (Some(buffer[0] != 0), 1) + Ok((buffer[0] != 0, 1)) } } impl Loadable for u8 { - fn load(buffer: &[u8]) -> (Option, usize) { + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { if buffer.len() < 1 { - return (None, 0); + return Err(LoadError::TooSmallBuffer); } - (Some(buffer[0]), 1) + Ok((buffer[0], 1)) } } impl Loadable for i8 { - fn load(buffer: &[u8]) -> (Option, usize) { + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { if buffer.len() < 1 { - return (None, 0); + return Err(LoadError::TooSmallBuffer); } - (Some(i8::from_le_bytes([buffer[0]])), 1) + Ok((i8::from_le_bytes([buffer[0]]), 1)) } } macro_rules! int_impl { ($type:ty, $size:literal) => { impl Loadable for $type { - fn load(buffer: &[u8]) -> (Option, usize) { + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> { if buffer.len() < $size { - return (None, 0); + return Err(LoadError::TooSmallBuffer); } let mut bytes: [u8; $size] = [u8::MAX; $size]; bytes.copy_from_slice(&buffer[..$size]); let i = <$type>::from_le_bytes(bytes); - (Some(i), $size) + Ok((i, $size)) } } - } + }; } -int_impl!{u16, 2} -int_impl!{u32, 4} -int_impl!{u64, 8} -int_impl!{u128, 16} +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! {i16, 2} +int_impl! {i32, 4} +int_impl! {i64, 8} +int_impl! {i128, 16} -int_impl!{f32, 4} -int_impl!{f64, 8} +int_impl! {f32, 4} +int_impl! {f64, 8} #[cfg(test)] mod tests { @@ -100,16 +99,15 @@ mod tests { #[test] fn $fn_name() { let buffer = $data; - let (obj, read_len) = <$type>::load(&buffer); - assert!(obj.is_some(), "Load not ok"); + let (obj, read_len) = <$type>::load(&buffer).expect("Load not ok"); assert_eq!(read_len, $expected_len, "Wrong amount read"); - assert_eq!(obj.unwrap(), $expected_load, "Loaded value not as expected"); + assert_eq!(obj, $expected_load, "Loaded value not as expected"); } - } + }; } - test_impl!{string_load_test, [4u8, 0, 0, 0, 116, 101, 115, 116, 0, 128], String, 8, "test"} - test_impl!{ + 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, @@ -121,23 +119,23 @@ mod tests { ] } - test_impl!{bool_true_load_test, [1], bool, 1, true} - test_impl!{bool_false_load_test, [0], bool, 1, false} + 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! {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! {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} + 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} } diff --git a/usdpl-core/src/serdes/mod.rs b/usdpl-core/src/serdes/mod.rs index 8490da0..f911e6d 100644 --- a/usdpl-core/src/serdes/mod.rs +++ b/usdpl-core/src/serdes/mod.rs @@ -6,5 +6,5 @@ mod load_impl; mod primitive; mod traits; -pub use traits::{Dumpable, Loadable}; pub use primitive::Primitive; +pub use traits::{DumpError, Dumpable, LoadError, Loadable}; diff --git a/usdpl-core/src/serdes/primitive.rs b/usdpl-core/src/serdes/primitive.rs index 6d401fe..585937b 100644 --- a/usdpl-core/src/serdes/primitive.rs +++ b/usdpl-core/src/serdes/primitive.rs @@ -1,4 +1,4 @@ -use super::{Loadable, Dumpable}; +use super::{DumpError, Dumpable, LoadError, Loadable}; /// Primitive types supported for communication between the USDPL back- and front-end. /// These are used for sending over the TCP connection. @@ -20,12 +20,12 @@ impl Primitive { 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::F32(_) => 3, + Self::F64(_) => 4, + Self::U32(_) => 5, + Self::U64(_) => 6, + Self::I32(_) => 7, + Self::I64(_) => 8, Self::Bool(_) => 9, Self::Json(_) => 10, } @@ -33,77 +33,49 @@ impl Primitive { } impl Loadable for Primitive { - fn load(buf: &[u8]) -> (Option, usize) { + fn load(buf: &[u8]) -> Result<(Self, usize), LoadError> { if buf.len() == 0 { - return (None, 1); + return Err(LoadError::TooSmallBuffer); } - let mut result: (Option, usize) = match buf[0] { + let mut result: (Self, usize) = match buf[0] { //0 => (None, 0), - 1 => (Some(Self::Empty), 0), - 2 => { - let (obj, len) = String::load(&buf[1..]); - (obj.map(Self::String), len) - }, - 3 => { - let (obj, len) = f32::load(&buf[1..]); - (obj.map(Self::F32), len) - }, - 4 => { - let (obj, len) = f64::load(&buf[1..]); - (obj.map(Self::F64), len) - }, - 5 => { - let (obj, len) = u32::load(&buf[1..]); - (obj.map(Self::U32), len) - }, - 6 => { - let (obj, len) = u64::load(&buf[1..]); - (obj.map(Self::U64), len) - }, - 7 => { - let (obj, len) = i32::load(&buf[1..]); - (obj.map(Self::I32), len) - }, - 8 => { - let (obj, len) = i64::load(&buf[1..]); - (obj.map(Self::I64), len) - }, - 9 => { - let (obj, len) = bool::load(&buf[1..]); - (obj.map(Self::Bool), len) - }, - 10 => { - let (obj, len) = String::load(&buf[1..]); - (obj.map(Self::Json), len) - } - _ => (None, 0) + 1 => (Self::Empty, 0), + 2 => String::load(&buf[1..]).map(|(obj, len)| (Self::String(obj), len))?, + 3 => f32::load(&buf[1..]).map(|(obj, len)| (Self::F32(obj), len))?, + 4 => f64::load(&buf[1..]).map(|(obj, len)| (Self::F64(obj), len))?, + 5 => u32::load(&buf[1..]).map(|(obj, len)| (Self::U32(obj), len))?, + 6 => u64::load(&buf[1..]).map(|(obj, len)| (Self::U64(obj), len))?, + 7 => i32::load(&buf[1..]).map(|(obj, len)| (Self::I32(obj), len))?, + 8 => i64::load(&buf[1..]).map(|(obj, len)| (Self::I64(obj), len))?, + 9 => bool::load(&buf[1..]).map(|(obj, len)| (Self::Bool(obj), len))?, + 10 => String::load(&buf[1..]).map(|(obj, len)| (Self::Json(obj), len))?, + _ => return Err(LoadError::InvalidData), }; result.1 += 1; - result + Ok(result) } } - impl Dumpable for Primitive { - fn dump(&self, buf: &mut [u8]) -> (bool, usize) { + fn dump(&self, buf: &mut [u8]) -> Result { if buf.len() == 0 { - return (false, 0); + return Err(DumpError::TooSmallBuffer); } buf[0] = self.discriminant(); let mut result = match self { - Self::Empty => (true, 0), + Self::Empty => Ok(0), Self::String(s) => s.dump(&mut buf[1..]), - Self::F32(x)=> x.dump(&mut buf[1..]), - Self::F64(x)=> x.dump(&mut buf[1..]), - Self::U32(x)=> x.dump(&mut buf[1..]), - Self::U64(x)=> x.dump(&mut buf[1..]), - Self::I32(x)=> x.dump(&mut buf[1..]), - Self::I64(x)=> x.dump(&mut buf[1..]), - Self::Bool(x)=> x.dump(&mut buf[1..]), + Self::F32(x) => x.dump(&mut buf[1..]), + Self::F64(x) => x.dump(&mut buf[1..]), + Self::U32(x) => x.dump(&mut buf[1..]), + Self::U64(x) => x.dump(&mut buf[1..]), + Self::I32(x) => x.dump(&mut buf[1..]), + Self::I64(x) => x.dump(&mut buf[1..]), + Self::Bool(x) => x.dump(&mut buf[1..]), Self::Json(x) => x.dump(&mut buf[1..]), - }; - result.1 += 1; - result + }?; + result += 1; + Ok(result) } } @@ -134,12 +106,13 @@ mod tests { let data = "Test"; let primitive = Primitive::String(data.to_string()); let mut buffer = [0u8; 128]; - let (ok, write_len) = primitive.dump(&mut buffer); - assert!(ok, "Dump not ok"); - let (obj, read_len) = Primitive::load(&buffer); - assert_eq!(write_len, read_len, "Amount written and amount read do not match"); - assert!(obj.is_some(), "Load not ok"); - if let Some(Primitive::String(result)) = obj { + let write_len = primitive.dump(&mut buffer).expect("Dump not ok"); + let (obj, read_len) = Primitive::load(&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"); @@ -150,12 +123,13 @@ mod tests { fn empty_idempotence_test() { let primitive = Primitive::Empty; let mut buffer = [0u8; 128]; - let (ok, write_len) = primitive.dump(&mut buffer); - assert!(ok, "Dump not ok"); - let (obj, read_len) = Primitive::load(&buffer); - assert_eq!(write_len, read_len, "Amount written and amount read do not match"); - assert!(obj.is_some(), "Load not ok"); - if let Some(Primitive::Empty) = obj { + let write_len = primitive.dump(&mut buffer).expect("Dump not ok"); + let (obj, read_len) = Primitive::load(&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"); diff --git a/usdpl-core/src/serdes/traits.rs b/usdpl-core/src/serdes/traits.rs index 2347558..6a6f804 100644 --- a/usdpl-core/src/serdes/traits.rs +++ b/usdpl-core/src/serdes/traits.rs @@ -1,36 +1,71 @@ -use base64::{encode_config_slice, decode_config_slice, Config}; +use base64::{decode_config_slice, encode_config_slice, Config}; const B64_CONF: Config = Config::new(base64::CharacterSet::Standard, true); +/// Errors from Loadable::load +#[derive(Debug)] +pub enum LoadError { + TooSmallBuffer, + InvalidData, + #[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(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, None should be returned. - fn load(buffer: &[u8]) -> (Option, usize); + fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError>; - fn load_base64(buffer: &[u8]) -> (Option, usize) { + fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> { let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE]; - let len = match decode_config_slice(buffer, B64_CONF, &mut buffer2) { - Ok(len) => len, - Err(_) => return (None, 0), - }; + let len = decode_config_slice(buffer, B64_CONF, &mut buffer2) + .map_err(|_| LoadError::InvalidData)?; Self::load(&buffer2[..len]) } } +/// Errors from Dumpable::dump +#[derive(Debug)] +pub enum DumpError { + TooSmallBuffer, + Unsupported, + #[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(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 [u8]) -> (bool, usize); + fn dump(&self, buffer: &mut [u8]) -> Result; - fn dump_base64(&self, buffer: &mut [u8]) -> (bool, usize) { + fn dump_base64(&self, buffer: &mut [u8]) -> Result { let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE]; - let (ok, len) = self.dump(&mut buffer2); - if !ok { - return (false, len) - } + let len = self.dump(&mut buffer2)?; let len = encode_config_slice(&buffer2[..len], B64_CONF, buffer); - (true, len) + Ok(len) } } diff --git a/usdpl-core/src/socket.rs b/usdpl-core/src/socket.rs index f4f0948..20be3eb 100644 --- a/usdpl-core/src/socket.rs +++ b/usdpl-core/src/socket.rs @@ -1,6 +1,6 @@ -use std::net::{SocketAddrV4, SocketAddr, Ipv4Addr}; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; -use crate::serdes::{Loadable, Dumpable}; +use crate::serdes::{DumpError, Dumpable, LoadError, Loadable}; use crate::{RemoteCall, RemoteCallResponse}; pub const HOST_STR: &str = "127.0.0.1"; @@ -42,56 +42,56 @@ impl Packet { } impl Loadable for Packet { - fn load(buf: &[u8]) -> (Option, usize) { + fn load(buf: &[u8]) -> Result<(Self, usize), LoadError> { if buf.len() == 0 { - return (None, 1); + return Err(LoadError::TooSmallBuffer); } - let mut result: (Option, usize) = match buf[0] { + let mut result: (Self, usize) = match buf[0] { //0 => (None, 0), 1 => { - let (obj, len) = RemoteCall::load(&buf[1..]); - (obj.map(Self::Call), len) - }, - 2 => { - let (obj, len) = RemoteCallResponse::load(&buf[1..]); - (obj.map(Self::CallResponse), len) - }, - 3 => (Some(Self::KeepAlive), 0), - 4 => (Some(Self::Invalid), 0), - 5 => { - let (obj, len) = String::load(&buf[1..]); - (obj.map(Self::Message), len) - }, - 6 => (Some(Self::Unsupported), 0), - 7 => (None, 0), - 8 => { - let (obj, len) = <_>::load(&buf[1..]); - (obj.map(Self::Many), len) + let (obj, len) = RemoteCall::load(&buf[1..])?; + (Self::Call(obj), len) } - _ => (None, 0) + 2 => { + let (obj, len) = RemoteCallResponse::load(&buf[1..])?; + (Self::CallResponse(obj), len) + } + 3 => (Self::KeepAlive, 0), + 4 => (Self::Invalid, 0), + 5 => { + let (obj, len) = String::load(&buf[1..])?; + (Self::Message(obj), len) + } + 6 => (Self::Unsupported, 0), + 7 => return Err(LoadError::InvalidData), + 8 => { + let (obj, len) = <_>::load(&buf[1..])?; + (Self::Many(obj), len) + } + _ => return Err(LoadError::InvalidData), }; result.1 += 1; - result + Ok(result) } } impl Dumpable for Packet { - fn dump(&self, buf: &mut [u8]) -> (bool, usize) { + fn dump(&self, buf: &mut [u8]) -> Result { if buf.len() == 0 { - return (false, 0); + return Err(DumpError::TooSmallBuffer); } buf[0] = self.discriminant(); let mut result = match self { Self::Call(c) => c.dump(&mut buf[1..]), Self::CallResponse(c) => c.dump(&mut buf[1..]), - Self::KeepAlive => (true, 0), - Self::Invalid => (true, 0), + Self::KeepAlive => Ok(0), + Self::Invalid => Ok(0), Self::Message(s) => s.dump(&mut buf[1..]), - Self::Unsupported => (true, 0), - Self::Bad => (false, 0), + Self::Unsupported => Ok(0), + Self::Bad => return Err(DumpError::Unsupported), Self::Many(v) => v.dump(&mut buf[1..]), - }; - result.1 += 1; - result + }?; + result += 1; + Ok(result) } } diff --git a/usdpl-front/Cargo.toml b/usdpl-front/Cargo.toml index d0d0d9d..ea385d4 100644 --- a/usdpl-front/Cargo.toml +++ b/usdpl-front/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "usdpl" -version = "0.3.0" +version = "0.4.0" authors = ["NGnius (Graham) "] edition = "2021" license = "GPL-3.0-only" @@ -43,7 +43,7 @@ web-sys = { version = "0.3", features = [ ]}#["WebSocket", "MessageEvent", "ErrorEvent", "BinaryType"] } js-sys = { version = "0.3" } -usdpl-core = { version = "0.3.0", path = "../usdpl-core" } +usdpl-core = { version = "0.4.0", path = "../usdpl-core" } [dev-dependencies] wasm-bindgen-test = "0.3.13" diff --git a/usdpl-front/src/connection.rs b/usdpl-front/src/connection.rs index 2fd943a..18a0b61 100644 --- a/usdpl-front/src/connection.rs +++ b/usdpl-front/src/connection.rs @@ -6,14 +6,12 @@ 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 js_sys::{ArrayBuffer, DataView, Uint8Array, JsString}; //use wasm_rs_shared_channel::{Expects, spsc::{Receiver, Sender}}; -use usdpl_core::socket; use usdpl_core::serdes::{Dumpable, Loadable, Primitive}; - -use super::imports; +use usdpl_core::socket; pub async fn send_js(packet: socket::Packet, port: u16) -> Result, JsValue> { let mut opts = RequestInit::new(); @@ -23,170 +21,36 @@ pub async fn send_js(packet: socket::Packet, port: u16) -> Result let url = format!("http://localhost:{}/usdpl/call", port); let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let (ok, len) = packet.dump_base64(&mut buffer); - if !ok { - imports::console_error("USDPL error: packet dump failed"); - return Err("Packet dump failed".into()); - } + let len = packet + .dump_base64(&mut buffer) + .map_err(super::convert::str_to_js)?; let string: String = String::from_utf8_lossy(&buffer[..len]).into(); opts.body(Some(&string.into())); let request = Request::new_with_str_and_init(&url, &opts)?; - request - .headers() - .set("Accept", "application/bytes")?; - //.set("Authorization", "wasm TODO_KEY")?; + request.headers().set("Accept", "application/bytes")?; + //.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()?; - - // Convert this other `Promise` into a rust `Future`. let text = JsFuture::from(resp.text()?).await?; - let string: JsString = text.dyn_into()?; - match socket::Packet::load_base64(&string.as_string().unwrap().as_bytes()).0 { - Some(socket::Packet::CallResponse(resp)) => { - Ok(resp.response) - }, + match socket::Packet::load_base64(string.as_string().unwrap().as_bytes()) + .map_err(super::convert::str_to_js)? + .0 + { + socket::Packet::CallResponse(resp) => Ok(resp.response), _ => { - imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url())); - Err("".into()) + //imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url())); + Err(format!( + "Expected call response message from {}, got something else", + resp.url() + ) + .into()) } } } - -/*#[allow(dead_code)] -/// Send packet over a Javascript socket -pub(crate) fn send_js(packet: socket::Packet, port: u16, callback: js_sys::Function) -> bool { - let addr = format!("wss://{}:{}", - "192.168.0.128",//socket::HOST_STR, - port); - - let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let (ok, len) = packet.dump(&mut buffer); - if !ok { - imports::console_error("USDPL error: packet dump failed"); - return false; - } - // copy to JS buffer - let array_buffer = ArrayBuffer::new(len as _); - let dataview = DataView::new(&array_buffer, 0, len); - for i in 0..len { - dataview.set_uint8(i, buffer[i]); - } - - imports::console_log("USDPL: creating WebSocket"); - - let socket = match WebSocket::new("wss://demo.piesocket.com/v3/channel_1?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV¬ify_self"/*addr*/) { - Ok(s) => s, - Err(e) => { - imports::console_error( - &format!("USDPL error: TcpSocket::new(...) failed with error {}", - js_sys::JSON::stringify(&e) - .map(|x| x.as_string().unwrap_or("WTF".into())) - .unwrap_or("unknown error".into()))); - return false; - } - }; - socket.set_binary_type(web_sys::BinaryType::Arraybuffer); - //let (tx, rx) : (Sender<_>, Receiver<_>) = wasm_rs_shared_channel::spsc::channel(socket::PACKET_BUFFER_SIZE as u32 + 4).split(); - - let onmessage_callback = Closure::wrap(Box::new(onmessage_factory(callback)) as Box); - //socket.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); - onmessage_callback.forget(); - - let onerror_callback = Closure::wrap(Box::new(onerror_factory()) as Box); - socket.set_onerror(Some(onerror_callback.as_ref().unchecked_ref())); - onerror_callback.forget(); - - let onopen_callback = Closure::wrap(Box::new(onopen_factory(array_buffer, socket.clone())) as Box); - socket.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); - onopen_callback.forget(); - - imports::console_log("USDPL: socket initialized"); - true -} - -fn onmessage_factory(callback: js_sys::Function) -> impl FnMut(MessageEvent) { - move |e: MessageEvent| { - super::imports::console_log("USDPL: Got message"); - if let Ok(buf) = e.data().dyn_into::() { - //let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let array = Uint8Array::new(&buf); - let len = array.byte_length() as usize; - super::imports::console_log(&format!("USDPL: Received websocket message with length {}", len)); - match socket::Packet::load(array.to_vec().as_slice()).0 { - Some(socket::Packet::CallResponse(resp)) => { - let mut vec: Vec = Vec::with_capacity(resp.response.len()); - for item in resp.response { - vec.push(super::convert::primitive_to_js(item)); - } - let array: js_sys::Array = vec.iter().collect(); - if let Err(e) = callback.call1(&JsValue::NULL, &array) { - imports::console_warn(&format!("USDPL warning: Callback error -- {:?}", e)); - } - }, - _ => { - imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", e.origin())); - } - } - } else { - imports::console_warn(&format!("USDPL warning: Got non-data message from {}", e.origin())); - } - } -} - -fn onerror_factory() -> impl FnMut(ErrorEvent) { - move |e: ErrorEvent| { - imports::console_error(&format!("USDPL error: got socket error {}", e.message())) - } -} - -fn onopen_factory(buffer: ArrayBuffer, socket: WebSocket) -> impl FnMut(JsValue) { - move |_| { - imports::console_log("USDPL: connection opened"); - match socket.send_with_array_buffer(&buffer) { - Ok(_) => {}, - Err(e) => { - imports::console_error(&format!("USDPL error: socket send_with_array_buffer(...) failed -- {:?}", e)); - } - } - } -} - -#[allow(dead_code)] -/// Send packet over a WASM-native TCP socket -pub(crate) fn send_native(packet: socket::Packet, port: u16) -> Option { - let mut socket = match TcpStream::connect(socket::socket_addr(port)) { - Ok(s) => s, - Err(e) => { - imports::console_error(&format!("USDPL error: TcpStream failed to connect with error {}", e)); - return None; - }, - }; - let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let (ok, len) = packet.dump(&mut buffer); - if !ok { - imports::console_error("USDPL error: packet dump failed"); - return None; - } - match socket.write(&buffer[..len]) { - Ok(_) => {}, - Err(e) => { - imports::console_error(&format!("USDPL error: socket write failed with error {}", e)); - return None; - } - } - let len = match socket.read(&mut buffer) { - Ok(len) => len, - Err(e) => { - imports::console_error(&format!("USDPL error: socket read failed with error {}", e)); - return None; - } - }; - socket::Packet::load(&buffer[..len]).0 -}*/ diff --git a/usdpl-front/src/convert.rs b/usdpl-front/src/convert.rs index 847e976..b074ae6 100644 --- a/usdpl-front/src/convert.rs +++ b/usdpl-front/src/convert.rs @@ -1,5 +1,6 @@ +use js_sys::JsString; +use js_sys::JSON::{parse, stringify}; use wasm_bindgen::prelude::JsValue; -use js_sys::JSON::{stringify, parse}; use usdpl_core::serdes::Primitive; @@ -7,12 +8,12 @@ pub(crate) fn primitive_to_js(primitive: Primitive) -> JsValue { match primitive { Primitive::Empty => JsValue::null(), Primitive::String(s) => JsValue::from_str(&s), - Primitive::F32(f)=> JsValue::from_f64(f as _), - Primitive::F64(f)=> JsValue::from_f64(f), - Primitive::U32(f)=> JsValue::from_f64(f as _), - Primitive::U64(f)=> JsValue::from_f64(f as _), - Primitive::I32(f)=> JsValue::from_f64(f as _), - Primitive::I64(f)=> JsValue::from_f64(f as _), + Primitive::F32(f) => JsValue::from_f64(f as _), + Primitive::F64(f) => JsValue::from_f64(f), + Primitive::U32(f) => JsValue::from_f64(f as _), + Primitive::U64(f) => JsValue::from_f64(f as _), + Primitive::I32(f) => JsValue::from_f64(f as _), + Primitive::I64(f) => JsValue::from_f64(f as _), Primitive::Bool(b) => JsValue::from_bool(b), Primitive::Json(s) => parse(&s).ok().unwrap_or(JsValue::from_str(&s)), } @@ -33,3 +34,7 @@ pub(crate) fn js_to_primitive(val: JsValue) -> Primitive { Primitive::Empty } } + +pub(crate) fn str_to_js(s: S) -> JsString { + s.to_string().into() +} diff --git a/usdpl-front/src/imports.rs b/usdpl-front/src/imports.rs index e7d3ec7..92f9f9a 100644 --- a/usdpl-front/src/imports.rs +++ b/usdpl-front/src/imports.rs @@ -1,7 +1,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] -extern { +extern "C" { #[wasm_bindgen(js_namespace = console, js_name = log)] pub fn console_log(s: &str); diff --git a/usdpl-front/src/lib.rs b/usdpl-front/src/lib.rs index bcd2f51..d47a11c 100644 --- a/usdpl-front/src/lib.rs +++ b/usdpl-front/src/lib.rs @@ -8,17 +8,14 @@ mod connection; mod convert; mod imports; -use wasm_bindgen::prelude::*; use js_sys::Array; +use wasm_bindgen::prelude::*; use usdpl_core::{socket::Packet, RemoteCall}; //const REMOTE_CALL_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); //const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(31337); -static mut CTX: UsdplContext = UsdplContext { - port: 31337, - id: 1, -}; +static mut CTX: UsdplContext = UsdplContext { port: 31337, id: 1 }; #[wasm_bindgen] #[derive(Debug)] @@ -28,12 +25,14 @@ pub struct UsdplContext { } fn get_port() -> u16 { - unsafe {CTX.port} + unsafe { CTX.port } } fn increment_id() -> u64 { - let current_id = unsafe {CTX.id}; - unsafe {CTX.id += 1;} + let current_id = unsafe { CTX.id }; + unsafe { + CTX.id += 1; + } current_id } @@ -49,29 +48,25 @@ pub fn init_usdpl(port: u16) { console_error_panic_hook::set_once(); //REMOTE_PORT.store(port, std::sync::atomic::Ordering::SeqCst); unsafe { - CTX = UsdplContext { - port: port, - id: 1, - }; + CTX = UsdplContext { port: port, id: 1 }; } } /// Get the targeted plugin framework, or "any" if unknown #[wasm_bindgen] pub fn target() -> String { - #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] - {"decky".to_string()} - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - {"crankshaft".to_string()} - #[cfg(not(any(feature = "decky", feature = "crankshaft")))] - {"any".to_string()} + usdpl_core::api::Platform::current().to_string() } /// 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 { - imports::console_log(&format!("call_backend({}, [params; {}])", name, parameters.len())); + imports::console_log(&format!( + "call_backend({}, [params; {}])", + name, + parameters.len() + )); let next_id = increment_id(); let mut params = Vec::with_capacity(parameters.len()); for val in parameters { @@ -79,11 +74,15 @@ pub async fn call_backend(name: String, parameters: Vec) -> JsValue { } let port = get_port(); imports::console_log(&format!("USDPL: Got port {}", port)); - let results = connection::send_js(Packet::Call(RemoteCall { - id: next_id, - function: name.clone(), - parameters: params, - }), port).await; + let results = connection::send_js( + Packet::Call(RemoteCall { + id: next_id, + function: name.clone(), + parameters: params, + }), + port, + ) + .await; let results = match results { Ok(x) => x, Err(e) => {