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

View file

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

View file

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

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::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);
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
if let Some(packet) = obj_maybe {
let response = Self::handle_call(packet);
let (ok, len) = response.dump_base64(&mut buffer);
if !ok {
eprintln!("Failed to dump response packet");
warp::reply::with_status(warp::http::Response::builder()
.body("Failed to dump response packet".to_string()), warp::http::StatusCode::from_u16(400).unwrap())
} else {
let string: String = String::from_utf8_lossy(&buffer[..len]).into();
warp::reply::with_status(warp::http::Response::builder()
.body(string), warp::http::StatusCode::from_u16(200).unwrap())
.map(move |data: bytes::Bytes| {
let (packet, _) = match socket::Packet::load_base64(&data) {
Ok(x) => x,
Err(_) => {
return warp::reply::with_status(
warp::http::Response::builder()
.body("Failed to load packet".to_string()),
warp::http::StatusCode::from_u16(400).unwrap(),
)
}
} else {
eprintln!("Failed to load packet");
warp::reply::with_status(warp::http::Response::builder()
.body("Failed to load packet".to_string()), warp::http::StatusCode::from_u16(400).unwrap())
}
};
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
let response = Self::handle_call(packet, &handlers);
let len = match response.dump_base64(&mut buffer) {
Ok(x) => x,
Err(_) => {
return warp::reply::with_status(
warp::http::Response::builder()
.body("Failed to dump response packet".to_string()),
warp::http::StatusCode::from_u16(500).unwrap(),
)
}
};
let string: String = String::from_utf8_lossy(&buffer[..len]).into();
warp::reply::with_status(
warp::http::Response::builder().body(string),
warp::http::StatusCode::from_u16(200).unwrap(),
)
})
.map(|reply| {
warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*")
});
.map(|reply| warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*"));
#[cfg(debug_assertions)]
warp::serve(calls).run(([0, 0, 0, 0], self.port)).await;
#[cfg(not(debug_assertions))]
warp::serve(calls).run(([127, 0, 0, 1], self.port)).await;
Ok(())
}
@ -180,110 +129,6 @@ impl Instance {
#[cfg(test)]
mod tests {
//use std::net::TcpStream;
//use super::*;
//const PORT: u16 = 31337;
/*#[test]
fn serve_full_test() -> std::io::Result<()> {
let _server = std::thread::spawn(|| {
Instance::new(PORT)
.register("echo".to_string(), &mut |params| params)
.register("hello".to_string(), &mut |params| {
if let Some(Primitive::String(name)) = params.get(0) {
vec![Primitive::String(format!("Hello {}", name))]
} else {
vec![]
}
})
.serve::<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::*;
}

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

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.
/// 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; 128];
let (ok, write_len) = primitive.dump(&mut buffer);
assert!(ok, "Dump not ok");
let (obj, read_len) = Primitive::load(&buffer);
assert_eq!(write_len, read_len, "Amount written and amount read do not match");
assert!(obj.is_some(), "Load not ok");
if let Some(Primitive::Empty) = obj {
let write_len = primitive.dump(&mut buffer).expect("Dump not ok");
let (obj, read_len) = Primitive::load(&buffer).expect("Load not ok");
assert_eq!(
write_len, read_len,
"Amount written and amount read do not match"
);
if let Primitive::Empty = obj {
//assert_eq!(data, result, "Data written and read does not match");
} else {
panic!("Read non-string primitive");

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);
/// Errors from Loadable::load
#[derive(Debug)]
pub enum LoadError {
TooSmallBuffer,
InvalidData,
#[cfg(debug_assertions)]
Todo,
}
impl std::fmt::Display for LoadError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::TooSmallBuffer => write!(f, "LoadError: TooSmallBuffer"),
Self::InvalidData => write!(f, "LoadError: InvalidData"),
#[cfg(debug_assertions)]
Self::Todo => write!(f, "LoadError: TODO!"),
}
}
}
/// Load an object from the buffer
pub trait Loadable: Sized {
/// Read the buffer, building the object and returning the amount of bytes read.
/// If anything is wrong with the buffer, None should be returned.
fn load(buffer: &[u8]) -> (Option<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 len = match decode_config_slice(buffer, B64_CONF, &mut buffer2) {
Ok(len) => len,
Err(_) => return (None, 0),
};
let len = decode_config_slice(buffer, B64_CONF, &mut buffer2)
.map_err(|_| LoadError::InvalidData)?;
Self::load(&buffer2[..len])
}
}
/// Errors from Dumpable::dump
#[derive(Debug)]
pub enum DumpError {
TooSmallBuffer,
Unsupported,
#[cfg(debug_assertions)]
Todo,
}
impl std::fmt::Display for DumpError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::TooSmallBuffer => write!(f, "DumpError: TooSmallBuffer"),
Self::Unsupported => write!(f, "DumpError: Unsupported"),
#[cfg(debug_assertions)]
Self::Todo => write!(f, "DumpError: TODO!"),
}
}
}
/// Dump an object into the buffer
pub trait Dumpable {
/// Write the object to the buffer, returning the amount of bytes written.
/// If anything is wrong, false should be returned.
fn dump(&self, buffer: &mut [u8]) -> (bool, usize);
fn dump(&self, buffer: &mut [u8]) -> Result<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 (ok, len) = self.dump(&mut buffer2);
if !ok {
return (false, len)
}
let len = self.dump(&mut buffer2)?;
let len = encode_config_slice(&buffer2[..len], B64_CONF, buffer);
(true, len)
Ok(len)
}
}

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

View file

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

View file

@ -6,14 +6,12 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
//use web_sys::{WebSocket, MessageEvent, ErrorEvent};
use js_sys::JsString;
use web_sys::{Request, RequestInit, RequestMode, Response};
use js_sys::{ArrayBuffer, DataView, Uint8Array, JsString};
//use wasm_rs_shared_channel::{Expects, spsc::{Receiver, Sender}};
use usdpl_core::socket;
use usdpl_core::serdes::{Dumpable, Loadable, Primitive};
use super::imports;
use usdpl_core::socket;
pub async fn send_js(packet: socket::Packet, port: u16) -> Result<Vec<Primitive>, JsValue> {
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 mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
let (ok, len) = packet.dump_base64(&mut buffer);
if !ok {
imports::console_error("USDPL error: packet dump failed");
return Err("Packet dump failed".into());
}
let len = packet
.dump_base64(&mut buffer)
.map_err(super::convert::str_to_js)?;
let string: String = String::from_utf8_lossy(&buffer[..len]).into();
opts.body(Some(&string.into()));
let request = Request::new_with_str_and_init(&url, &opts)?;
request
.headers()
.set("Accept", "application/bytes")?;
//.set("Authorization", "wasm TODO_KEY")?;
request.headers().set("Accept", "application/bytes")?;
//.set("Authorization", "wasm TODO_KEY")?;
let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into()?;
// Convert this other `Promise` into a rust `Future`.
let text = JsFuture::from(resp.text()?).await?;
let string: JsString = text.dyn_into()?;
match socket::Packet::load_base64(&string.as_string().unwrap().as_bytes()).0 {
Some(socket::Packet::CallResponse(resp)) => {
Ok(resp.response)
},
match socket::Packet::load_base64(string.as_string().unwrap().as_bytes())
.map_err(super::convert::str_to_js)?
.0
{
socket::Packet::CallResponse(resp) => Ok(resp.response),
_ => {
imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url()));
Err("".into())
//imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url()));
Err(format!(
"Expected call response message from {}, got something else",
resp.url()
)
.into())
}
}
}
/*#[allow(dead_code)]
/// Send packet over a Javascript socket
pub(crate) fn send_js(packet: socket::Packet, port: u16, callback: js_sys::Function) -> bool {
let addr = format!("wss://{}:{}",
"192.168.0.128",//socket::HOST_STR,
port);
let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE];
let (ok, len) = packet.dump(&mut buffer);
if !ok {
imports::console_error("USDPL error: packet dump failed");
return false;
}
// copy to JS buffer
let array_buffer = ArrayBuffer::new(len as _);
let dataview = DataView::new(&array_buffer, 0, len);
for i in 0..len {
dataview.set_uint8(i, buffer[i]);
}
imports::console_log("USDPL: creating WebSocket");
let socket = match WebSocket::new("wss://demo.piesocket.com/v3/channel_1?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV&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 js_sys::JSON::{stringify, parse};
use usdpl_core::serdes::Primitive;
@ -7,12 +8,12 @@ pub(crate) fn primitive_to_js(primitive: Primitive) -> JsValue {
match primitive {
Primitive::Empty => JsValue::null(),
Primitive::String(s) => JsValue::from_str(&s),
Primitive::F32(f)=> JsValue::from_f64(f as _),
Primitive::F64(f)=> JsValue::from_f64(f),
Primitive::U32(f)=> JsValue::from_f64(f as _),
Primitive::U64(f)=> JsValue::from_f64(f as _),
Primitive::I32(f)=> JsValue::from_f64(f as _),
Primitive::I64(f)=> JsValue::from_f64(f as _),
Primitive::F32(f) => JsValue::from_f64(f as _),
Primitive::F64(f) => JsValue::from_f64(f),
Primitive::U32(f) => JsValue::from_f64(f as _),
Primitive::U64(f) => JsValue::from_f64(f as _),
Primitive::I32(f) => JsValue::from_f64(f as _),
Primitive::I64(f) => JsValue::from_f64(f as _),
Primitive::Bool(b) => JsValue::from_bool(b),
Primitive::Json(s) => parse(&s).ok().unwrap_or(JsValue::from_str(&s)),
}
@ -33,3 +34,7 @@ pub(crate) fn js_to_primitive(val: JsValue) -> Primitive {
Primitive::Empty
}
}
pub(crate) fn str_to_js<S: std::string::ToString>(s: S) -> JsString {
s.to_string().into()
}

View file

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

View file

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