Minor refactor and code cleanup to improve ergonomics

This commit is contained in:
NGnius 2022-06-15 21:18:24 -04:00
parent 63ed7fa5d1
commit 0a5323c927
25 changed files with 467 additions and 713 deletions

7
Cargo.lock generated
View file

@ -926,7 +926,7 @@ dependencies = [
[[package]] [[package]]
name = "usdpl" name = "usdpl"
version = "0.3.0" version = "0.4.0"
dependencies = [ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"js-sys", "js-sys",
@ -940,10 +940,9 @@ dependencies = [
[[package]] [[package]]
name = "usdpl-back" name = "usdpl-back"
version = "0.3.0" version = "0.4.0"
dependencies = [ dependencies = [
"bytes", "bytes",
"lazy_static",
"tokio", "tokio",
"usdpl-core", "usdpl-core",
"warp", "warp",
@ -951,7 +950,7 @@ dependencies = [
[[package]] [[package]]
name = "usdpl-core" name = "usdpl-core"
version = "0.1.0" version = "0.4.0"
dependencies = [ dependencies = [
"base64", "base64",
] ]

View file

@ -7,9 +7,11 @@ $0 [decky|crankshaft|<nothing>]"
elif [ "$1" == "decky" ]; then elif [ "$1" == "decky" ]; then
echo "Building back & front for decky framework" echo "Building back & front for decky framework"
# usdpl-back # usdpl-back
echo "...Running usdpl-back build..."
cd ./usdpl-back cd ./usdpl-back
./build.sh decky ./build.sh decky
# usdpl-front # usdpl-front
echo "...Running usdpl-front build..."
cd ../usdpl-front cd ../usdpl-front
./build.sh decky ./build.sh decky
cd .. cd ..
@ -35,11 +37,11 @@ else
# usdpl-back # usdpl-back
echo "...Running usdpl-back build..." echo "...Running usdpl-back build..."
cd ./usdpl-back cd ./usdpl-back
cargo build --release ./build.sh
# usdpl-front # usdpl-front
echo "...Running usdpl-front build..." echo "...Running usdpl-front build..."
cd ../usdpl-front cd ../usdpl-front
./build.sh crankshaft ./build.sh
cd .. cd ..
echo "Built usdpl back & front for any" echo "Built usdpl back & front for any"
fi fi

View file

@ -1,6 +1,6 @@
[package] [package]
name = "usdpl-back" name = "usdpl-back"
version = "0.3.0" version = "0.4.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-only" license = "GPL-3.0-only"
repository = "https://github.com/NGnius/usdpl-rs" repository = "https://github.com/NGnius/usdpl-rs"
@ -13,10 +13,9 @@ decky = []
crankshaft = [] crankshaft = []
[dependencies] [dependencies]
usdpl-core = { version = "0.3.0", path = "../usdpl-core" } usdpl-core = { version = "0.4.0", path = "../usdpl-core" }
# HTTP web framework # HTTP web framework
warp = { version = "0.3" } warp = { version = "0.3" }
bytes = { version = "1.1" } bytes = { version = "1.1" }
tokio = { version = "1.19", features = ["rt", "rt-multi-thread"] } tokio = { version = "1.19", features = ["rt", "rt-multi-thread"] }
lazy_static = { version = "1.4" }

View file

@ -0,0 +1,5 @@
use usdpl_core::serdes::Primitive;
pub trait Callable: Send + Sync {
fn call(&mut self, params: Vec<Primitive>) -> Vec<Primitive>;
}

View file

@ -1,24 +1,19 @@
//use std::net::{TcpListener, TcpStream, SocketAddr};
use std::collections::HashMap; use std::collections::HashMap;
//use std::sync::Arc; use std::sync::Arc;
//use std::io::{Read, Write}; use std::sync::Mutex;
use lazy_static::lazy_static;
use warp::Filter; use warp::Filter;
use usdpl_core::serdes::{Dumpable, Loadable, Primitive}; use usdpl_core::serdes::{Dumpable, Loadable};
use usdpl_core::{RemoteCallResponse, socket}; use usdpl_core::{socket, RemoteCallResponse};
type Callable = Box<(dyn (Fn(Vec<Primitive>) -> Vec<Primitive>) + Send + Sync)>; use super::Callable;
lazy_static! { type WrappedCallable = Arc<Mutex<Box<dyn Callable>>>; // thread-safe, cloneable Callable
static ref CALLS: std::sync::Mutex<HashMap<String, Callable>> = std::sync::Mutex::new(HashMap::new());
}
/// Back-end instance for interacting with the front-end /// Back-end instance for interacting with the front-end
pub struct Instance { pub struct Instance {
//calls: HashMap<String, Callable>, calls: HashMap<String, WrappedCallable>,
port: u16, port: u16,
} }
@ -27,86 +22,25 @@ impl Instance {
#[inline] #[inline]
pub fn new(port_usdpl: u16) -> Self { pub fn new(port_usdpl: u16) -> Self {
Instance { Instance {
//calls: HashMap::new(), calls: HashMap::new(),
port: port_usdpl, port: port_usdpl,
} }
} }
/// Register a function which can be invoked by the front-end /// Register a function which can be invoked by the front-end
pub fn register<S: std::convert::Into<String>, F: (Fn(Vec<Primitive>) -> Vec<Primitive>) + Send + Sync + 'static>(&mut self, name: S, f: F) -> &mut Self { pub fn register<S: std::convert::Into<String>, F: Callable + 'static>(
CALLS.lock().unwrap().insert(name.into(), Box::new(f)); &mut self,
//self.calls.insert(name.into(), Box::new(f)); 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 self
} }
/*fn handle_packet<const ERROR: bool>(&mut self, packet: socket::Packet, buffer: &mut [u8], peer_addr: &SocketAddr) -> Result<String, super::ServerError> { pub fn serve(&self) -> 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::<ERROR>(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 {
let result = self.serve_internal(); let result = self.serve_internal();
//println!("Stopping server due to serve_internal returning a result");
tokio::runtime::Builder::new_multi_thread() tokio::runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
.build() .build()
@ -114,13 +48,18 @@ impl Instance {
.block_on(result) .block_on(result)
} }
fn handle_call(packet: socket::Packet) -> socket::Packet { fn handle_call(
println!("Got packet"); packet: socket::Packet,
handlers: &HashMap<String, WrappedCallable>,
) -> socket::Packet {
match packet { match packet {
socket::Packet::Call(call) => { 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) { 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 { socket::Packet::CallResponse(RemoteCallResponse {
id: call.id, id: call.id,
response: result, response: result,
@ -128,51 +67,61 @@ impl Instance {
} else { } else {
socket::Packet::Invalid socket::Packet::Invalid
} }
}, }
socket::Packet::Many(packets) => { socket::Packet::Many(packets) => {
let mut result = Vec::with_capacity(packets.len()); let mut result = Vec::with_capacity(packets.len());
for packet in packets { for packet in packets {
result.push(Self::handle_call(packet)); result.push(Self::handle_call(packet, handlers));
} }
socket::Packet::Many(result) socket::Packet::Many(result)
}, }
_ => socket::Packet::Invalid, _ => socket::Packet::Invalid,
} }
} }
/// Receive and execute callbacks forever /// Receive and execute callbacks forever
pub async fn serve_internal(self) -> super::ServerResult { pub async fn serve_internal(&self) -> Result<(), ()> {
//let handlers = self.calls; let handlers = self.calls.clone();
//self.calls = HashMap::new(); //self.calls = HashMap::new();
let calls = warp::post() let calls = warp::post()
.and(warp::path("usdpl/call")) .and(warp::path!("usdpl" / "call"))
.and(warp::body::content_length_limit((socket::PACKET_BUFFER_SIZE * 2) as _)) .and(warp::body::content_length_limit(
(socket::PACKET_BUFFER_SIZE * 2) as _,
))
.and(warp::body::bytes()) .and(warp::body::bytes())
.map(|data: bytes::Bytes| { .map(move |data: bytes::Bytes| {
let (obj_maybe, _) = socket::Packet::load_base64(&data); 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(),
)
}
};
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
if let Some(packet) = obj_maybe { let response = Self::handle_call(packet, &handlers);
let response = Self::handle_call(packet); let len = match response.dump_base64(&mut buffer) {
let (ok, len) = response.dump_base64(&mut buffer); Ok(x) => x,
if !ok { Err(_) => {
eprintln!("Failed to dump response packet"); return warp::reply::with_status(
warp::reply::with_status(warp::http::Response::builder() warp::http::Response::builder()
.body("Failed to dump response packet".to_string()), warp::http::StatusCode::from_u16(400).unwrap()) .body("Failed to dump response packet".to_string()),
} else { warp::http::StatusCode::from_u16(500).unwrap(),
)
}
};
let string: String = String::from_utf8_lossy(&buffer[..len]).into(); let string: String = String::from_utf8_lossy(&buffer[..len]).into();
warp::reply::with_status(warp::http::Response::builder() warp::reply::with_status(
.body(string), warp::http::StatusCode::from_u16(200).unwrap()) warp::http::Response::builder().body(string),
} warp::http::StatusCode::from_u16(200).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())
}
}) })
.map(|reply| { .map(|reply| warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*"));
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; warp::serve(calls).run(([127, 0, 0, 1], self.port)).await;
Ok(()) Ok(())
} }
@ -180,110 +129,6 @@ impl Instance {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
//use std::net::TcpStream; #[allow(unused_imports)]
//use super::*; 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::<true>()
});
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::<true>()
.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::<true>()
});
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");
}
}*/
} }

View file

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

View file

@ -1,6 +1,6 @@
[package] [package]
name = "usdpl-core" name = "usdpl-core"
version = "0.3.0" version = "0.4.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-only" license = "GPL-3.0-only"
repository = "https://github.com/NGnius/usdpl-rs" repository = "https://github.com/NGnius/usdpl-rs"

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,3 @@
mod target;
pub use target::Platform;

View file

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

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -2,7 +2,25 @@
//! This contains serialization functionality and networking datatypes. //! This contains serialization functionality and networking datatypes.
mod remote_call; 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 serdes;
pub mod socket;
pub use remote_call::{RemoteCall, RemoteCallResponse}; 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::*;
}

View file

@ -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 /// Remote call packet representing a function to call on the back-end, sent from the front-end
pub struct RemoteCall { pub struct RemoteCall {
@ -8,42 +8,27 @@ pub struct RemoteCall {
} }
impl Loadable for RemoteCall { impl Loadable for RemoteCall {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
let (id_num, len0) = u64::load(buffer); let (id_num, len0) = u64::load(buffer)?;
if id_num.is_none() { let (function_name, len1) = String::load(&buffer[len0..])?;
return (None, len0); let (params, len2) = Vec::<Primitive>::load(&buffer[len0 + len1..])?;
} Ok((
let (function_name, len1) = String::load(&buffer[len0..]); Self {
if function_name.is_none() { id: id_num,
return (None, len1); function: function_name,
} parameters: params,
let (params, len2) = Vec::<Primitive>::load(&buffer[len0+len1..]); },
if params.is_none() { len0 + len1 + len2,
return (None, len1 + len2); ))
}
(
Some(Self {
id: id_num.unwrap(),
function: function_name.unwrap(),
parameters: params.unwrap(),
}),
len0 + len1 + len2
)
} }
} }
impl Dumpable for RemoteCall { impl Dumpable for RemoteCall {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
let (ok0, len0) = self.id.dump(buffer); let len0 = self.id.dump(buffer)?;
if !ok0 { let len1 = self.function.dump(&mut buffer[len0..])?;
return (ok0, len0); let len2 = self.parameters.dump(&mut buffer[len0 + len1..])?;
} Ok(len0 + len1 + len2)
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)
} }
} }
@ -54,33 +39,23 @@ pub struct RemoteCallResponse {
} }
impl Loadable for RemoteCallResponse { impl Loadable for RemoteCallResponse {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
let (id_num, len0) = u64::load(buffer); let (id_num, len0) = u64::load(buffer)?;
if id_num.is_none() { let (response_var, len1) = Vec::<Primitive>::load(&buffer[len0..])?;
return (None, len0); Ok((
} Self {
let (response_var, len1) = Vec::<Primitive>::load(&buffer[len0..]); id: id_num,
if response_var.is_none() { response: response_var,
return (None, len1); },
} len0 + len1,
( ))
Some(Self {
id: id_num.unwrap(),
response: response_var.unwrap(),
}),
len0 + len1
)
} }
} }
impl Dumpable for RemoteCallResponse { impl Dumpable for RemoteCallResponse {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
let (ok0, len0) = self.id.dump(buffer); let len0 = self.id.dump(buffer)?;
if !ok0 { let len1 = self.response.dump(&mut buffer[len0..])?;
return (ok0, len0); Ok(len0 + len1)
}
let (ok1, len1) = self.response.dump(&mut buffer[len0..]);
(ok1, len0 + len1)
} }
} }

View file

@ -1,91 +1,88 @@
use super::Dumpable; use super::{DumpError, Dumpable};
impl Dumpable for String { impl Dumpable for String {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
let str_bytes = self.as_bytes(); let str_bytes = self.as_bytes();
let len_bytes = (str_bytes.len() as u32).to_le_bytes(); let len_bytes = (str_bytes.len() as u32).to_le_bytes();
let total_len = str_bytes.len() + 4; let total_len = str_bytes.len() + 4;
if buffer.len() < total_len { if buffer.len() < total_len {
return (false, 0); return Err(DumpError::TooSmallBuffer);
} }
(&mut buffer[..4]).copy_from_slice(&len_bytes); (&mut buffer[..4]).copy_from_slice(&len_bytes);
(&mut buffer[4..total_len]).copy_from_slice(str_bytes); (&mut buffer[4..total_len]).copy_from_slice(str_bytes);
(true, total_len) Ok(total_len)
} }
} }
impl<T: Dumpable> Dumpable for Vec<T> { impl<T: Dumpable> Dumpable for Vec<T> {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
let len_bytes = (self.len() as u32).to_le_bytes(); let len_bytes = (self.len() as u32).to_le_bytes();
(&mut buffer[..4]).copy_from_slice(&len_bytes); (&mut buffer[..4]).copy_from_slice(&len_bytes);
let mut cursor = 4; let mut cursor = 4;
for obj in self.iter() { for obj in self.iter() {
let (ok, len) = obj.dump(&mut buffer[cursor..]); let len = obj.dump(&mut buffer[cursor..])?;
cursor += len; cursor += len;
if !ok {
return (false, cursor);
} }
} Ok(cursor)
(true, cursor)
} }
} }
impl Dumpable for bool { impl Dumpable for bool {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
if buffer.len() < 1 { if buffer.len() < 1 {
return (false, 0); return Err(DumpError::TooSmallBuffer);
} }
buffer[0] = *self as u8; buffer[0] = *self as u8;
(true, 1) Ok(1)
} }
} }
impl Dumpable for u8 { impl Dumpable for u8 {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
if buffer.len() < 1 { if buffer.len() < 1 {
return (false, 0); return Err(DumpError::TooSmallBuffer);
} }
buffer[0] = *self; buffer[0] = *self;
(true, 1) Ok(1)
} }
} }
impl Dumpable for i8 { impl Dumpable for i8 {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
if buffer.len() < 1 { if buffer.len() < 1 {
return (false, 0); return Err(DumpError::TooSmallBuffer);
} }
buffer[0] = self.to_le_bytes()[0]; buffer[0] = self.to_le_bytes()[0];
(true, 1) Ok(1)
} }
} }
macro_rules! int_impl { macro_rules! int_impl {
($type:ty, $size:literal) => { ($type:ty, $size:literal) => {
impl Dumpable for $type { impl Dumpable for $type {
fn dump(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
if buffer.len() < $size { if buffer.len() < $size {
return (false, 0); return Err(DumpError::TooSmallBuffer);
} }
(&mut buffer[..$size]).copy_from_slice(&self.to_le_bytes()); (&mut buffer[..$size]).copy_from_slice(&self.to_le_bytes());
(true, $size) Ok($size)
}
} }
} }
};
} }
int_impl!{u16, 2} int_impl! {u16, 2}
int_impl!{u32, 4} int_impl! {u32, 4}
int_impl!{u64, 8} int_impl! {u64, 8}
int_impl!{u128, 16} int_impl! {u128, 16}
int_impl!{i16, 2} int_impl! {i16, 2}
int_impl!{i32, 4} int_impl! {i32, 4}
int_impl!{i64, 8} int_impl! {i64, 8}
int_impl!{i128, 16} int_impl! {i128, 16}
int_impl!{f32, 4} int_impl! {f32, 4}
int_impl!{f64, 8} int_impl! {f64, 8}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -97,17 +94,16 @@ mod tests {
fn $fn_name() { fn $fn_name() {
let data = $data; let data = $data;
let mut buffer = [0u8; 128]; let mut buffer = [0u8; 128];
let (ok, write_len) = data.dump(&mut buffer); let write_len = data.dump(&mut buffer).expect("Dump not ok");
assert!(ok, "Dump not ok");
assert_eq!(write_len, $expected_len, "Wrong amount written"); assert_eq!(write_len, $expected_len, "Wrong amount written");
assert_eq!(&buffer[..write_len], $expected_dump); 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_dump_test,
vec![ vec![
"".to_string(), "".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] &[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_true_dump_test, true, 1, &[1]}
test_impl!{bool_false_dump_test, false, 1, &[0]} test_impl! {bool_false_dump_test, false, 1, &[0]}
// testing macro-generated code isn't particularly useful, but do it anyway // testing macro-generated code isn't particularly useful, but do it anyway
test_impl!{u8_dump_test, 42u8, 1, &[42]} test_impl! {u8_dump_test, 42u8, 1, &[42]}
test_impl!{u16_dump_test, 42u16, 2, &[42, 0]} test_impl! {u16_dump_test, 42u16, 2, &[42, 0]}
test_impl!{u32_dump_test, 42u32, 4, &[42, 0, 0, 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! {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! {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! {i8_dump_test, 42i8, 1, &[42]}
test_impl!{i16_dump_test, 42i16, 2, &[42, 0]} test_impl! {i16_dump_test, 42i16, 2, &[42, 0]}
test_impl!{i32_dump_test, 42i32, 4, &[42, 0, 0, 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! {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! {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! {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! {f64_dump_test, 42f64, 8, &[0, 0, 0, 0, 0, 0, 69, 64]}
} }

View file

@ -1,21 +1,24 @@
use super::Loadable; use super::{LoadError, Loadable};
impl Loadable for String { impl Loadable for String {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
if buffer.len() < 4 { if buffer.len() < 4 {
return (None, 0); return Err(LoadError::TooSmallBuffer);
} }
let mut u32_bytes: [u8; 4] = [u8::MAX; 4]; let mut u32_bytes: [u8; 4] = [u8::MAX; 4];
u32_bytes.copy_from_slice(&buffer[..4]); u32_bytes.copy_from_slice(&buffer[..4]);
let str_size = u32::from_le_bytes(u32_bytes) as usize; 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<T: Loadable> Loadable for Vec<T> { impl<T: Loadable> Loadable for Vec<T> {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
if buffer.len() < 4 { if buffer.len() < 4 {
return (None, 0); return Err(LoadError::TooSmallBuffer);
} }
let mut u32_bytes: [u8; 4] = [u8::MAX; 4]; let mut u32_bytes: [u8; 4] = [u8::MAX; 4];
u32_bytes.copy_from_slice(&buffer[..4]); u32_bytes.copy_from_slice(&buffer[..4]);
@ -23,73 +26,69 @@ impl<T: Loadable> Loadable for Vec<T> {
let mut cursor = 4; let mut cursor = 4;
let mut items = Vec::with_capacity(count); let mut items = Vec::with_capacity(count);
for _ in 0..count { for _ in 0..count {
let (obj, len) = T::load(&buffer[cursor..]); let (obj, len) = T::load(&buffer[cursor..])?;
cursor += len; cursor += len;
if let Some(obj) = obj {
items.push(obj); items.push(obj);
} else {
return (None, cursor);
} }
} Ok((items, cursor))
(Some(items), cursor)
} }
} }
impl Loadable for bool { impl Loadable for bool {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
if buffer.len() < 1 { 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 { impl Loadable for u8 {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
if buffer.len() < 1 { if buffer.len() < 1 {
return (None, 0); return Err(LoadError::TooSmallBuffer);
} }
(Some(buffer[0]), 1) Ok((buffer[0], 1))
} }
} }
impl Loadable for i8 { impl Loadable for i8 {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
if buffer.len() < 1 { 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 { macro_rules! int_impl {
($type:ty, $size:literal) => { ($type:ty, $size:literal) => {
impl Loadable for $type { impl Loadable for $type {
fn load(buffer: &[u8]) -> (Option<Self>, usize) { fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
if buffer.len() < $size { if buffer.len() < $size {
return (None, 0); return Err(LoadError::TooSmallBuffer);
} }
let mut bytes: [u8; $size] = [u8::MAX; $size]; let mut bytes: [u8; $size] = [u8::MAX; $size];
bytes.copy_from_slice(&buffer[..$size]); bytes.copy_from_slice(&buffer[..$size]);
let i = <$type>::from_le_bytes(bytes); let i = <$type>::from_le_bytes(bytes);
(Some(i), $size) Ok((i, $size))
}
} }
} }
};
} }
int_impl!{u16, 2} int_impl! {u16, 2}
int_impl!{u32, 4} int_impl! {u32, 4}
int_impl!{u64, 8} int_impl! {u64, 8}
int_impl!{u128, 16} int_impl! {u128, 16}
int_impl!{i16, 2} int_impl! {i16, 2}
int_impl!{i32, 4} int_impl! {i32, 4}
int_impl!{i64, 8} int_impl! {i64, 8}
int_impl!{i128, 16} int_impl! {i128, 16}
int_impl!{f32, 4} int_impl! {f32, 4}
int_impl!{f64, 8} int_impl! {f64, 8}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -100,16 +99,15 @@ mod tests {
#[test] #[test]
fn $fn_name() { fn $fn_name() {
let buffer = $data; let buffer = $data;
let (obj, read_len) = <$type>::load(&buffer); let (obj, read_len) = <$type>::load(&buffer).expect("Load not ok");
assert!(obj.is_some(), "Load not ok");
assert_eq!(read_len, $expected_len, "Wrong amount read"); 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! {string_load_test, [4u8, 0, 0, 0, 116, 101, 115, 116, 0, 128], String, 8, "test"}
test_impl!{ test_impl! {
vec_load_test, 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], [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>, Vec<String>,
@ -121,23 +119,23 @@ mod tests {
] ]
} }
test_impl!{bool_true_load_test, [1], bool, 1, true} test_impl! {bool_true_load_test, [1], bool, 1, true}
test_impl!{bool_false_load_test, [0], bool, 1, false} test_impl! {bool_false_load_test, [0], bool, 1, false}
// testing macro-generated code isn't particularly useful, but do it anyway // testing macro-generated code isn't particularly useful, but do it anyway
test_impl!{u8_load_test, [42], u8, 1, 42u8} test_impl! {u8_load_test, [42], u8, 1, 42u8}
test_impl!{u16_load_test, [42, 0], u16, 2, 42u16} test_impl! {u16_load_test, [42, 0], u16, 2, 42u16}
test_impl!{u32_load_test, [42, 0, 0, 0], u32, 4, 42u32} 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! {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! {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! {i8_load_test, [42], i8, 1, 42i8}
test_impl!{i16_load_test, [42, 0], i16, 2, 42i16} test_impl! {i16_load_test, [42, 0], i16, 2, 42i16}
test_impl!{i32_load_test, [42, 0, 0, 0], i32, 4, 42i32} 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! {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! {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! {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! {f64_load_test, [0, 0, 0, 0, 0, 0, 69, 64], f64, 8, 42f64}
} }

View file

@ -6,5 +6,5 @@ mod load_impl;
mod primitive; mod primitive;
mod traits; mod traits;
pub use traits::{Dumpable, Loadable};
pub use primitive::Primitive; pub use primitive::Primitive;
pub use traits::{DumpError, Dumpable, LoadError, Loadable};

View file

@ -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. /// Primitive types supported for communication between the USDPL back- and front-end.
/// These are used for sending over the TCP connection. /// These are used for sending over the TCP connection.
@ -20,12 +20,12 @@ impl Primitive {
match self { match self {
Self::Empty => 1, Self::Empty => 1,
Self::String(_) => 2, Self::String(_) => 2,
Self::F32(_)=> 3, Self::F32(_) => 3,
Self::F64(_)=> 4, Self::F64(_) => 4,
Self::U32(_)=> 5, Self::U32(_) => 5,
Self::U64(_)=> 6, Self::U64(_) => 6,
Self::I32(_)=> 7, Self::I32(_) => 7,
Self::I64(_)=> 8, Self::I64(_) => 8,
Self::Bool(_) => 9, Self::Bool(_) => 9,
Self::Json(_) => 10, Self::Json(_) => 10,
} }
@ -33,77 +33,49 @@ impl Primitive {
} }
impl Loadable for Primitive { impl Loadable for Primitive {
fn load(buf: &[u8]) -> (Option<Self>, usize) { fn load(buf: &[u8]) -> Result<(Self, usize), LoadError> {
if buf.len() == 0 { if buf.len() == 0 {
return (None, 1); return Err(LoadError::TooSmallBuffer);
} }
let mut result: (Option<Self>, usize) = match buf[0] { let mut result: (Self, usize) = match buf[0] {
//0 => (None, 0), //0 => (None, 0),
1 => (Some(Self::Empty), 0), 1 => (Self::Empty, 0),
2 => { 2 => String::load(&buf[1..]).map(|(obj, len)| (Self::String(obj), len))?,
let (obj, len) = String::load(&buf[1..]); 3 => f32::load(&buf[1..]).map(|(obj, len)| (Self::F32(obj), len))?,
(obj.map(Self::String), 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))?,
3 => { 6 => u64::load(&buf[1..]).map(|(obj, len)| (Self::U64(obj), len))?,
let (obj, len) = f32::load(&buf[1..]); 7 => i32::load(&buf[1..]).map(|(obj, len)| (Self::I32(obj), len))?,
(obj.map(Self::F32), 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))?,
4 => { 10 => String::load(&buf[1..]).map(|(obj, len)| (Self::Json(obj), len))?,
let (obj, len) = f64::load(&buf[1..]); _ => return Err(LoadError::InvalidData),
(obj.map(Self::F64), len)
},
5 => {
let (obj, len) = u32::load(&buf[1..]);
(obj.map(Self::U32), len)
},
6 => {
let (obj, len) = u64::load(&buf[1..]);
(obj.map(Self::U64), len)
},
7 => {
let (obj, len) = i32::load(&buf[1..]);
(obj.map(Self::I32), len)
},
8 => {
let (obj, len) = i64::load(&buf[1..]);
(obj.map(Self::I64), len)
},
9 => {
let (obj, len) = bool::load(&buf[1..]);
(obj.map(Self::Bool), len)
},
10 => {
let (obj, len) = String::load(&buf[1..]);
(obj.map(Self::Json), len)
}
_ => (None, 0)
}; };
result.1 += 1; result.1 += 1;
result Ok(result)
} }
} }
impl Dumpable for Primitive { impl Dumpable for Primitive {
fn dump(&self, buf: &mut [u8]) -> (bool, usize) { fn dump(&self, buf: &mut [u8]) -> Result<usize, DumpError> {
if buf.len() == 0 { if buf.len() == 0 {
return (false, 0); return Err(DumpError::TooSmallBuffer);
} }
buf[0] = self.discriminant(); buf[0] = self.discriminant();
let mut result = match self { let mut result = match self {
Self::Empty => (true, 0), Self::Empty => Ok(0),
Self::String(s) => s.dump(&mut buf[1..]), Self::String(s) => s.dump(&mut buf[1..]),
Self::F32(x)=> x.dump(&mut buf[1..]), Self::F32(x) => x.dump(&mut buf[1..]),
Self::F64(x)=> x.dump(&mut buf[1..]), Self::F64(x) => x.dump(&mut buf[1..]),
Self::U32(x)=> x.dump(&mut buf[1..]), Self::U32(x) => x.dump(&mut buf[1..]),
Self::U64(x)=> x.dump(&mut buf[1..]), Self::U64(x) => x.dump(&mut buf[1..]),
Self::I32(x)=> x.dump(&mut buf[1..]), Self::I32(x) => x.dump(&mut buf[1..]),
Self::I64(x)=> x.dump(&mut buf[1..]), Self::I64(x) => x.dump(&mut buf[1..]),
Self::Bool(x)=> x.dump(&mut buf[1..]), Self::Bool(x) => x.dump(&mut buf[1..]),
Self::Json(x) => x.dump(&mut buf[1..]), Self::Json(x) => x.dump(&mut buf[1..]),
}; }?;
result.1 += 1; result += 1;
result Ok(result)
} }
} }
@ -134,12 +106,13 @@ mod tests {
let data = "Test"; let data = "Test";
let primitive = Primitive::String(data.to_string()); let primitive = Primitive::String(data.to_string());
let mut buffer = [0u8; 128]; let mut buffer = [0u8; 128];
let (ok, write_len) = primitive.dump(&mut buffer); let write_len = primitive.dump(&mut buffer).expect("Dump not ok");
assert!(ok, "Dump not ok"); let (obj, read_len) = Primitive::load(&buffer).expect("Load not ok");
let (obj, read_len) = Primitive::load(&buffer); assert_eq!(
assert_eq!(write_len, read_len, "Amount written and amount read do not match"); write_len, read_len,
assert!(obj.is_some(), "Load not ok"); "Amount written and amount read do not match"
if let Some(Primitive::String(result)) = obj { );
if let Primitive::String(result) = obj {
assert_eq!(data, result, "Data written and read does not match"); assert_eq!(data, result, "Data written and read does not match");
} else { } else {
panic!("Read non-string primitive"); panic!("Read non-string primitive");
@ -150,12 +123,13 @@ mod tests {
fn empty_idempotence_test() { fn empty_idempotence_test() {
let primitive = Primitive::Empty; let primitive = Primitive::Empty;
let mut buffer = [0u8; 128]; let mut buffer = [0u8; 128];
let (ok, write_len) = primitive.dump(&mut buffer); let write_len = primitive.dump(&mut buffer).expect("Dump not ok");
assert!(ok, "Dump not ok"); let (obj, read_len) = Primitive::load(&buffer).expect("Load not ok");
let (obj, read_len) = Primitive::load(&buffer); assert_eq!(
assert_eq!(write_len, read_len, "Amount written and amount read do not match"); write_len, read_len,
assert!(obj.is_some(), "Load not ok"); "Amount written and amount read do not match"
if let Some(Primitive::Empty) = obj { );
if let Primitive::Empty = obj {
//assert_eq!(data, result, "Data written and read does not match"); //assert_eq!(data, result, "Data written and read does not match");
} else { } else {
panic!("Read non-string primitive"); panic!("Read non-string primitive");

View file

@ -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); 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 /// Load an object from the buffer
pub trait Loadable: Sized { pub trait Loadable: Sized {
/// Read the buffer, building the object and returning the amount of bytes read. /// Read the buffer, building the object and returning the amount of bytes read.
/// If anything is wrong with the buffer, None should be returned. /// If anything is wrong with the buffer, None should be returned.
fn load(buffer: &[u8]) -> (Option<Self>, usize); fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError>;
fn load_base64(buffer: &[u8]) -> (Option<Self>, usize) { fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE]; let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE];
let len = match decode_config_slice(buffer, B64_CONF, &mut buffer2) { let len = decode_config_slice(buffer, B64_CONF, &mut buffer2)
Ok(len) => len, .map_err(|_| LoadError::InvalidData)?;
Err(_) => return (None, 0),
};
Self::load(&buffer2[..len]) 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 /// Dump an object into the buffer
pub trait Dumpable { pub trait Dumpable {
/// Write the object to the buffer, returning the amount of bytes written. /// Write the object to the buffer, returning the amount of bytes written.
/// If anything is wrong, false should be returned. /// If anything is wrong, false should be returned.
fn dump(&self, buffer: &mut [u8]) -> (bool, usize); fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError>;
fn dump_base64(&self, buffer: &mut [u8]) -> (bool, usize) { fn dump_base64(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE]; let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE];
let (ok, len) = self.dump(&mut buffer2); let len = self.dump(&mut buffer2)?;
if !ok {
return (false, len)
}
let len = encode_config_slice(&buffer2[..len], B64_CONF, buffer); let len = encode_config_slice(&buffer2[..len], B64_CONF, buffer);
(true, len) Ok(len)
} }
} }

View file

@ -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}; use crate::{RemoteCall, RemoteCallResponse};
pub const HOST_STR: &str = "127.0.0.1"; pub const HOST_STR: &str = "127.0.0.1";
@ -42,56 +42,56 @@ impl Packet {
} }
impl Loadable for Packet { impl Loadable for Packet {
fn load(buf: &[u8]) -> (Option<Self>, usize) { fn load(buf: &[u8]) -> Result<(Self, usize), LoadError> {
if buf.len() == 0 { if buf.len() == 0 {
return (None, 1); return Err(LoadError::TooSmallBuffer);
} }
let mut result: (Option<Self>, usize) = match buf[0] { let mut result: (Self, usize) = match buf[0] {
//0 => (None, 0), //0 => (None, 0),
1 => { 1 => {
let (obj, len) = RemoteCall::load(&buf[1..]); let (obj, len) = RemoteCall::load(&buf[1..])?;
(obj.map(Self::Call), len) (Self::Call(obj), 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)
} }
_ => (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.1 += 1;
result Ok(result)
} }
} }
impl Dumpable for Packet { impl Dumpable for Packet {
fn dump(&self, buf: &mut [u8]) -> (bool, usize) { fn dump(&self, buf: &mut [u8]) -> Result<usize, DumpError> {
if buf.len() == 0 { if buf.len() == 0 {
return (false, 0); return Err(DumpError::TooSmallBuffer);
} }
buf[0] = self.discriminant(); buf[0] = self.discriminant();
let mut result = match self { let mut result = match self {
Self::Call(c) => c.dump(&mut buf[1..]), Self::Call(c) => c.dump(&mut buf[1..]),
Self::CallResponse(c) => c.dump(&mut buf[1..]), Self::CallResponse(c) => c.dump(&mut buf[1..]),
Self::KeepAlive => (true, 0), Self::KeepAlive => Ok(0),
Self::Invalid => (true, 0), Self::Invalid => Ok(0),
Self::Message(s) => s.dump(&mut buf[1..]), Self::Message(s) => s.dump(&mut buf[1..]),
Self::Unsupported => (true, 0), Self::Unsupported => Ok(0),
Self::Bad => (false, 0), Self::Bad => return Err(DumpError::Unsupported),
Self::Many(v) => v.dump(&mut buf[1..]), Self::Many(v) => v.dump(&mut buf[1..]),
}; }?;
result.1 += 1; result += 1;
result Ok(result)
} }
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "usdpl" name = "usdpl"
version = "0.3.0" version = "0.4.0"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"] authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
edition = "2021" edition = "2021"
license = "GPL-3.0-only" license = "GPL-3.0-only"
@ -43,7 +43,7 @@ web-sys = { version = "0.3", features = [
]}#["WebSocket", "MessageEvent", "ErrorEvent", "BinaryType"] } ]}#["WebSocket", "MessageEvent", "ErrorEvent", "BinaryType"] }
js-sys = { version = "0.3" } 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] [dev-dependencies]
wasm-bindgen-test = "0.3.13" wasm-bindgen-test = "0.3.13"

View file

@ -6,14 +6,12 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
//use web_sys::{WebSocket, MessageEvent, ErrorEvent}; //use web_sys::{WebSocket, MessageEvent, ErrorEvent};
use js_sys::JsString;
use web_sys::{Request, RequestInit, RequestMode, Response}; use web_sys::{Request, RequestInit, RequestMode, Response};
use js_sys::{ArrayBuffer, DataView, Uint8Array, JsString};
//use wasm_rs_shared_channel::{Expects, spsc::{Receiver, Sender}}; //use wasm_rs_shared_channel::{Expects, spsc::{Receiver, Sender}};
use usdpl_core::socket;
use usdpl_core::serdes::{Dumpable, Loadable, Primitive}; use usdpl_core::serdes::{Dumpable, Loadable, Primitive};
use usdpl_core::socket;
use super::imports;
pub async fn send_js(packet: socket::Packet, port: u16) -> Result<Vec<Primitive>, JsValue> { pub async fn send_js(packet: socket::Packet, port: u16) -> Result<Vec<Primitive>, JsValue> {
let mut opts = RequestInit::new(); let mut opts = RequestInit::new();
@ -23,170 +21,36 @@ pub async fn send_js(packet: socket::Packet, port: u16) -> Result<Vec<Primitive>
let url = format!("http://localhost:{}/usdpl/call", port); let url = format!("http://localhost:{}/usdpl/call", port);
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
let (ok, len) = packet.dump_base64(&mut buffer); let len = packet
if !ok { .dump_base64(&mut buffer)
imports::console_error("USDPL error: packet dump failed"); .map_err(super::convert::str_to_js)?;
return Err("Packet dump failed".into());
}
let string: String = String::from_utf8_lossy(&buffer[..len]).into(); let string: String = String::from_utf8_lossy(&buffer[..len]).into();
opts.body(Some(&string.into())); opts.body(Some(&string.into()));
let request = Request::new_with_str_and_init(&url, &opts)?; let request = Request::new_with_str_and_init(&url, &opts)?;
request request.headers().set("Accept", "application/bytes")?;
.headers()
.set("Accept", "application/bytes")?;
//.set("Authorization", "wasm TODO_KEY")?; //.set("Authorization", "wasm TODO_KEY")?;
let window = web_sys::window().unwrap(); let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into()?; let resp: Response = resp_value.dyn_into()?;
// Convert this other `Promise` into a rust `Future`.
let text = JsFuture::from(resp.text()?).await?; let text = JsFuture::from(resp.text()?).await?;
let string: JsString = text.dyn_into()?; let string: JsString = text.dyn_into()?;
match socket::Packet::load_base64(&string.as_string().unwrap().as_bytes()).0 { match socket::Packet::load_base64(string.as_string().unwrap().as_bytes())
Some(socket::Packet::CallResponse(resp)) => { .map_err(super::convert::str_to_js)?
Ok(resp.response) .0
}, {
socket::Packet::CallResponse(resp) => Ok(resp.response),
_ => { _ => {
imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url())); //imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url()));
Err("".into()) 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&notify_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<dyn FnMut(MessageEvent)>);
//socket.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();
let onerror_callback = Closure::wrap(Box::new(onerror_factory()) as Box<dyn FnMut(ErrorEvent)>);
socket.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
onerror_callback.forget();
let onopen_callback = Closure::wrap(Box::new(onopen_factory(array_buffer, socket.clone())) as Box<dyn FnMut(JsValue)>);
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::<js_sys::ArrayBuffer>() {
//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<JsValue> = 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<socket::Packet> {
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
}*/

View file

@ -1,5 +1,6 @@
use js_sys::JsString;
use js_sys::JSON::{parse, stringify};
use wasm_bindgen::prelude::JsValue; use wasm_bindgen::prelude::JsValue;
use js_sys::JSON::{stringify, parse};
use usdpl_core::serdes::Primitive; use usdpl_core::serdes::Primitive;
@ -7,12 +8,12 @@ pub(crate) fn primitive_to_js(primitive: Primitive) -> JsValue {
match primitive { match primitive {
Primitive::Empty => JsValue::null(), Primitive::Empty => JsValue::null(),
Primitive::String(s) => JsValue::from_str(&s), Primitive::String(s) => JsValue::from_str(&s),
Primitive::F32(f)=> JsValue::from_f64(f as _), Primitive::F32(f) => JsValue::from_f64(f as _),
Primitive::F64(f)=> JsValue::from_f64(f), Primitive::F64(f) => JsValue::from_f64(f),
Primitive::U32(f)=> JsValue::from_f64(f as _), Primitive::U32(f) => JsValue::from_f64(f as _),
Primitive::U64(f)=> JsValue::from_f64(f as _), Primitive::U64(f) => JsValue::from_f64(f as _),
Primitive::I32(f)=> JsValue::from_f64(f as _), Primitive::I32(f) => JsValue::from_f64(f as _),
Primitive::I64(f)=> JsValue::from_f64(f as _), Primitive::I64(f) => JsValue::from_f64(f as _),
Primitive::Bool(b) => JsValue::from_bool(b), Primitive::Bool(b) => JsValue::from_bool(b),
Primitive::Json(s) => parse(&s).ok().unwrap_or(JsValue::from_str(&s)), 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 Primitive::Empty
} }
} }
pub(crate) fn str_to_js<S: std::string::ToString>(s: S) -> JsString {
s.to_string().into()
}

View file

@ -1,7 +1,7 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
extern { extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = log)] #[wasm_bindgen(js_namespace = console, js_name = log)]
pub fn console_log(s: &str); pub fn console_log(s: &str);

View file

@ -8,17 +8,14 @@ mod connection;
mod convert; mod convert;
mod imports; mod imports;
use wasm_bindgen::prelude::*;
use js_sys::Array; use js_sys::Array;
use wasm_bindgen::prelude::*;
use usdpl_core::{socket::Packet, RemoteCall}; use usdpl_core::{socket::Packet, RemoteCall};
//const REMOTE_CALL_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); //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); //const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(31337);
static mut CTX: UsdplContext = UsdplContext { static mut CTX: UsdplContext = UsdplContext { port: 31337, id: 1 };
port: 31337,
id: 1,
};
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug)] #[derive(Debug)]
@ -28,12 +25,14 @@ pub struct UsdplContext {
} }
fn get_port() -> u16 { fn get_port() -> u16 {
unsafe {CTX.port} unsafe { CTX.port }
} }
fn increment_id() -> u64 { fn increment_id() -> u64 {
let current_id = unsafe {CTX.id}; let current_id = unsafe { CTX.id };
unsafe {CTX.id += 1;} unsafe {
CTX.id += 1;
}
current_id current_id
} }
@ -49,29 +48,25 @@ pub fn init_usdpl(port: u16) {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
//REMOTE_PORT.store(port, std::sync::atomic::Ordering::SeqCst); //REMOTE_PORT.store(port, std::sync::atomic::Ordering::SeqCst);
unsafe { unsafe {
CTX = UsdplContext { CTX = UsdplContext { port: port, id: 1 };
port: port,
id: 1,
};
} }
} }
/// Get the targeted plugin framework, or "any" if unknown /// Get the targeted plugin framework, or "any" if unknown
#[wasm_bindgen] #[wasm_bindgen]
pub fn target() -> String { pub fn target() -> String {
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] usdpl_core::api::Platform::current().to_string()
{"decky".to_string()}
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
{"crankshaft".to_string()}
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
{"any".to_string()}
} }
/// Call a function on the back-end. /// Call a function on the back-end.
/// Returns null (None) if this fails for any reason. /// Returns null (None) if this fails for any reason.
#[wasm_bindgen] #[wasm_bindgen]
pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue { pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> 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 next_id = increment_id();
let mut params = Vec::with_capacity(parameters.len()); let mut params = Vec::with_capacity(parameters.len());
for val in parameters { for val in parameters {
@ -79,11 +74,15 @@ pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue {
} }
let port = get_port(); let port = get_port();
imports::console_log(&format!("USDPL: Got port {}", port)); imports::console_log(&format!("USDPL: Got port {}", port));
let results = connection::send_js(Packet::Call(RemoteCall { let results = connection::send_js(
Packet::Call(RemoteCall {
id: next_id, id: next_id,
function: name.clone(), function: name.clone(),
parameters: params, parameters: params,
}), port).await; }),
port,
)
.await;
let results = match results { let results = match results {
Ok(x) => x, Ok(x) => x,
Err(e) => { Err(e) => {