Minor refactor and code cleanup to improve ergonomics

pull/4/head
NGnius 2 years ago
parent 63ed7fa5d1
commit 0a5323c927

7
Cargo.lock generated

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

@ -7,9 +7,11 @@ $0 [decky|crankshaft|<nothing>]"
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

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

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

@ -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<Primitive>) -> Vec<Primitive>) + Send + Sync)>;
use super::Callable;
lazy_static! {
static ref CALLS: std::sync::Mutex<HashMap<String, Callable>> = std::sync::Mutex::new(HashMap::new());
}
type WrappedCallable = Arc<Mutex<Box<dyn Callable>>>; // thread-safe, cloneable Callable
/// Back-end instance for interacting with the front-end
pub struct Instance {
//calls: HashMap<String, Callable>,
calls: HashMap<String, WrappedCallable>,
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<S: std::convert::Into<String>, F: (Fn(Vec<Primitive>) -> Vec<Primitive>) + 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<S: std::convert::Into<String>, 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<const ERROR: bool>(&mut self, packet: socket::Packet, buffer: &mut [u8], peer_addr: &SocketAddr) -> Result<String, super::ServerError> {
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 {
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<String, WrappedCallable>,
) -> 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);
.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(),
)
}
};
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())
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(),
)
}
} 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 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::<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");
}
}*/
#[allow(unused_imports)]
use super::*;
}

@ -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::*;

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

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

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

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

@ -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<Self>, usize) {
let (id_num, len0) = u64::load(buffer);
if id_num.is_none() {
return (None, len0);
}
let (function_name, len1) = String::load(&buffer[len0..]);
if function_name.is_none() {
return (None, len1);
}
let (params, len2) = Vec::<Primitive>::load(&buffer[len0+len1..]);
if params.is_none() {
return (None, len1 + len2);
}
(
Some(Self {
id: id_num.unwrap(),
function: function_name.unwrap(),
parameters: params.unwrap(),
}),
len0 + len1 + len2
)
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::<Primitive>::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<usize, DumpError> {
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<Self>, usize) {
let (id_num, len0) = u64::load(buffer);
if id_num.is_none() {
return (None, len0);
}
let (response_var, len1) = Vec::<Primitive>::load(&buffer[len0..]);
if response_var.is_none() {
return (None, len1);
}
(
Some(Self {
id: id_num.unwrap(),
response: response_var.unwrap(),
}),
len0 + len1
)
fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
let (id_num, len0) = u64::load(buffer)?;
let (response_var, len1) = Vec::<Primitive>::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<usize, DumpError> {
let len0 = self.id.dump(buffer)?;
let len1 = self.response.dump(&mut buffer[len0..])?;
Ok(len0 + len1)
}
}

@ -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<usize, DumpError> {
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<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();
(&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<usize, DumpError> {
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<usize, DumpError> {
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<usize, DumpError> {
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<usize, DumpError> {
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]}
}

@ -1,21 +1,24 @@
use super::Loadable;
use super::{LoadError, Loadable};
impl Loadable for String {
fn load(buffer: &[u8]) -> (Option<Self>, 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<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 {
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<T: Loadable> Loadable for Vec<T> {
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<Self>, 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<Self>, 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<Self>, 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<Self>, 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<String>,
@ -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}
}

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

@ -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<Self>, usize) {
fn load(buf: &[u8]) -> Result<(Self, usize), LoadError> {
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),
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<usize, DumpError> {
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;