Improve flexibility of WASM conversions in code gen

This commit is contained in:
NGnius (Graham) 2023-06-04 14:05:33 -04:00
parent febaafe50c
commit 0b44ebc12b
33 changed files with 1028 additions and 545 deletions

1
Cargo.lock generated
View file

@ -1546,7 +1546,6 @@ dependencies = [
"nrpc 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"obfstr",
"prost",
"usdpl-build",
"usdpl-core",
"wasm-bindgen",
"wasm-bindgen-futures",

View file

@ -1,3 +0,0 @@
fn main() {
usdpl_build::back::build()
}

View file

@ -5,14 +5,10 @@ use std::process::Command;
/// The home directory of the user currently running the Steam Deck UI (specifically: running gamescope).
pub fn home() -> Option<PathBuf> {
let who_out = Command::new("who")
.output().ok()?;
let who_out = Command::new("who").output().ok()?;
let who_str = String::from_utf8_lossy(who_out.stdout.as_slice());
for login in who_str.split("\n") {
let username = login
.split(" ")
.next()?
.trim();
let username = login.split(" ").next()?.trim();
let path = Path::new("/home").join(username);
if path.is_dir() {
return Some(path);

View file

@ -7,12 +7,10 @@ pub fn home() -> Option<PathBuf> {
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
let result = crate::api_any::dirs::home();
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
let result = crate::api_decky::home().ok()
.map(|x| PathBuf::from(x)
.join("..")
.canonicalize()
.ok()
).flatten();
let result = crate::api_decky::home()
.ok()
.map(|x| PathBuf::from(x).join("..").canonicalize().ok())
.flatten();
result
}

View file

@ -1,8 +1,8 @@
//! Common low-level file operations
use std::fmt::Display;
use std::path::Path;
use std::fs::File;
use std::io::{Read, Write, self};
use std::io::{self, Read, Write};
use std::path::Path;
use std::str::FromStr;
/// Write something to a file.
@ -31,14 +31,12 @@ impl<E: std::error::Error> std::fmt::Display for ReadError<E> {
}
}
impl<E: std::error::Error> std::error::Error for ReadError<E> {
}
impl<E: std::error::Error> std::error::Error for ReadError<E> {}
/// Read something from a file.
/// Useful for kernel configuration files.
#[inline]
pub fn read_single<P: AsRef<Path>, D: FromStr<Err=E>, E>(path: P) -> Result<D, ReadError<E>> {
pub fn read_single<P: AsRef<Path>, D: FromStr<Err = E>, E>(path: P) -> Result<D, ReadError<E>> {
let mut file = File::open(path).map_err(ReadError::Io)?;
let mut string = String::new();
file.read_to_string(&mut string).map_err(ReadError::Io)?;

View file

@ -5,12 +5,10 @@
//!
#![warn(missing_docs)]
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
#[cfg(not(any(feature = "decky")))]
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"))))]
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
mod api_decky;
mod rpc;
@ -27,16 +25,16 @@ pub mod api {
pub use super::api_common::*;
/// Standard interfaces not specific to a single plugin loader
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
pub mod any { pub use super::super::api_any::*; }
/// Crankshaft-specific interfaces (FIXME)
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
pub mod crankshaft { pub use super::super::api_crankshaft::*; }
#[cfg(not(any(feature = "decky")))]
pub mod any {
pub use super::super::api_any::*;
}
/// Decky-specific interfaces
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
pub mod decky { pub use super::super::api_decky::*; }
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
pub mod decky {
pub use super::super::api_decky::*;
}
}
/// usdpl-core re-export
@ -49,9 +47,9 @@ pub mod nrpc {
pub use nrpc::*;
}
/// nRPC-generated exports
/*/// nRPC-generated exports
#[allow(missing_docs)]
#[allow(dead_code)]
pub mod services {
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}
}*/

View file

@ -1,6 +1,6 @@
use async_lock::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
use async_lock::Mutex;
use nrpc::{ServerService, ServiceError};
@ -19,7 +19,12 @@ impl<'a> ServiceRegistry<'a> {
format!("{}.{}", package, service)
}*/
pub async fn call_descriptor(&self, descriptor: &str, method: &str, data: bytes::Bytes) -> Result<bytes::Bytes, ServiceError> {
pub async fn call_descriptor(
&self,
descriptor: &str,
method: &str,
data: bytes::Bytes,
) -> Result<bytes::Bytes, ServiceError> {
if let Some(service) = self.entries.get(descriptor) {
let mut output = bytes::BytesMut::new();
let mut service_lock = service.lock_arc().await;
@ -32,7 +37,8 @@ impl<'a> ServiceRegistry<'a> {
pub fn register<S: ServerService + Send + 'a>(&mut self, service: S) -> &mut Self {
let key = service.descriptor().to_owned();
self.entries.insert(key, Arc::new(Mutex::new(Box::new(service))));
self.entries
.insert(key, Arc::new(Mutex::new(Box::new(service))));
self
}

View file

@ -61,16 +61,19 @@ impl WebsocketServer {
runner.block_on(self.run())
}
async fn connection_handler(services: ServiceRegistry<'static>, stream: TcpStream) -> Result<(), RatchetError> {
async fn connection_handler(
services: ServiceRegistry<'static>,
stream: TcpStream,
) -> Result<(), RatchetError> {
let upgraded = ratchet_rs::accept_with(
stream,
WebSocketConfig::default(),
DeflateExtProvider::default(),
ProtocolRegistry::new(["usdpl-nrpc"])?,
)
.await?
.upgrade()
.await?;
.await?
.upgrade()
.await?;
let request_path = upgraded.request.uri().path();
@ -82,19 +85,27 @@ impl WebsocketServer {
let mut buf = BytesMut::new();
loop {
match websocket.read(&mut buf).await? {
Message::Text => return Err(RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, "Websocket text messages are not accepted")),
Message::Text => {
return Err(RatchetError::with_cause(
ratchet_rs::ErrorKind::Protocol,
"Websocket text messages are not accepted",
))
}
Message::Binary => {
let response = services.call_descriptor(
descriptor.service,
descriptor.method,
buf.clone().freeze()
)
let response = services
.call_descriptor(
descriptor.service,
descriptor.method,
buf.clone().freeze(),
)
.await
.map_err(|e| RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string()))?;
.map_err(|e| {
RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string())
})?;
websocket.write_binary(response).await?;
},
}
Message::Ping(x) => websocket.write_pong(x).await?,
Message::Pong(_) => {},
Message::Pong(_) => {}
Message::Close(_) => break,
}
}
@ -106,10 +117,7 @@ impl WebsocketServer {
if let Some(service) = iter.next() {
if let Some(method) = iter.next() {
if iter.next().is_none() {
return Ok(MethodDescriptor {
service,
method
});
return Ok(MethodDescriptor { service, method });
} else {
Err("URL path has too many separators")
}

View file

@ -22,4 +22,6 @@ message LogMessage {
string msg = 2;
}
message Empty {}
message Empty {
bool ok = 1;
}

View file

@ -1,7 +1,10 @@
pub fn build() {
pub fn build(
custom_protos: impl Iterator<Item = String>,
custom_dirs: impl Iterator<Item = String>,
) {
crate::dump_protos_out().unwrap();
nrpc_build::compile_servers(
crate::all_proto_filenames(crate::proto_builtins_out_path()),
crate::proto_out_paths()
crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos),
crate::proto_out_paths(custom_dirs),
)
}

View file

@ -7,16 +7,24 @@ pub use service_generator::WasmServiceGenerator;
mod shared_state;
pub(crate) use shared_state::SharedState;
pub fn build() {
pub fn build(
custom_protos: impl Iterator<Item = String>,
custom_dirs: impl Iterator<Item = String>,
) {
let shared_state = SharedState::new();
crate::dump_protos_out().unwrap();
nrpc_build::Transpiler::new(
crate::all_proto_filenames(crate::proto_builtins_out_path()),
crate::proto_out_paths()
).unwrap()
.generate_client()
.with_preprocessor(nrpc_build::AbstractImpl::outer(WasmProtoPreprocessor::with_state(&shared_state)))
.with_service_generator(nrpc_build::AbstractImpl::outer(WasmServiceGenerator::with_state(&shared_state)))
.transpile()
.unwrap()
crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos),
crate::proto_out_paths(custom_dirs),
)
.unwrap()
.generate_client()
.with_preprocessor(nrpc_build::AbstractImpl::outer(
WasmProtoPreprocessor::with_state(&shared_state),
))
.with_service_generator(nrpc_build::AbstractImpl::outer(
WasmServiceGenerator::with_state(&shared_state),
))
.transpile()
.unwrap()
}

View file

@ -18,10 +18,7 @@ impl WasmProtoPreprocessor {
impl IPreprocessor for WasmProtoPreprocessor {
fn process(&mut self, fds: &mut FileDescriptorSet) -> proc_macro2::TokenStream {
self.shared.lock()
.expect("Cannot lock shared state")
.fds = Some(fds.clone());
quote::quote!{}
self.shared.lock().expect("Cannot lock shared state").fds = Some(fds.clone());
quote::quote! {}
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,9 +7,7 @@ pub struct SharedState(Arc<Mutex<SharedProtoData>>);
impl SharedState {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(SharedProtoData {
fds: None,
})))
Self(Arc::new(Mutex::new(SharedProtoData { fds: None })))
}
}

View file

@ -2,4 +2,6 @@ pub mod back;
pub mod front;
mod proto_files;
pub use proto_files::{dump_protos, dump_protos_out, proto_out_paths, all_proto_filenames, proto_builtins_out_path};
pub use proto_files::{
all_proto_filenames, dump_protos, dump_protos_out, proto_builtins_out_path, proto_out_paths,
};

View file

@ -17,26 +17,25 @@ const TRANSLATIONS_PROTO: IncludedFileStr<'static> = IncludedFileStr {
contents: include_str!("../protos/translations.proto"),
};
const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [
DEBUG_PROTO,
TRANSLATIONS_PROTO,
];
const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [DEBUG_PROTO, TRANSLATIONS_PROTO];
pub fn proto_builtins_out_path() -> PathBuf {
PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)")).join("protos")
PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)"))
.join("protos")
}
pub fn proto_out_paths() -> impl Iterator<Item = String> {
pub fn proto_out_paths(additionals: impl Iterator<Item = String>) -> impl Iterator<Item = String> {
std::iter::once(proto_builtins_out_path())
.map(|x| x.to_str().unwrap().to_owned())
.chain(custom_protos_dirs().into_iter())
.chain(custom_protos_dirs(additionals).into_iter())
}
fn custom_protos_dirs() -> Vec<String> {
fn custom_protos_dirs(additionals: impl Iterator<Item = String>) -> Vec<String> {
let dirs = std::env::var(ADDITIONAL_PROTOBUFS_ENV_VAR).unwrap_or_else(|_| "".to_owned());
dirs.split(':')
.filter(|x| std::fs::read_dir(x).is_ok())
.map(|x| x.to_owned())
.chain(additionals)
.collect()
}
@ -48,14 +47,27 @@ fn custom_protos_filenames() -> Vec<String> {
.flat_map(|x| x.unwrap())
.filter(|x| x.is_ok())
.map(|x| x.unwrap().path())
.filter(|x| if let Some(ext) = x.extension() { ext.to_ascii_lowercase() == "proto" && x.is_file() } else { false })
.filter(|x| {
if let Some(ext) = x.extension() {
ext.to_ascii_lowercase() == "proto" && x.is_file()
} else {
false
}
})
.filter_map(|x| x.to_str().map(|x| x.to_owned()))
.collect()
}
pub fn all_proto_filenames(p: impl AsRef<Path> + 'static) -> impl Iterator<Item = String> {
pub fn all_proto_filenames(
p: impl AsRef<Path> + 'static,
additionals: impl Iterator<Item = String>,
) -> impl Iterator<Item = String> {
//let p = p.as_ref();
ALL_PROTOS.iter().map(move |x| p.as_ref().join(x.filename).to_str().unwrap().to_owned()).chain(custom_protos_filenames())
ALL_PROTOS
.iter()
.map(move |x| p.as_ref().join(x.filename).to_str().unwrap().to_owned())
.chain(custom_protos_filenames())
.chain(additionals)
}
pub fn dump_protos(p: impl AsRef<Path>) -> std::io::Result<()> {

View file

@ -4,12 +4,10 @@
mod remote_call;
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
#[cfg(not(any(feature = "decky")))]
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"))))]
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
mod api_decky;
pub mod serdes;
@ -20,11 +18,9 @@ pub use remote_call::{RemoteCall, RemoteCallResponse};
/// USDPL core API.
/// This contains functionality used in both the back-end and front-end.
pub mod api {
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
#[cfg(not(any(feature = "decky")))]
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"))))]
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
pub use super::api_decky::*;
}

View file

@ -73,7 +73,7 @@ mod tests {
#[test]
fn remote_call_idempotence_test() {
let call = RemoteCall{
let call = RemoteCall {
id: 42,
function: "something very long just in case this causes unexpected issues".into(),
parameters: vec!["param1".into(), 42f64.into()],
@ -88,7 +88,10 @@ mod tests {
assert_eq!(len, loaded_len, "Expected load and dump lengths to match");
assert_eq!(loaded_call.id, call.id, "RemoteCall.id does not match");
assert_eq!(loaded_call.function, call.function, "RemoteCall.function does not match");
assert_eq!(
loaded_call.function, call.function,
"RemoteCall.function does not match"
);
if let Primitive::String(loaded) = &loaded_call.parameters[0] {
if let Primitive::String(original) = &call.parameters[0] {
assert_eq!(loaded, original, "RemoteCall.parameters[0] does not match");

View file

@ -26,43 +26,34 @@ impl<T: Dumpable> Dumpable for Vec<T> {
impl<T0: Dumpable, T1: Dumpable> Dumpable for (T0, T1) {
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
Ok(
self.0.dump(buffer)?
+ self.1.dump(buffer)?
)
Ok(self.0.dump(buffer)? + self.1.dump(buffer)?)
}
}
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable> Dumpable for (T0, T1, T2) {
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
Ok(
self.0.dump(buffer)?
+ self.1.dump(buffer)?
+ self.2.dump(buffer)?
)
Ok(self.0.dump(buffer)? + self.1.dump(buffer)? + self.2.dump(buffer)?)
}
}
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable> Dumpable for (T0, T1, T2, T3) {
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
Ok(
self.0.dump(buffer)?
Ok(self.0.dump(buffer)?
+ self.1.dump(buffer)?
+ self.2.dump(buffer)?
+ self.3.dump(buffer)?
)
+ self.3.dump(buffer)?)
}
}
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable, T4: Dumpable> Dumpable for (T0, T1, T2, T3, T4) {
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable, T4: Dumpable> Dumpable
for (T0, T1, T2, T3, T4)
{
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
Ok(
self.0.dump(buffer)?
Ok(self.0.dump(buffer)?
+ self.1.dump(buffer)?
+ self.2.dump(buffer)?
+ self.3.dump(buffer)?
+ self.4.dump(buffer)?
)
+ self.4.dump(buffer)?)
}
}

View file

@ -42,10 +42,7 @@ impl<T0: Loadable, T1: Loadable> Loadable for (T0, T1) {
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
let (t0, len0) = T0::load(buffer)?;
let (t1, len1) = T1::load(buffer)?;
Ok((
(t0, t1),
len0 + len1
))
Ok(((t0, t1), len0 + len1))
}
}
@ -54,10 +51,7 @@ impl<T0: Loadable, T1: Loadable, T2: Loadable> Loadable for (T0, T1, T2) {
let (t0, len0) = T0::load(buffer)?;
let (t1, len1) = T1::load(buffer)?;
let (t2, len2) = T2::load(buffer)?;
Ok((
(t0, t1, t2),
len0 + len1 + len2
))
Ok(((t0, t1, t2), len0 + len1 + len2))
}
}
@ -67,24 +61,20 @@ impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable> Loadable for (T0, T
let (t1, len1) = T1::load(buffer)?;
let (t2, len2) = T2::load(buffer)?;
let (t3, len3) = T3::load(buffer)?;
Ok((
(t0, t1, t2, t3),
len0 + len1 + len2 + len3
))
Ok(((t0, t1, t2, t3), len0 + len1 + len2 + len3))
}
}
impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable, T4: Loadable> Loadable for (T0, T1, T2, T3, T4) {
impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable, T4: Loadable> Loadable
for (T0, T1, T2, T3, T4)
{
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
let (t0, len0) = T0::load(buffer)?;
let (t1, len1) = T1::load(buffer)?;
let (t2, len2) = T2::load(buffer)?;
let (t3, len3) = T3::load(buffer)?;
let (t4, len4) = T4::load(buffer)?;
Ok((
(t0, t1, t2, t3, t4),
len0 + len1 + len2 + len3 + len4
))
Ok(((t0, t1, t2, t3, t4), len0 + len1 + len2 + len3 + len4))
}
}

View file

@ -1,5 +1,5 @@
use std::io::{Read, Write};
use super::{DumpError, Dumpable, LoadError, Loadable};
use std::io::{Read, Write};
/// Primitive types supported for communication between the USDPL back- and front-end.
/// These are used for sending over the TCP connection.
@ -47,7 +47,8 @@ impl Primitive {
impl Loadable for Primitive {
fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> {
let mut discriminant_buf = [u8::MAX; 1];
buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?;
buf.read_exact(&mut discriminant_buf)
.map_err(LoadError::Io)?;
let mut result: (Self, usize) = match discriminant_buf[0] {
//0 => (None, 0),
1 => (Self::Empty, 0),
@ -105,7 +106,7 @@ macro_rules! into_impl {
Primitive::$variant(self)
}
}
}
};
}
into_impl! {String, String}

View file

@ -1,5 +1,5 @@
use std::io::{Read, Write, Cursor};
use base64::{decode_config_buf, encode_config_buf, Config};
use std::io::{Cursor, Read, Write};
const B64_CONF: Config = Config::new(base64::CharacterSet::Standard, true);
@ -49,8 +49,7 @@ pub trait Loadable: Sized {
/// Load data from a base64-encoded buffer
fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
decode_config_buf(buffer, B64_CONF, &mut buffer2)
.map_err(|_| LoadError::InvalidData)?;
decode_config_buf(buffer, B64_CONF, &mut buffer2).map_err(|_| LoadError::InvalidData)?;
let mut cursor = Cursor::new(buffer2);
Self::load(&mut cursor)
}
@ -66,7 +65,9 @@ pub trait Loadable: Sized {
base64::decode_config_buf(buffer, B64_CONF, &mut decoded_buf)
.map_err(|_| LoadError::InvalidData)?;
//println!("Decoded buf: {:?}", decoded_buf);
cipher.decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf).map_err(|_| LoadError::DecryptionError)?;
cipher
.decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf)
.map_err(|_| LoadError::DecryptionError)?;
//println!("Decrypted buf: {:?}", decoded_buf);
let mut cursor = Cursor::new(decoded_buf);
Self::load(&mut cursor)
@ -121,7 +122,12 @@ pub trait Dumpable {
/// Dump data as an encrypted base64-encoded buffer
#[cfg(feature = "encrypt")]
fn dump_encrypted(&self, buffer: &mut Vec<u8>, key: &[u8], nonce: &[u8]) -> Result<usize, DumpError> {
fn dump_encrypted(
&self,
buffer: &mut Vec<u8>,
key: &[u8],
nonce: &[u8],
) -> Result<usize, DumpError> {
let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
let size = self.dump(&mut buffer2)?;
buffer2.truncate(size);
@ -129,7 +135,9 @@ pub trait Dumpable {
let key = aes_gcm_siv::Key::from_slice(key);
let cipher = aes_gcm_siv::Aes256GcmSiv::new(key);
let nonce = aes_gcm_siv::Nonce::from_slice(nonce);
cipher.encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2).map_err(|_| DumpError::EncryptionError)?;
cipher
.encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2)
.map_err(|_| DumpError::EncryptionError)?;
//println!("Encrypted slice: {:?}", &buffer2);
let mut base64_buf = String::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
encode_config_buf(buffer2.as_slice(), B64_CONF, &mut base64_buf);

View file

@ -1,6 +1,6 @@
//! Web messaging
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::io::{Read, Write};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use crate::serdes::{DumpError, Dumpable, LoadError, Loadable};
use crate::{RemoteCall, RemoteCallResponse};
@ -66,7 +66,8 @@ impl Packet {
impl Loadable for Packet {
fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> {
let mut discriminant_buf = [u8::MAX; 1];
buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?;
buf.read_exact(&mut discriminant_buf)
.map_err(LoadError::Io)?;
let mut result: (Self, usize) = match discriminant_buf[0] {
//0 => (None, 0),
1 => {
@ -88,15 +89,15 @@ impl Loadable for Packet {
8 => {
let (obj, len) = <_>::load(buf)?;
(Self::Many(obj), len)
},
}
9 => {
let (obj, len) = <_>::load(buf)?;
(Self::Translations(obj), len)
},
}
10 => {
let (obj, len) = <_>::load(buf)?;
(Self::Language(obj), len)
},
}
_ => return Err(LoadError::InvalidData),
};
result.1 += 1;
@ -130,24 +131,39 @@ mod tests {
#[cfg(feature = "encrypt")]
#[test]
fn encryption_integration_test() {
let key = hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265");
let key =
hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265");
let nonce = [0u8; NONCE_SIZE];
let packet = Packet::Call(RemoteCall{
let packet = Packet::Call(RemoteCall {
id: 42,
function: "test".into(),
parameters: Vec::new(),
});
let mut buffer = Vec::with_capacity(PACKET_BUFFER_SIZE);
let len = packet.dump_encrypted(&mut buffer, &key, &nonce).unwrap();
println!("buffer: {}", String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap());
println!(
"buffer: {}",
String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap()
);
let (packet_out, _len) = Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap();
let (packet_out, _len) =
Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap();
if let Packet::Call(call_out) = packet_out {
if let Packet::Call(call_in) = packet {
assert_eq!(call_in.id, call_out.id, "Input and output packets do not match");
assert_eq!(call_in.function, call_out.function, "Input and output packets do not match");
assert_eq!(call_in.parameters.len(), call_out.parameters.len(), "Input and output packets do not match");
assert_eq!(
call_in.id, call_out.id,
"Input and output packets do not match"
);
assert_eq!(
call_in.function, call_out.function,
"Input and output packets do not match"
);
assert_eq!(
call_in.parameters.len(),
call_out.parameters.len(),
"Input and output packets do not match"
);
} else {
panic!("Packet in not a Call");
}

View file

@ -50,6 +50,3 @@ prost = "0.11"
[dev-dependencies]
wasm-bindgen-test = { version = "0.3.13" }
[build-dependencies]
usdpl-build = { version = "0.11", path = "../usdpl-build" }

View file

@ -1,3 +0,0 @@
fn main() {
usdpl_build::front::build()
}

View file

@ -1,20 +1,23 @@
use std::sync::atomic::{AtomicU64, Ordering};
use nrpc::{ClientHandler, ServiceError, _helpers::bytes, _helpers::async_trait};
use gloo_net::websocket::{Message, futures::WebSocket};
use wasm_bindgen_futures::spawn_local;
use futures::{SinkExt, StreamExt};
use gloo_net::websocket::{futures::WebSocket, Message};
use nrpc::{ClientHandler, ServiceError, _helpers::async_trait, _helpers::bytes};
use wasm_bindgen_futures::spawn_local;
static LAST_ID: AtomicU64 = AtomicU64::new(0);
/// Websocket client.
/// In most cases, this shouldn't be used directly, but generated code will use this.
pub struct WebSocketHandler {
// TODO
port: u16,
}
async fn send_recv_ws(url: String, input: bytes::Bytes) -> Result<Vec<u8>, String> {
let mut ws = WebSocket::open(&url).map_err(|e| e.to_string())?;
ws.send(Message::Bytes(input.into())).await.map_err(|e| e.to_string())?;
ws.send(Message::Bytes(input.into()))
.await
.map_err(|e| e.to_string())?;
read_next_incoming(ws).await
}
@ -42,7 +45,7 @@ impl std::fmt::Display for ErrorStr {
impl std::error::Error for ErrorStr {}
impl WebSocketHandler {
#[allow(dead_code)]
/// Instantiate the web socket client for connecting on the specified port
pub fn new(port: u16) -> Self {
Self { port }
}
@ -50,33 +53,29 @@ impl WebSocketHandler {
#[async_trait::async_trait]
impl ClientHandler for WebSocketHandler {
async fn call(&mut self,
package: &str,
service: &str,
method: &str,
input: bytes::Bytes,
output: &mut bytes::BytesMut) -> Result<(), ServiceError> {
async fn call(
&mut self,
package: &str,
service: &str,
method: &str,
input: bytes::Bytes,
output: &mut bytes::BytesMut,
) -> Result<(), ServiceError> {
let id = LAST_ID.fetch_add(1, Ordering::SeqCst);
let url = format!(
"ws://usdpl-ws-{}.localhost:{}/{}.{}/{}",
id,
self.port,
package,
service,
method,
id, self.port, package, service, method,
);
let (tx, rx) = async_channel::bounded(1);
spawn_local(async move {
tx.send(send_recv_ws(
url,
input
).await).await.unwrap_or(());
tx.send(send_recv_ws(url, input).await).await.unwrap_or(());
});
output.extend_from_slice(
&rx.recv().await
&rx.recv()
.await
.map_err(|e| ServiceError::Method(Box::new(e)))?
.map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))?
.map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))?,
);
Ok(())
}

View file

@ -14,26 +14,33 @@ use usdpl_core::serdes::{Dumpable, Loadable, Primitive};
use usdpl_core::socket;
#[cfg(feature = "encrypt")]
const NONCE: [u8; socket::NONCE_SIZE]= [0u8; socket::NONCE_SIZE];
const NONCE: [u8; socket::NONCE_SIZE] = [0u8; socket::NONCE_SIZE];
pub async fn send_recv_packet(
id: u64,
packet: socket::Packet,
port: u16,
#[cfg(feature = "encrypt")]
key: Vec<u8>,
#[cfg(feature = "encrypt")] key: Vec<u8>,
) -> Result<socket::Packet, JsValue> {
let mut opts = RequestInit::new();
opts.method("POST");
opts.mode(RequestMode::Cors);
let url = format!("http://usdpl{}.{}:{}/usdpl/call", id, socket::HOST_STR, port);
let url = format!(
"http://usdpl{}.{}:{}/usdpl/call",
id,
socket::HOST_STR,
port
);
#[allow(unused_variables)]
let (buffer, len) = dump_to_buffer(packet, #[cfg(feature = "encrypt")] key.as_slice())?;
let (buffer, len) = dump_to_buffer(
packet,
#[cfg(feature = "encrypt")]
key.as_slice(),
)?;
let string: String = String::from_utf8_lossy(buffer.as_slice()).into();
#[cfg(feature="debug")]
#[cfg(feature = "debug")]
crate::imports::console_log(&format!("Dumped base64 `{}` len:{}", string, len));
opts.body(Some(&string.into()));
@ -50,31 +57,46 @@ pub async fn send_recv_packet(
let string: JsString = text.dyn_into()?;
let rust_str = string.as_string().unwrap();
#[cfg(feature="debug")]
crate::imports::console_log(&format!("Received base64 `{}` len:{}", rust_str, rust_str.len()));
#[cfg(feature = "debug")]
crate::imports::console_log(&format!(
"Received base64 `{}` len:{}",
rust_str,
rust_str.len()
));
#[cfg(not(feature = "encrypt"))]
{Ok(socket::Packet::load_base64(rust_str.as_bytes())
.map_err(super::convert::str_to_js)?
.0)}
{
Ok(socket::Packet::load_base64(rust_str.as_bytes())
.map_err(super::convert::str_to_js)?
.0)
}
#[cfg(feature = "encrypt")]
{Ok(socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE)
.map_err(super::convert::str_to_js)?
.0)}
{
Ok(
socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE)
.map_err(super::convert::str_to_js)?
.0,
)
}
}
pub async fn send_call(
id: u64,
packet: socket::Packet,
port: u16,
#[cfg(feature = "encrypt")]
key: Vec<u8>,
#[cfg(feature = "encrypt")] key: Vec<u8>,
) -> Result<Vec<Primitive>, JsValue> {
let packet = send_recv_packet(id, packet, port, #[cfg(feature = "encrypt")] key).await?;
let packet = send_recv_packet(
id,
packet,
port,
#[cfg(feature = "encrypt")]
key,
)
.await?;
match packet
{
match packet {
socket::Packet::CallResponse(resp) => Ok(resp.response),
_ => {
//imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url()));

View file

@ -6,13 +6,22 @@
#![warn(missing_docs)]
mod client_handler;
pub use client_handler::WebSocketHandler;
mod connection;
mod convert;
mod imports;
pub mod wasm;
#[allow(missing_docs)] // existence is pain otherwise
/*#[allow(missing_docs)] // existence is pain otherwise
pub mod _nrpc_js_interop {
include!(concat!(env!("OUT_DIR"), "/usdpl.rs"));
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}*/
#[allow(missing_docs)]
pub mod _helpers {
pub use js_sys;
pub use wasm_bindgen;
pub use wasm_bindgen_futures;
}
use std::sync::atomic::{AtomicU64, Ordering};
@ -99,7 +108,11 @@ pub fn version_usdpl() -> String {
#[wasm_bindgen]
pub fn set_value(key: String, value: JsValue) -> JsValue {
unsafe {
CACHE.as_mut().unwrap().insert(key, value).unwrap_or(JsValue::NULL)
CACHE
.as_mut()
.unwrap()
.insert(key, value)
.unwrap_or(JsValue::NULL)
}
}
@ -107,7 +120,12 @@ pub fn set_value(key: String, value: JsValue) -> JsValue {
#[wasm_bindgen]
pub fn get_value(key: String) -> JsValue {
unsafe {
CACHE.as_ref().unwrap().get(&key).map(|x| x.to_owned()).unwrap_or(JsValue::UNDEFINED)
CACHE
.as_ref()
.unwrap()
.get(&key)
.map(|x| x.to_owned())
.unwrap_or(JsValue::UNDEFINED)
}
}
@ -138,7 +156,7 @@ pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue {
}),
port,
#[cfg(feature = "encrypt")]
get_key()
get_key(),
)
.await;
let results = match results {
@ -168,8 +186,10 @@ pub async fn init_tr(locale: String) {
Packet::Language(locale.clone()),
get_port(),
#[cfg(feature = "encrypt")]
get_key()
).await {
get_key(),
)
.await
{
Ok(Packet::Translations(translations)) => {
#[cfg(feature = "debug")]
imports::console_log(&format!("USDPL: Got translations for {}", locale));
@ -179,12 +199,12 @@ pub async fn init_tr(locale: String) {
tr_map.insert(key, val);
}
unsafe { TRANSLATIONS = Some(tr_map) }
},
}
Ok(_) => {
#[cfg(feature = "debug")]
imports::console_error(&format!("USDPL: Got wrong packet response for init_tr"));
unsafe { TRANSLATIONS = None }
},
}
#[allow(unused_variables)]
Err(e) => {
#[cfg(feature = "debug")]

View file

@ -0,0 +1,94 @@
use js_sys::Array;
use super::{FromWasmable, IntoWasmable};
macro_rules! numbers_array {
($num_ty: ident) => {
impl FromWasmable<Array> for Vec<$num_ty> {
fn from_wasm(js: Array) -> Self {
let mut result = Vec::with_capacity(js.length() as usize);
js.for_each(&mut |val, _index, _arr| {
// according to MDN, this is guaranteed to be in order so index can be ignored
if let Some(val) = val.as_f64() {
result.push(val as $num_ty);
}
});
result
}
}
impl IntoWasmable<Array> for Vec<$num_ty> {
fn into_wasm(self) -> Array {
let result = Array::new();
for val in self {
result.push(&val.into());
}
result
}
}
};
}
numbers_array! { f64 }
numbers_array! { f32 }
numbers_array! { isize }
numbers_array! { usize }
numbers_array! { i8 }
numbers_array! { i16 }
numbers_array! { i32 }
numbers_array! { i64 }
numbers_array! { i128 }
numbers_array! { u8 }
numbers_array! { u16 }
numbers_array! { u32 }
numbers_array! { u64 }
numbers_array! { u128 }
impl FromWasmable<Array> for Vec<String> {
fn from_wasm(js: Array) -> Self {
let mut result = Vec::with_capacity(js.length() as usize);
js.for_each(&mut |val, _index, _arr| {
// according to MDN, this is guaranteed to be in order so index can be ignored
if let Some(val) = val.as_string() {
result.push(val);
}
});
result
}
}
impl IntoWasmable<Array> for Vec<String> {
fn into_wasm(self) -> Array {
let result = Array::new();
for val in self {
result.push(&val.into());
}
result
}
}
impl FromWasmable<Array> for Vec<bool> {
fn from_wasm(js: Array) -> Self {
let mut result = Vec::with_capacity(js.length() as usize);
js.for_each(&mut |val, _index, _arr| {
// according to MDN, this is guaranteed to be in order so index can be ignored
if let Some(val) = val.as_bool() {
result.push(val);
}
});
result
}
}
impl IntoWasmable<Array> for Vec<bool> {
fn into_wasm(self) -> Array {
let result = Array::new();
for val in self {
result.push(&val.into());
}
result
}
}

View file

@ -0,0 +1,99 @@
use std::collections::HashMap;
use js_sys::Map;
use super::{FromWasmable, IntoWasmable};
macro_rules! numbers_map {
($num_ty: ident) => {
impl FromWasmable<Map> for HashMap<String, $num_ty> {
fn from_wasm(js: Map) -> Self {
let mut result = HashMap::with_capacity(js.size() as usize);
js.for_each(&mut |key, val| {
if let Some(key) = key.as_string() {
if let Some(val) = val.as_f64() {
result.insert(key, val as $num_ty);
}
}
});
result
}
}
impl IntoWasmable<Map> for HashMap<String, $num_ty> {
fn into_wasm(self) -> Map {
let result = Map::new();
for (key, val) in self {
result.set(&key.into(), &val.into());
}
result
}
}
};
}
numbers_map! { f64 }
numbers_map! { f32 }
numbers_map! { isize }
numbers_map! { usize }
numbers_map! { i8 }
numbers_map! { i16 }
numbers_map! { i32 }
numbers_map! { i64 }
numbers_map! { i128 }
numbers_map! { u8 }
numbers_map! { u16 }
numbers_map! { u32 }
numbers_map! { u64 }
numbers_map! { u128 }
impl FromWasmable<Map> for HashMap<String, String> {
fn from_wasm(js: Map) -> Self {
let mut result = HashMap::with_capacity(js.size() as usize);
js.for_each(&mut |key, val| {
if let Some(key) = key.as_string() {
if let Some(val) = val.as_string() {
result.insert(key, val);
}
}
});
result
}
}
impl IntoWasmable<Map> for HashMap<String, String> {
fn into_wasm(self) -> Map {
let result = Map::new();
for (key, val) in self {
result.set(&key.into(), &val.into());
}
result
}
}
impl FromWasmable<Map> for HashMap<String, bool> {
fn from_wasm(js: Map) -> Self {
let mut result = HashMap::with_capacity(js.size() as usize);
js.for_each(&mut |key, val| {
if let Some(key) = key.as_string() {
if let Some(val) = val.as_bool() {
result.insert(key, val);
}
}
});
result
}
}
impl IntoWasmable<Map> for HashMap<String, bool> {
fn into_wasm(self) -> Map {
let result = Map::new();
for (key, val) in self {
result.set(&key.into(), &val.into());
}
result
}
}

View file

@ -0,0 +1,7 @@
//! WASM <-> Rust interop utilities
mod arrays;
mod maps;
mod trivials;
mod wasm_traits;
pub use wasm_traits::*;

View file

@ -0,0 +1,40 @@
use super::{FromWasmable, IntoWasmable};
macro_rules! trivial_convert {
($ty: ty) => {
impl FromWasmable<$ty> for $ty {
fn from_wasm(js: $ty) -> Self {
js
}
}
impl IntoWasmable<$ty> for $ty {
fn into_wasm(self) -> $ty {
self
}
}
};
}
trivial_convert! { f64 }
trivial_convert! { f32 }
trivial_convert! { isize }
trivial_convert! { usize }
trivial_convert! { i8 }
trivial_convert! { i16 }
trivial_convert! { i32 }
trivial_convert! { i64 }
trivial_convert! { i128 }
trivial_convert! { u8 }
trivial_convert! { u16 }
trivial_convert! { u32 }
trivial_convert! { u64 }
trivial_convert! { u128 }
trivial_convert! { bool }
trivial_convert! { String }
trivial_convert! { () }

View file

@ -0,0 +1,40 @@
/// A Rust type which supports Into/FromWasmAbi or WasmDescribe
pub trait KnownWasmCompatible {}
/// Convert Rust type to WASM-compatible type
pub trait IntoWasmable<T: KnownWasmCompatible> {
/// Required method
fn into_wasm(self) -> T;
}
/// Convert WASM-compatible type to Rust-centric type
pub trait FromWasmable<T: KnownWasmCompatible> {
/// Required method
fn from_wasm(js: T) -> Self;
}
impl KnownWasmCompatible for f64 {}
impl KnownWasmCompatible for f32 {}
impl KnownWasmCompatible for isize {}
impl KnownWasmCompatible for usize {}
impl KnownWasmCompatible for i8 {}
impl KnownWasmCompatible for i16 {}
impl KnownWasmCompatible for i32 {}
impl KnownWasmCompatible for i64 {}
impl KnownWasmCompatible for i128 {}
impl KnownWasmCompatible for u8 {}
impl KnownWasmCompatible for u16 {}
impl KnownWasmCompatible for u32 {}
impl KnownWasmCompatible for u64 {}
impl KnownWasmCompatible for u128 {}
impl KnownWasmCompatible for bool {}
impl KnownWasmCompatible for String {}
impl KnownWasmCompatible for () {}
impl KnownWasmCompatible for js_sys::Map {}
impl KnownWasmCompatible for js_sys::Array {}