diff --git a/build.sbt b/build.sbt index 1cfff06..835cd02 100644 --- a/build.sbt +++ b/build.sbt @@ -32,7 +32,7 @@ lazy val root = project "java.classTarget" -> (baseDirectory.value / "target" / "scala-3.2.2" / "classes").toString ), Test / javaOptions ++= Seq( - "-Djava.library.path="++path.value("glue.target"), + "-Djava.library.path=" ++ path.value("glue.target") ), Compile / doc / javacOptions ++= Seq( "--enable-preview", diff --git a/glue/Cargo.lock b/glue/Cargo.lock index f81de88..7ff5d11 100644 --- a/glue/Cargo.lock +++ b/glue/Cargo.lock @@ -582,9 +582,9 @@ dependencies = [ [[package]] name = "toad-jni" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6f9b7ff8462ec97df69ef2bb01b485626fb43ce62a8fe33c6179f3537a9d38" +checksum = "125f835c70283545b5840dff39d9bd132324d929041d4eaec1ed9378f08ac166" dependencies = [ "embedded-time", "jni", diff --git a/glue/Cargo.toml b/glue/Cargo.toml index ce23fc2..75b4e7e 100644 --- a/glue/Cargo.toml +++ b/glue/Cargo.toml @@ -15,7 +15,7 @@ e2e = [] jni = "0.21.1" nb = "1" toad = "0.17.3" -toad-jni = "0.10.1" +toad-jni = "0.11.0" no-std-net = "0.6" toad-msg = "0.18.1" tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]} diff --git a/glue/src/dev/toad/ffi/uint.rs b/glue/src/dev/toad/ffi/uint.rs index 3ef42e4..c99d2b8 100644 --- a/glue/src/dev/toad/ffi/uint.rs +++ b/glue/src/dev/toad/ffi/uint.rs @@ -1,6 +1,8 @@ use core::primitive as rust; -use toad_jni::java; +use jni::objects::JObject; +use jni::sys::{jbyteArray, jobject}; +use toad_jni::java::{self, Object}; #[allow(non_camel_case_types)] pub struct u64(java::lang::Object); @@ -84,3 +86,38 @@ impl u8 { CTOR.invoke(e, u.into()) } } + +#[no_mangle] +pub extern "system" fn Java_dev_toad_ffi_u8_toByte<'local>(mut env: java::Env<'local>, + u: JObject<'local>) + -> i8 { + let u = java::lang::Object::from_local(&mut env, u).upcast_to::(&mut env); + i8::from_be_bytes([u.to_rust(&mut env).to_be()]) +} + +#[no_mangle] +pub extern "system" fn Java_dev_toad_ffi_u16_toBytes<'local>(mut env: java::Env<'local>, + u: JObject<'local>) + -> jbyteArray { + let u = java::lang::Object::from_local(&mut env, u).upcast_to::(&mut env); + let bs = u.to_rust(&mut env).to_be_bytes(); + env.byte_array_from_slice(&bs).unwrap().as_raw() +} + +#[no_mangle] +pub extern "system" fn Java_dev_toad_ffi_u32_toBytes<'local>(mut env: java::Env<'local>, + u: JObject<'local>) + -> jbyteArray { + let u = java::lang::Object::from_local(&mut env, u).upcast_to::(&mut env); + let bs = u.to_rust(&mut env).to_be_bytes(); + env.byte_array_from_slice(&bs).unwrap().as_raw() +} + +#[no_mangle] +pub extern "system" fn Java_dev_toad_ffi_u64_toBytes<'local>(mut env: java::Env<'local>, + u: JObject<'local>) + -> jbyteArray { + let u = java::lang::Object::from_local(&mut env, u).upcast_to::(&mut env); + let bs = u.to_rust(&mut env).to_be_bytes(); + env.byte_array_from_slice(&bs).unwrap().as_raw() +} diff --git a/glue/src/dev/toad/mod.rs b/glue/src/dev/toad/mod.rs index bf97eb8..a919308 100644 --- a/glue/src/dev/toad/mod.rs +++ b/glue/src/dev/toad/mod.rs @@ -7,9 +7,11 @@ use std::net::{Ipv4Addr, SocketAddr}; use jni::objects::{JClass, JObject}; use jni::sys::jobject; pub use retry_strategy::RetryStrategy; +use toad::net::Addrd; use toad::platform::Platform; use toad::retry::{Attempts, Strategy}; use toad::time::Millis; +use toad_jni::java::net::InetSocketAddress; use toad_jni::java::nio::channels::{DatagramChannel, PeekableDatagramChannel}; use toad_jni::java::{self, Object}; @@ -44,8 +46,7 @@ impl Toad { fn poll_req_impl(e: &mut java::Env, addr: i64) -> java::util::Optional { match unsafe { Shared::deref::(addr).as_ref().unwrap() }.poll_req() { | Ok(req) => { - let msg_ptr: *mut toad_msg::alloc::Message = - unsafe { Shared::alloc_message(req.unwrap().into()) }; + let msg_ptr = unsafe { Shared::alloc_message(req.map(Into::into)) }; let mr = msg::ref_::Message::new(e, msg_ptr.addr() as i64); java::util::Optional::::of(e, mr) }, @@ -57,6 +58,46 @@ impl Toad { }, } } + + fn poll_resp_impl(e: &mut java::Env, + addr: i64, + token: msg::Token, + sock: InetSocketAddress) + -> java::util::Optional { + match unsafe { Shared::deref::(addr).as_ref().unwrap() }.poll_resp(token.to_toad(e), + sock.to_no_std(e)) + { + | Ok(resp) => { + let msg_ptr = unsafe { Shared::alloc_message(resp.map(Into::into)) }; + let mr = msg::ref_::Message::new(e, msg_ptr.addr() as i64); + java::util::Optional::::of(e, mr) + }, + | Err(nb::Error::WouldBlock) => java::util::Optional::empty(e), + | Err(nb::Error::Other(err)) => { + let err = err.downcast_ref(e).to_local(e); + e.throw(jni::objects::JThrowable::from(err)).unwrap(); + java::util::Optional::::empty(e) + }, + } + } + + fn send_message_impl(e: &mut java::Env, + addr: i64, + msg: msg::owned::Message) + -> java::util::Optional { + match unsafe { Shared::deref::(addr).as_ref().unwrap() }.send_msg(Addrd(msg.to_toad(e), msg.addr(e).unwrap().to_no_std(e))) { + | Ok((id, token)) => { + let out = IdAndToken::new(e, id, token); + java::util::Optional::of(e, out) + }, + | Err(nb::Error::WouldBlock) => java::util::Optional::empty(e), + | Err(nb::Error::Other(err)) => { + let err = err.downcast_ref(e).to_local(e); + e.throw(jni::objects::JThrowable::from(err)).unwrap(); + java::util::Optional::empty(e) + }, + } + } } java::object_newtype!(Toad); @@ -65,6 +106,21 @@ impl java::Class for Toad { const PATH: &'static str = package!(dev.toad.Toad); } +pub struct IdAndToken(java::lang::Object); + +java::object_newtype!(IdAndToken); +impl java::Class for IdAndToken { + const PATH: &'static str = concat!(package!(dev.toad.Toad), "$IdAndToken"); +} + +impl IdAndToken { + pub fn new(e: &mut java::Env, id: toad_msg::Id, token: toad_msg::Token) -> Self { + static CTOR: java::Constructor = java::Constructor::new(); + let (id, token) = (msg::Id::from_toad(e, id), msg::Token::from_toad(e, token)); + CTOR.invoke(e, id, token) + } +} + pub struct Config(java::lang::Object); java::object_newtype!(Config); @@ -275,6 +331,17 @@ pub extern "system" fn Java_dev_toad_Toad_init<'local>(mut e: java::Env<'local>, Toad::init_impl(e, cfg, channel.peekable()) } +#[no_mangle] +pub extern "system" fn Java_dev_toad_Toad_sendMessage<'local>(mut e: java::Env<'local>, + _: JClass<'local>, + addr: i64, + msg: JObject<'local>) + -> jobject { + let e = &mut e; + let msg: msg::owned::Message = java::lang::Object::from_local(e, msg).upcast_to(e); + Toad::send_message_impl(e, addr, msg).yield_to_java(e) +} + #[no_mangle] pub extern "system" fn Java_dev_toad_Toad_pollReq<'local>(mut e: java::Env<'local>, _: JClass<'local>, @@ -283,3 +350,16 @@ pub extern "system" fn Java_dev_toad_Toad_pollReq<'local>(mut e: java::Env<'loca let e = &mut e; Toad::poll_req_impl(e, addr).yield_to_java(e) } + +#[no_mangle] +pub extern "system" fn Java_dev_toad_Toad_pollResp<'local>(mut e: java::Env<'local>, + _: JClass<'local>, + addr: i64, + token: JObject<'local>, + sock: JObject<'local>) + -> jobject { + let e = &mut e; + let token = java::lang::Object::from_local(e, token).upcast_to::(e); + let sock = java::lang::Object::from_local(e, sock).upcast_to::(e); + Toad::poll_resp_impl(e, addr, token, sock).yield_to_java(e) +} diff --git a/glue/src/dev/toad/msg/code.rs b/glue/src/dev/toad/msg/code.rs index 0da4bee..6ce3349 100644 --- a/glue/src/dev/toad/msg/code.rs +++ b/glue/src/dev/toad/msg/code.rs @@ -9,7 +9,7 @@ impl java::Class for Code { impl Code { pub fn from_toad(e: &mut java::Env, code: toad_msg::Code) -> Self { - static CTOR: java::Constructor = java::Constructor::new(); + static CTOR: java::Constructor = java::Constructor::new(); CTOR.invoke(e, code.class.into(), code.detail.into()) } diff --git a/glue/src/dev/toad/msg/id.rs b/glue/src/dev/toad/msg/id.rs index 988ab58..22ddc7b 100644 --- a/glue/src/dev/toad/msg/id.rs +++ b/glue/src/dev/toad/msg/id.rs @@ -1,4 +1,6 @@ -use toad_jni::java; +use jni::objects::JClass; +use jni::sys::jobject; +use toad_jni::java::{self, Object}; use crate::dev::toad::ffi; @@ -42,3 +44,10 @@ mod tests { assert_eq!(toadj.to_toad(e), toad); } } + +#[no_mangle] +pub extern "system" fn Java_dev_toad_msg_Id_defaultId<'local>(mut env: java::Env<'local>, + _: JClass<'local>) + -> jobject { + Id::from_toad(&mut env, toad_msg::Id(0)).yield_to_java(&mut env) +} diff --git a/glue/src/dev/toad/msg/mod.rs b/glue/src/dev/toad/msg/mod.rs index 30d945c..fa48a77 100644 --- a/glue/src/dev/toad/msg/mod.rs +++ b/glue/src/dev/toad/msg/mod.rs @@ -1,6 +1,10 @@ +pub mod owned; pub mod ref_; mod ty; +use jni::objects::JObject; +use jni::sys::jobject; +use toad_jni::java; pub use ty::Type; mod code; diff --git a/glue/src/dev/toad/msg/owned/mod.rs b/glue/src/dev/toad/msg/owned/mod.rs new file mode 100644 index 0000000..bb1733f --- /dev/null +++ b/glue/src/dev/toad/msg/owned/mod.rs @@ -0,0 +1,8 @@ +mod msg; +pub use msg::Message; + +mod opt; +pub use opt::Opt; + +mod opt_value; +pub use opt_value::OptValue; diff --git a/glue/src/dev/toad/msg/owned/msg.rs b/glue/src/dev/toad/msg/owned/msg.rs new file mode 100644 index 0000000..b8a320a --- /dev/null +++ b/glue/src/dev/toad/msg/owned/msg.rs @@ -0,0 +1,90 @@ +use std::collections::BTreeMap; + +use jni::objects::JObject; +use jni::sys::{jbyteArray, jobject}; +use toad_jni::java::net::InetSocketAddress; +use toad_jni::java::util::ArrayList; +use toad_jni::java::{self}; +use toad_msg::{OptNumber, TryIntoBytes}; + +use crate::dev::toad::msg::owned::Opt; +use crate::dev::toad::msg::{Code, Id, Token, Type}; + +pub struct Message(java::lang::Object); + +java::object_newtype!(Message); +impl java::Class for Message { + const PATH: &'static str = package!(dev.toad.msg.owned.Message); +} + +impl Message { + pub fn id(&self, e: &mut java::Env) -> Id { + static ID: java::Field = java::Field::new("id"); + ID.get(e, self) + } + + pub fn token(&self, e: &mut java::Env) -> Token { + static TOKEN: java::Field = java::Field::new("token"); + TOKEN.get(e, self) + } + + pub fn ty(&self, e: &mut java::Env) -> Type { + static TY: java::Field = java::Field::new("type"); + TY.get(e, self) + } + + pub fn code(&self, e: &mut java::Env) -> Code { + static CODE: java::Field = java::Field::new("code"); + CODE.get(e, self) + } + + pub fn options(&self, e: &mut java::Env) -> Vec { + static OPTIONS: java::Field> = java::Field::new("opts"); + OPTIONS.get(e, self).into_iter().collect() + } + + pub fn payload(&self, e: &mut java::Env) -> Vec { + static PAYLOAD: java::Field> = java::Field::new("payload"); + PAYLOAD.get(e, self) + .into_iter() + .map(|i| u8::from_be_bytes(i.to_be_bytes())) + .collect() + } + + pub fn addr(&self, e: &mut java::Env) -> Option { + static ADDR: java::Field> = + java::Field::new("addr"); + ADDR.get(e, self).to_option(e) + } + + pub fn to_toad(&self, e: &mut java::Env) -> toad_msg::alloc::Message { + toad_msg::Message { id: self.id(e).to_toad(e), + ty: self.ty(e).to_toad(e), + ver: Default::default(), + token: self.token(e).to_toad(e), + code: self.code(e).to_toad(e), + opts: + self.options(e) + .into_iter() + .map(|opt| { + (opt.number(e), + opt.values(e) + .into_iter() + .map(|v| toad_msg::OptValue(v.bytes(e))) + .collect()) + }) + .collect::>>>>(), + payload: toad_msg::Payload(self.payload(e)) } + } +} + +#[no_mangle] +pub extern "system" fn Java_dev_toad_msg_owned_Message_toBytes<'local>(mut env: java::Env<'local>, + msg: JObject<'local>) + -> jbyteArray { + let jmsg = java::lang::Object::from_local(&mut env, msg).upcast_to::(&mut env); + let message = jmsg.to_toad(&mut env); + let bytes = message.try_into_bytes::>().unwrap(); + + env.byte_array_from_slice(&bytes).unwrap().as_raw() +} diff --git a/glue/src/dev/toad/msg/owned/opt.rs b/glue/src/dev/toad/msg/owned/opt.rs new file mode 100644 index 0000000..3b9a6cf --- /dev/null +++ b/glue/src/dev/toad/msg/owned/opt.rs @@ -0,0 +1,24 @@ +use toad_jni::java::util::ArrayList; +use toad_jni::java::{self}; +use toad_msg::OptNumber; + +use super::OptValue; + +pub struct Opt(java::lang::Object); + +java::object_newtype!(Opt); +impl java::Class for Opt { + const PATH: &'static str = package!(dev.toad.msg.owned.Option); +} + +impl Opt { + pub fn number(&self, e: &mut java::Env) -> OptNumber { + static NUMBER: java::Field = java::Field::new("number"); + OptNumber(NUMBER.get(e, self).to_rust(e)) + } + + pub fn values(&self, e: &mut java::Env) -> Vec { + static VALUES: java::Field> = java::Field::new("values"); + VALUES.get(e, self).into_iter().collect() + } +} diff --git a/glue/src/dev/toad/msg/owned/opt_value.rs b/glue/src/dev/toad/msg/owned/opt_value.rs new file mode 100644 index 0000000..2f076e5 --- /dev/null +++ b/glue/src/dev/toad/msg/owned/opt_value.rs @@ -0,0 +1,17 @@ +use toad_jni::java; + +pub struct OptValue(java::lang::Object); +java::object_newtype!(OptValue); +impl java::Class for OptValue { + const PATH: &'static str = package!(dev.toad.msg.owned.OptionValue); +} + +impl OptValue { + pub fn bytes(&self, e: &mut java::Env) -> Vec { + static BYTES: java::Field> = java::Field::new("bytes"); + BYTES.get(e, self) + .into_iter() + .map(|i| u8::from_be_bytes(i.to_be_bytes())) + .collect() + } +} diff --git a/glue/src/dev/toad/msg/ref_/msg.rs b/glue/src/dev/toad/msg/ref_/msg.rs index 424581b..dd06053 100644 --- a/glue/src/dev/toad/msg/ref_/msg.rs +++ b/glue/src/dev/toad/msg/ref_/msg.rs @@ -2,6 +2,8 @@ use std::collections::BTreeMap; use jni::objects::JClass; use jni::sys::jobject; +use toad::net::Addrd; +use toad_jni::java::net::InetSocketAddress; use toad_jni::java::{self, Object}; use crate::dev::toad::msg::ref_::Opt; @@ -21,24 +23,27 @@ impl Message { CTOR.invoke(env, msg_addr) } - pub fn to_toad(&self, env: &mut java::Env) -> toad_msg::alloc::Message { - toad_msg::alloc::Message { ty: self.ty(env), - ver: toad_msg::Version::default(), - code: self.code(env), - id: self.id(env), - token: self.token(env), - payload: toad_msg::Payload(self.payload(env)), - opts: self.options(env) - .into_iter() - .map(|opt| { - (opt.number(env), - opt.values(env) - .into_iter() - .map(|v| toad_msg::OptValue(v.bytes(env))) - .collect()) - }) - .collect::>>>>() } + pub fn to_toad(&self, env: &mut java::Env) -> Addrd { + let msg = toad_msg::alloc::Message { ty: self.ty(env), + ver: toad_msg::Version::default(), + code: self.code(env), + id: self.id(env), + token: self.token(env), + payload: toad_msg::Payload(self.payload(env)), + opts: self.options(env) + .into_iter() + .map(|opt| { + (opt.number(env), + opt.values(env) + .into_iter() + .map(|v| toad_msg::OptValue(v.bytes(env))) + .collect()) + }) + .collect::>>>>() }; + Addrd(msg, + self.addr(env) + .expect("java should have made sure the address was present")) } pub fn close(&self, env: &mut java::Env) { @@ -46,6 +51,14 @@ impl Message { CLOSE.invoke(env, self) } + pub fn addr(&self, env: &mut java::Env) -> Option { + static SOURCE: java::Method java::util::Optional> = + java::Method::new("addr"); + SOURCE.invoke(env, self) + .to_option(env) + .map(|a| a.to_no_std(env)) + } + pub fn ty(&self, env: &mut java::Env) -> toad_msg::Type { static TYPE: java::Method Type> = java::Method::new("type"); TYPE.invoke(env, self).to_toad(env) @@ -87,10 +100,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_id<'local>(mut env: java::E -> jobject { let e = &mut env; let msg = unsafe { - Shared::deref::(addr).as_ref() - .unwrap() + Shared::deref::>(addr).as_ref() + .unwrap() }; - Id::from_toad(e, msg.id).yield_to_java(e) + Id::from_toad(e, msg.data().id).yield_to_java(e) } #[no_mangle] @@ -100,10 +113,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_token<'local>(mut env: java -> jobject { let e = &mut env; let msg = unsafe { - Shared::deref::(addr).as_ref() - .unwrap() + Shared::deref::>(addr).as_ref() + .unwrap() }; - Token::from_toad(e, msg.token).yield_to_java(e) + Token::from_toad(e, msg.data().token).yield_to_java(e) } #[no_mangle] @@ -112,10 +125,12 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_payload<'local>(mut env: ja addr: i64) -> jobject { let msg = unsafe { - Shared::deref::(addr).as_ref() - .unwrap() + Shared::deref::>(addr).as_ref() + .unwrap() }; - env.byte_array_from_slice(&msg.payload.0).unwrap().as_raw() + env.byte_array_from_slice(&msg.data().payload.0) + .unwrap() + .as_raw() } #[no_mangle] @@ -124,10 +139,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_typ<'local>(mut e: java::En addr: i64) -> jobject { let msg = unsafe { - Shared::deref::(addr).as_ref() - .unwrap() + Shared::deref::>(addr).as_ref() + .unwrap() }; - Type::new(&mut e, msg.ty).yield_to_java(&mut e) + Type::new(&mut e, msg.data().ty).yield_to_java(&mut e) } #[no_mangle] @@ -136,10 +151,23 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_code<'local>(mut e: java::E addr: i64) -> jobject { let msg = unsafe { - Shared::deref::(addr).as_ref() - .unwrap() + Shared::deref::>(addr).as_ref() + .unwrap() }; - Code::from_toad(&mut e, msg.code).yield_to_java(&mut e) + Code::from_toad(&mut e, msg.data().code).yield_to_java(&mut e) +} + +#[no_mangle] +pub extern "system" fn Java_dev_toad_msg_ref_Message_addr<'local>(mut e: java::Env<'local>, + _: JClass<'local>, + addr: i64) + -> jobject { + let msg = unsafe { + Shared::deref::>(addr).as_ref() + .unwrap() + }; + + InetSocketAddress::from_no_std(&mut e, msg.addr()).yield_to_java(&mut e) } #[no_mangle] @@ -148,10 +176,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_opts<'local>(mut e: java::E addr: i64) -> jobject { let msg = unsafe { - Shared::deref::(addr).as_ref() - .unwrap() + Shared::deref::>(addr).as_ref() + .unwrap() }; - let opts = &msg.opts; + let opts = &msg.data().opts; let refs = opts.into_iter() .map(|(n, v)| Opt::new(&mut e, v as *const _ as i64, n.0.into())) @@ -182,7 +210,8 @@ mod tests { toad_msg.set_path("foo/bar/baz").ok(); toad_msg.set_payload(Payload(r#"{"id": 123, "stuff": ["abc"]}"#.as_bytes().to_vec())); - let ptr: *mut toad_msg::alloc::Message = Box::into_raw(Box::new(toad_msg)); + let ptr: *mut Addrd = + Box::into_raw(Box::new(Addrd(toad_msg, "127.0.0.1:1234".parse().unwrap()))); let msg = Message::new(e, ptr.addr() as i64); @@ -204,7 +233,8 @@ mod tests { toad_msg.set_path("foo/bar/baz").ok(); toad_msg.set_payload(Payload(r#"{"id": 123, "stuff": ["abc"]}"#.as_bytes().to_vec())); - let ptr: *mut toad_msg::alloc::Message = Box::into_raw(Box::new(toad_msg)); + let ptr: *mut Addrd = + Box::into_raw(Box::new(Addrd(toad_msg, "127.0.0.1:1234".parse().unwrap()))); let msg = Message::new(e, ptr.addr() as i64); diff --git a/glue/src/dev/toad/msg/token.rs b/glue/src/dev/toad/msg/token.rs index 0a55ce6..5f52092 100644 --- a/glue/src/dev/toad/msg/token.rs +++ b/glue/src/dev/toad/msg/token.rs @@ -1,5 +1,7 @@ +use jni::objects::JClass; +use jni::sys::jobject; use tinyvec::ArrayVec; -use toad_jni::java; +use toad_jni::java::{self, Object}; use crate::dev::toad::ffi; @@ -36,6 +38,13 @@ impl Token { } } +#[no_mangle] +pub extern "system" fn Java_dev_toad_msg_Token_defaultToken<'local>(mut env: java::Env<'local>, + _: JClass<'local>) + -> jobject { + Token::from_toad(&mut env, toad_msg::Token(Default::default())).yield_to_java(&mut env) +} + #[cfg(test)] mod tests { use tinyvec::array_vec; diff --git a/glue/src/lib.rs b/glue/src/lib.rs index be2f615..7198356 100644 --- a/glue/src/lib.rs +++ b/glue/src/lib.rs @@ -9,14 +9,12 @@ mod runtime { use std::collections::BTreeMap; use toad::config::Config; - use toad::net::Addrd; use toad::platform::{Effect, Platform}; - use toad::req::Req; - use toad::resp::Resp; use toad::step::runtime::Runtime as DefaultSteps; use toad_jni::java::io::IOException; - use toad_jni::java::lang::Throwable; + use toad_jni::java::lang::System; use toad_jni::java::nio::channels::PeekableDatagramChannel; + use toad_jni::java::{self, Object}; use toad_msg::{OptNumber, OptValue}; #[derive(Clone, Copy, Debug)] @@ -55,7 +53,10 @@ mod runtime { type Error = IOException; fn log(&self, level: log::Level, msg: toad::todo::String<1000>) -> Result<(), Self::Error> { - println!("[{}]: {}", level, msg.as_str()); + let mut e = java::env(); + let e = &mut e; + let (level, msg) = (level.to_string().downcast(e), msg.as_str().to_string().downcast(e)); + System::out(e).printf(e, "[%s]: %s", vec![level, msg]); Ok(()) } @@ -121,26 +122,26 @@ pub mod test { pub fn init<'a>() -> java::Env<'a> { static INIT: Once = Once::new(); INIT.call_once(|| { - let repo_root = Command::new("git").arg("rev-parse") - .arg("--show-toplevel") - .output() - .unwrap(); - assert!(repo_root.status.success()); + let repo_root = Command::new("git").arg("rev-parse") + .arg("--show-toplevel") + .output() + .unwrap(); + assert!(repo_root.status.success()); - let lib_path = String::from_utf8(repo_root.stdout).unwrap() - .trim() - .to_string(); - let lib_path = PathBuf::from(lib_path).join("target/glue/debug"); + let lib_path = String::from_utf8(repo_root.stdout).unwrap() + .trim() + .to_string(); + let lib_path = PathBuf::from(lib_path).join("target/glue/debug"); - let jvm = + let jvm = JavaVM::new(InitArgsBuilder::new().option(format!("-Djava.library.path={}", lib_path.to_string_lossy())) .option("-Djava.class.path=../target/scala-3.2.2/classes") .option("--enable-preview") .build() .unwrap()).unwrap(); - toad_jni::global::init_with(jvm); - }); + toad_jni::global::init_with(jvm); + }); let mut env = toad_jni::global::jvm().attach_current_thread_permanently() .unwrap(); diff --git a/glue/src/mem.rs b/glue/src/mem.rs index 1022bb3..5c7d7d0 100644 --- a/glue/src/mem.rs +++ b/glue/src/mem.rs @@ -1,5 +1,6 @@ use std::sync::Mutex; +use toad::net::Addrd; use toad_msg::alloc::Message; /// global [`RuntimeAllocator`] implementation @@ -15,10 +16,10 @@ pub trait SharedMemoryRegion: core::default::Default + core::fmt::Debug + Copy { /// Pass ownership of a [`Message`] to the shared memory region, /// yielding a stable pointer to this message. - unsafe fn alloc_message(m: Message) -> *mut Message; + unsafe fn alloc_message(m: Addrd) -> *mut Addrd; /// Delete a message from the shared memory region. - unsafe fn dealloc_message(m: *mut Message); + unsafe fn dealloc_message(m: *mut Addrd); /// Teardown unsafe fn dealloc(); @@ -38,7 +39,7 @@ static mut MEM: Mem = Mem { runtime: None, struct Mem { runtime: Option, - messages: Vec, + messages: Vec>, /// Lock used by `alloc_message` and `dealloc_message` to ensure /// they are run serially. @@ -63,7 +64,7 @@ impl SharedMemoryRegion for GlobalStatic { MEM.runtime.as_mut().unwrap() as _ } - unsafe fn alloc_message(m: Message) -> *mut Message { + unsafe fn alloc_message(m: Addrd) -> *mut Addrd { let Mem { ref mut messages, ref mut messages_lock, .. } = &mut MEM; @@ -73,7 +74,7 @@ impl SharedMemoryRegion for GlobalStatic { &mut messages[len - 1] as _ } - unsafe fn dealloc_message(m: *mut Message) { + unsafe fn dealloc_message(m: *mut Addrd) { let Mem { messages, messages_lock, .. } = &mut MEM; diff --git a/src/main/java/dev.toad/Async.java b/src/main/java/dev.toad/Async.java new file mode 100644 index 0000000..61b3af1 --- /dev/null +++ b/src/main/java/dev.toad/Async.java @@ -0,0 +1,39 @@ +package dev.toad; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public class Async { + + public static ScheduledExecutorService executor = + Executors.newSingleThreadScheduledExecutor(); + + public static CompletableFuture pollCompletable( + Supplier> sup + ) { + var fut = new CompletableFuture(); + var pollTask = Async.executor.scheduleAtFixedRate( + () -> { + try { + var t = sup.get(); + if (!t.isEmpty()) { + fut.complete(t.get()); + } + } catch (Throwable ex) { + fut.completeExceptionally(ex); + } + }, + 0, + 10, + TimeUnit.MILLISECONDS + ); + + return fut.whenComplete((t, err) -> { + pollTask.cancel(true); + }); + } +} diff --git a/src/main/java/dev.toad/Client.java b/src/main/java/dev.toad/Client.java index 4efa0f1..3a52ff2 100644 --- a/src/main/java/dev.toad/Client.java +++ b/src/main/java/dev.toad/Client.java @@ -1,10 +1,36 @@ package dev.toad; -public final class Client { +import dev.toad.msg.Message; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public final class Client implements AutoCloseable { final Toad toad; Client(Toad toad) { this.toad = toad; } + + public CompletableFuture send(Message message) { + if (message.addr().isEmpty()) { + throw new IllegalArgumentException( + "Message destination address must be set" + ); + } + + return Async + .pollCompletable(() -> this.toad.sendMessage(message)) + .thenCompose((Toad.IdAndToken sent) -> + Async.pollCompletable(() -> + this.toad.pollResp(sent.token, message.addr().get()) + ) + ) + .thenApply(msg -> msg.toOwned()); + } + + @Override + public void close() { + this.toad.close(); + } } diff --git a/src/main/java/dev.toad/Toad.java b/src/main/java/dev.toad/Toad.java index 917c7c2..957d0be 100644 --- a/src/main/java/dev.toad/Toad.java +++ b/src/main/java/dev.toad/Toad.java @@ -1,6 +1,7 @@ package dev.toad; import dev.toad.ffi.*; +import dev.toad.msg.*; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetSocketAddress; @@ -28,7 +29,7 @@ public final class Toad implements AutoCloseable { Toad.loadNativeLib(); } - static void loadNativeLib() { + public static void loadNativeLib() { System.loadLibrary("toad_java_glue"); } @@ -38,6 +39,11 @@ public final class Toad implements AutoCloseable { static native long init(DatagramChannel chan, Config o); + static native Optional sendMessage( + long ptr, + dev.toad.msg.owned.Message msg + ); + static native Optional pollReq(long ptr); static native Optional pollResp( @@ -46,12 +52,16 @@ public final class Toad implements AutoCloseable { InetSocketAddress n ); + public Optional sendMessage(Message msg) { + return Toad.sendMessage(this.ptr.addr(), msg.toOwned()); + } + public Optional pollReq() { return Toad.pollReq(this.ptr.addr()); } public Optional pollResp( - dev.toad.msg.Token regarding, + Token regarding, InetSocketAddress from ) { return Toad.pollResp(this.ptr.addr(), regarding, from); @@ -76,6 +86,17 @@ public final class Toad implements AutoCloseable { this.ptr.release(); } + public static final class IdAndToken { + + public final Id id; + public final Token token; + + public IdAndToken(Id id, Token token) { + this.id = id; + this.token = token; + } + } + public interface BuilderRequiresSocket { Toad.Builder port(short port); Toad.Builder address(InetSocketAddress addr); @@ -91,10 +112,11 @@ public final class Toad implements AutoCloseable { Builder() {} - public Toad build() throws IOException { - if (!this.ioException.isEmpty()) { + public Client buildClient() throws IOException { + if (this.ioException.isEmpty()) { var cfg = new Config(this.concurrency, this.msg.build()); - return new Toad(cfg, this.channel.get()); + var toad = new Toad(cfg, this.channel.get()); + return new Client(toad); } else { throw this.ioException.get(); } diff --git a/src/main/java/dev.toad/ffi/u16.java b/src/main/java/dev.toad/ffi/u16.java index ce4ccc8..acb2961 100644 --- a/src/main/java/dev.toad/ffi/u16.java +++ b/src/main/java/dev.toad/ffi/u16.java @@ -2,6 +2,8 @@ package dev.toad.ffi; public final class u16 { + public native byte[] toBytes(); + public static final int MAX = (int) (Math.pow(2, 16) - 1); private final int l; diff --git a/src/main/java/dev.toad/ffi/u32.java b/src/main/java/dev.toad/ffi/u32.java index c9a56ff..65424bf 100644 --- a/src/main/java/dev.toad/ffi/u32.java +++ b/src/main/java/dev.toad/ffi/u32.java @@ -2,6 +2,8 @@ package dev.toad.ffi; public final class u32 { + public native byte[] toBytes(); + public static final long MAX = (long) (Math.pow(2, 32) - 1); private final long l; diff --git a/src/main/java/dev.toad/ffi/u64.java b/src/main/java/dev.toad/ffi/u64.java index 149d6c5..a944667 100644 --- a/src/main/java/dev.toad/ffi/u64.java +++ b/src/main/java/dev.toad/ffi/u64.java @@ -4,6 +4,8 @@ import java.math.BigInteger; public final class u64 { + public native byte[] toBytes(); + public static final BigInteger MAX = BigInteger.TWO .pow(64) .subtract(BigInteger.ONE); diff --git a/src/main/java/dev.toad/msg/Code.java b/src/main/java/dev.toad/msg/Code.java index 5f0ed20..8f8d780 100644 --- a/src/main/java/dev.toad/msg/Code.java +++ b/src/main/java/dev.toad/msg/Code.java @@ -2,14 +2,45 @@ package dev.toad.msg; import dev.toad.ffi.u8; -public class Code { +public final class Code { final u8 clazz; final u8 detail; - public Code(short clazz, short detail) { - this.clazz = new u8(clazz); - this.detail = new u8(detail); + public static final Code EMPTY = new Code(0, 0); + + public static final Code GET = new Code(0, 1); + public static final Code POST = new Code(0, 2); + public static final Code PUT = new Code(0, 3); + public static final Code DELETE = new Code(0, 4); + + public static final Code OK_CREATED = new Code(2, 1); + public static final Code OK_DELETED = new Code(2, 2); + public static final Code OK_VALID = new Code(2, 3); + public static final Code OK_CHANGED = new Code(2, 4); + public static final Code OK_CONTENT = new Code(2, 5); + + public static final Code BAD_REQUEST = new Code(4, 0); + public static final Code UNAUTHORIZED = new Code(4, 1); + public static final Code BAD_OPTION = new Code(4, 2); + public static final Code FORBIDDEN = new Code(4, 3); + public static final Code NOT_FOUND = new Code(4, 4); + public static final Code METHOD_NOT_ALLOWED = new Code(4, 5); + public static final Code NOT_ACCEPTABLE = new Code(4, 6); + public static final Code PRECONDITION_FAILED = new Code(4, 12); + public static final Code REQUEST_ENTITY_TOO_LARGE = new Code(4, 13); + public static final Code UNSUPPORTED_CONTENT_FORMAT = new Code(4, 15); + + public static final Code INTERNAL_SERVER_ERROR = new Code(5, 0); + public static final Code NOT_IMPLEMENTED = new Code(5, 1); + public static final Code BAD_GATEWAY = new Code(5, 2); + public static final Code SERVICE_UNAVAILABLE = new Code(5, 3); + public static final Code GATEWAY_TIMEOUT = new Code(5, 4); + public static final Code PROXYING_NOT_SUPPORTED = new Code(5, 5); + + public Code(int clazz, int detail) { + this.clazz = new u8((short) clazz); + this.detail = new u8((short) detail); } public short codeClass() { diff --git a/src/main/java/dev.toad/msg/Id.java b/src/main/java/dev.toad/msg/Id.java index 519ce4b..fd26f65 100644 --- a/src/main/java/dev.toad/msg/Id.java +++ b/src/main/java/dev.toad/msg/Id.java @@ -4,6 +4,8 @@ import dev.toad.ffi.u16; public final class Id { + public static native Id defaultId(); + final u16 id; public Id(int id) { diff --git a/src/main/java/dev.toad/msg/Message.java b/src/main/java/dev.toad/msg/Message.java index 9bae185..5aa5f04 100644 --- a/src/main/java/dev.toad/msg/Message.java +++ b/src/main/java/dev.toad/msg/Message.java @@ -2,9 +2,10 @@ package dev.toad.msg; import java.net.InetSocketAddress; import java.util.List; +import java.util.Optional; public interface Message { - public InetSocketAddress source(); + public Optional addr(); public Id id(); @@ -19,4 +20,8 @@ public interface Message { public byte[] payloadBytes(); public String payloadString(); + + public dev.toad.msg.owned.Message toOwned(); + + public byte[] toBytes(); } diff --git a/src/main/java/dev.toad/msg/OptionValue.java b/src/main/java/dev.toad/msg/OptionValue.java index bdd3f53..532f0cd 100644 --- a/src/main/java/dev.toad/msg/OptionValue.java +++ b/src/main/java/dev.toad/msg/OptionValue.java @@ -4,4 +4,6 @@ public interface OptionValue { public byte[] asBytes(); public String asString(); + + public dev.toad.msg.owned.OptionValue toOwned(); } diff --git a/src/main/java/dev.toad/msg/Token.java b/src/main/java/dev.toad/msg/Token.java index 0800651..2306722 100644 --- a/src/main/java/dev.toad/msg/Token.java +++ b/src/main/java/dev.toad/msg/Token.java @@ -2,6 +2,8 @@ package dev.toad.msg; public final class Token { + public static native Token defaultToken(); + final byte[] bytes; public Token(byte[] bytes) { diff --git a/src/main/java/dev.toad/msg/build/Message.java b/src/main/java/dev.toad/msg/build/Message.java new file mode 100644 index 0000000..a7f22d4 --- /dev/null +++ b/src/main/java/dev.toad/msg/build/Message.java @@ -0,0 +1,103 @@ +package dev.toad.msg.build; + +import dev.toad.msg.Code; +import dev.toad.msg.Id; +import dev.toad.msg.Token; +import dev.toad.msg.Type; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class Message + implements MessageNeeds.Code, MessageNeeds.Destination, MessageNeeds.Type { + + HashMap> options = + new HashMap<>(); + Optional id = Optional.empty(); + Optional token = Optional.empty(); + Optional addr = Optional.empty(); + Optional code = Optional.empty(); + Optional type = Optional.empty(); + byte[] payload = new byte[] {}; + + Message() {} + + public static MessageNeeds.Destination builder() { + return new Message(); + } + + public MessageNeeds.Type addr(InetSocketAddress addr) { + this.addr = Optional.of(addr); + return this; + } + + public MessageNeeds.Code type(Type type) { + this.type = Optional.of(type); + return this; + } + + public Message code(Code code) { + this.code = Optional.of(code); + return this; + } + + public Message id(Id id) { + this.id = Optional.of(id); + return this; + } + + public Message token(Token token) { + this.token = Optional.of(token); + return this; + } + + public Message option( + Function fun + ) { + var opt = fun.apply(Option.builder()); + return this.option(opt); + } + + public Message option(dev.toad.msg.Option opt) { + return this.option(opt.number(), opt.values()); + } + + public Message option(long number, List values) { + if (this.options.get(number) == null) { + this.options.put(number, new ArrayList<>()); + } + + this.options.get(number) + .addAll(values.stream().map(v -> v.toOwned()).toList()); + return this; + } + + public Message payload(String payload) { + this.payload = payload.getBytes(); + return this; + } + + public Message payload(byte[] payload) { + this.payload = payload; + return this; + } + + public dev.toad.msg.Message build() { + return new dev.toad.msg.owned.Message( + this.addr, + this.type.get(), + this.code.get(), + this.id.orElse(Id.defaultId()), + this.token.orElse(Token.defaultToken()), + this.payload, + this.options.entrySet() + .stream() + .map(ent -> new dev.toad.msg.owned.Option(ent.getKey(), ent.getValue())) + .collect(Collectors.toCollection(() -> new ArrayList<>())) + ); + } +} diff --git a/src/main/java/dev.toad/msg/build/MessageNeeds.java b/src/main/java/dev.toad/msg/build/MessageNeeds.java new file mode 100644 index 0000000..b5bdc76 --- /dev/null +++ b/src/main/java/dev.toad/msg/build/MessageNeeds.java @@ -0,0 +1,18 @@ +package dev.toad.msg.build; + +import java.net.InetSocketAddress; + +public final class MessageNeeds { + + public interface Destination { + MessageNeeds.Type addr(InetSocketAddress addr); + } + + public interface Type { + MessageNeeds.Code type(dev.toad.msg.Type type); + } + + public interface Code { + Message code(dev.toad.msg.Code code); + } +} diff --git a/src/main/java/dev.toad/msg/build/Option.java b/src/main/java/dev.toad/msg/build/Option.java new file mode 100644 index 0000000..6c0a82b --- /dev/null +++ b/src/main/java/dev.toad/msg/build/Option.java @@ -0,0 +1,36 @@ +package dev.toad.msg.build; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.stream.Collectors; + +public final class Option implements OptionNeeds.Number { + + Optional number = Optional.empty(); + ArrayList values = new ArrayList<>(); + + Option() {} + + public static OptionNeeds.Number builder() { + return new Option(); + } + + public Option number(long num) { + this.number = Optional.of(num); + return this; + } + + public Option addValue(byte[] bytes) { + this.values.add(bytes); + return this; + } + + public dev.toad.msg.owned.Option build() { + return new dev.toad.msg.owned.Option( + this.number.get(), + this.values.stream() + .map(bytes -> new dev.toad.msg.owned.OptionValue(bytes)) + .collect(Collectors.toCollection(() -> new ArrayList<>())) + ); + } +} diff --git a/src/main/java/dev.toad/msg/build/OptionNeeds.java b/src/main/java/dev.toad/msg/build/OptionNeeds.java new file mode 100644 index 0000000..bf73817 --- /dev/null +++ b/src/main/java/dev.toad/msg/build/OptionNeeds.java @@ -0,0 +1,8 @@ +package dev.toad.msg.build; + +public final class OptionNeeds { + + public interface Number { + Option number(long num); + } +} diff --git a/src/main/java/dev.toad/msg/option/Accept.java b/src/main/java/dev.toad/msg/option/Accept.java new file mode 100644 index 0000000..8215227 --- /dev/null +++ b/src/main/java/dev.toad/msg/option/Accept.java @@ -0,0 +1,29 @@ +package dev.toad.msg.option; + +import dev.toad.ffi.u16; +import dev.toad.msg.Option; +import dev.toad.msg.OptionValue; +import java.util.ArrayList; +import java.util.List; + +public final class Accept extends ContentFormat implements Option { + + public static final Accept TEXT = new Accept(ContentFormat.TEXT); + public static final Accept LINK_FORMAT = new Accept( + ContentFormat.LINK_FORMAT + ); + public static final Accept XML = new Accept(ContentFormat.XML); + public static final Accept OCTET_STREAM = new Accept( + ContentFormat.OCTET_STREAM + ); + public static final Accept EXI = new Accept(ContentFormat.EXI); + public static final Accept JSON = new Accept(ContentFormat.JSON); + + Accept(int value) { + super(value); + } + + public Accept(ContentFormat format) { + this(format.value()); + } +} diff --git a/src/main/java/dev.toad/msg/option/ContentFormat.java b/src/main/java/dev.toad/msg/option/ContentFormat.java new file mode 100644 index 0000000..70a2ccf --- /dev/null +++ b/src/main/java/dev.toad/msg/option/ContentFormat.java @@ -0,0 +1,45 @@ +package dev.toad.msg.option; + +import dev.toad.ffi.u16; +import dev.toad.msg.Option; +import dev.toad.msg.OptionValue; +import java.util.ArrayList; +import java.util.List; + +public sealed class ContentFormat implements Option permits Accept { + + final u16 value; + + ContentFormat(int value) { + this.value = new u16(value); + } + + public static final ContentFormat TEXT = ContentFormat.custom(0); + public static final ContentFormat LINK_FORMAT = ContentFormat.custom(40); + public static final ContentFormat XML = ContentFormat.custom(41); + public static final ContentFormat OCTET_STREAM = ContentFormat.custom(42); + public static final ContentFormat EXI = ContentFormat.custom(47); + public static final ContentFormat JSON = ContentFormat.custom(50); + + public static ContentFormat custom(int value) { + return new ContentFormat(value); + } + + public boolean equals(ContentFormat other) { + return this.value == other.value; + } + + public long number() { + return 12; + } + + public int value() { + return this.value.intValue(); + } + + public List values() { + var list = new ArrayList(); + list.add(new dev.toad.msg.owned.OptionValue(this.value.toBytes())); + return list; + } +} diff --git a/src/main/java/dev.toad/msg/owned/Message.java b/src/main/java/dev.toad/msg/owned/Message.java index c0279e8..7758142 100644 --- a/src/main/java/dev.toad/msg/owned/Message.java +++ b/src/main/java/dev.toad/msg/owned/Message.java @@ -5,28 +5,31 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; public class Message implements dev.toad.msg.Message { - final InetSocketAddress source; + public native byte[] toBytes(); + + final Optional addr; final Id id; final Token token; final byte[] payload; final Code code; final Type type; - final List opts; + final ArrayList opts; public Message( - InetSocketAddress source, + Optional addr, Type type, Code code, Id id, Token token, byte[] payload, - List opts + ArrayList opts ) { - this.source = source; + this.addr = addr; this.id = id; this.token = token; this.payload = payload; @@ -37,7 +40,7 @@ public class Message implements dev.toad.msg.Message { public Message(dev.toad.msg.ref.Message ref) { this( - ref.source(), + ref.addr(), ref.type(), ref.code(), ref.id(), @@ -46,13 +49,17 @@ public class Message implements dev.toad.msg.Message { Arrays .asList(ref.optionRefs()) .stream() - .map(dev.toad.msg.ref.Option::clone) - .collect(Collectors.toList()) + .map(dev.toad.msg.ref.Option::toOwned) + .collect(Collectors.toCollection(() -> new ArrayList<>())) ); } - public InetSocketAddress source() { - return this.source; + public Message toOwned() { + return this; + } + + public Optional addr() { + return this.addr; } public Id id() { @@ -72,7 +79,7 @@ public class Message implements dev.toad.msg.Message { } public List options() { - return this.opts; + return List.copyOf(this.opts); } public byte[] payloadBytes() { diff --git a/src/main/java/dev.toad/msg/owned/Option.java b/src/main/java/dev.toad/msg/owned/Option.java index ca94e5e..25bcf3f 100644 --- a/src/main/java/dev.toad/msg/owned/Option.java +++ b/src/main/java/dev.toad/msg/owned/Option.java @@ -1,17 +1,19 @@ package dev.toad.msg.owned; +import dev.toad.ffi.u32; import dev.toad.msg.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Option implements dev.toad.msg.Option { - final long number; - final List values; + final u32 number; + final ArrayList values; - public Option(long number, List values) { - this.number = number; + public Option(long number, ArrayList values) { + this.number = new u32(number); this.values = values; } @@ -21,16 +23,16 @@ public class Option implements dev.toad.msg.Option { Arrays .asList(ref.valueRefs()) .stream() - .map(dev.toad.msg.ref.OptionValue::clone) - .collect(Collectors.toList()) + .map(dev.toad.msg.ref.OptionValue::toOwned) + .collect(Collectors.toCollection(() -> new ArrayList<>())) ); } public long number() { - return this.number; + return this.number.longValue(); } public List values() { - return this.values; + return List.copyOf(this.values); } } diff --git a/src/main/java/dev.toad/msg/owned/OptionValue.java b/src/main/java/dev.toad/msg/owned/OptionValue.java index 1fc9cab..ce4f0f7 100644 --- a/src/main/java/dev.toad/msg/owned/OptionValue.java +++ b/src/main/java/dev.toad/msg/owned/OptionValue.java @@ -19,4 +19,8 @@ public class OptionValue implements dev.toad.msg.OptionValue { public String asString() { return new String(this.asBytes()); } + + public dev.toad.msg.owned.OptionValue toOwned() { + return this; + } } diff --git a/src/main/java/dev.toad/msg/ref/Message.java b/src/main/java/dev.toad/msg/ref/Message.java index 63848af..2db4391 100644 --- a/src/main/java/dev.toad/msg/ref/Message.java +++ b/src/main/java/dev.toad/msg/ref/Message.java @@ -18,9 +18,9 @@ public final class Message implements dev.toad.msg.Message, AutoCloseable { Ptr ptr; - Optional source = Optional.empty(); + Optional addr = Optional.empty(); - static native InetSocketAddress source(long addr); + static native InetSocketAddress addr(long addr); static native Id id(long addr); @@ -34,20 +34,22 @@ public final class Message implements dev.toad.msg.Message, AutoCloseable { static native dev.toad.msg.ref.Option[] opts(long addr); + static native byte[] toBytes(long addr); + Message(long addr) { this.ptr = Ptr.register(this.getClass(), addr); } - public dev.toad.msg.Message clone() { + public dev.toad.msg.owned.Message toOwned() { return new dev.toad.msg.owned.Message(this); } - public InetSocketAddress source() { - if (this.source.isEmpty()) { - this.source = Optional.of(this.source(this.ptr.addr())); + public Optional addr() { + if (this.addr.isEmpty()) { + this.addr = Optional.of(this.addr(this.ptr.addr())); } - return this.source.get(); + return this.addr; } public Id id() { @@ -82,6 +84,10 @@ public final class Message implements dev.toad.msg.Message, AutoCloseable { return new String(this.payload(this.ptr.addr())); } + public byte[] toBytes() { + return this.toBytes(this.ptr.addr()); + } + @Override public void close() { this.ptr.release(); diff --git a/src/main/java/dev.toad/msg/ref/Option.java b/src/main/java/dev.toad/msg/ref/Option.java index a6ab620..bfcb7d4 100644 --- a/src/main/java/dev.toad/msg/ref/Option.java +++ b/src/main/java/dev.toad/msg/ref/Option.java @@ -29,7 +29,7 @@ public class Option implements dev.toad.msg.Option, AutoCloseable { return Arrays.asList(this.values(this.ptr.addr())); } - public dev.toad.msg.Option clone() { + public dev.toad.msg.owned.Option toOwned() { return new dev.toad.msg.owned.Option(this); } diff --git a/src/main/java/dev.toad/msg/ref/OptionValue.java b/src/main/java/dev.toad/msg/ref/OptionValue.java index 3483cbd..96e0b3c 100644 --- a/src/main/java/dev.toad/msg/ref/OptionValue.java +++ b/src/main/java/dev.toad/msg/ref/OptionValue.java @@ -21,7 +21,7 @@ public final class OptionValue return new String(this.bytes(this.ptr.addr())); } - public dev.toad.msg.OptionValue clone() { + public dev.toad.msg.owned.OptionValue toOwned() { return new dev.toad.msg.owned.OptionValue(this); } diff --git a/src/test/java/Mock.java b/src/test/java/Mock.java index d7c2387..6522273 100644 --- a/src/test/java/Mock.java +++ b/src/test/java/Mock.java @@ -97,7 +97,6 @@ public class Mock { return ent.getKey(); } } - return null; } diff --git a/src/test/scala/Async.scala b/src/test/scala/Async.scala new file mode 100644 index 0000000..96d6566 --- /dev/null +++ b/src/test/scala/Async.scala @@ -0,0 +1,49 @@ +import dev.toad.Async +import java.util.Optional +import java.util.concurrent.TimeUnit +import java.util.concurrent.CompletableFuture + +class AsyncTest extends munit.FunSuite { + test("pollCompletable polls then completes") { + var polls = 0 + + val fut = Async.pollCompletable(() => { + polls = polls + 1 + if polls == 10 then { + Optional.of(true) + } else { + Optional.empty + } + }) + + fut.get(200, TimeUnit.MILLISECONDS) + } + + test("pollCompletable does not poll after completion") { + var polls = 0 + val fut = Async.pollCompletable(() => { + polls = polls + 1 + Optional.of(true) + }) + + fut.get() + + Thread.sleep(100) + assertEquals(polls, 1) + } + + test("pollCompletable completesExceptionally when poll throws") { + val err = Async + .pollCompletable[Object](() => throw Exception("foo")) + .handle((ok, e) => { + if ok != null then { + "CompletableFuture was completed without exception" + } else { + e.getMessage + } + }) + .get() + + assertEquals(err, "java.lang.Exception: foo") + } +} diff --git a/src/test/scala/E2E.scala b/src/test/scala/E2E.scala index 62b4ed7..ef148ca 100644 --- a/src/test/scala/E2E.scala +++ b/src/test/scala/E2E.scala @@ -1,10 +1,60 @@ -import dev.toad.*; -import mock.java.nio.channels.Mock; +import dev.toad.* +import dev.toad.msg.* +import dev.toad.msg.option.ContentFormat +import dev.toad.msg.option.Accept +import mock.java.nio.channels.Mock +import java.net.InetSocketAddress +import java.util.ArrayList +import java.nio.ByteBuffer +import java.util.concurrent.TimeUnit class E2E extends munit.FunSuite { - test("foo") { + test("minimal client and server") { + Toad.loadNativeLib() + val mock = Mock.Channel() - val toad = Toad.builder.channel(mock).build - val req = Option.apply(toad.pollReq.get) + + val ack = dev.toad.msg.build.Message + .builder() + .addr(InetSocketAddress("127.0.0.1", 1111)) + .`type`(Type.ACK) + .code(Code.EMPTY) + .id(Id(2)) + .token(Token(Array(1))) + .option(ContentFormat.TEXT) + .payload("foobar") + .build + + val resp = dev.toad.msg.build.Message + .builder() + .addr(InetSocketAddress("127.0.0.1", 1111)) + .`type`(Type.NON) + .code(Code.OK_CONTENT) + .id(Id(3)) + .token(Token(Array(1))) + .option(ContentFormat.TEXT) + .payload("foobar") + .build + + val req = dev.toad.msg.build.Message + .builder() + .addr(InetSocketAddress("127.0.0.1", 2222)) + .`type`(Type.CON) + .code(Code(2, 4)) + .id(Id(1)) + .token(Token(Array(1))) + .option(Accept.TEXT) + .build + + val client = Toad.builder.channel(mock).buildClient + val respFuture = client.send(req) + + var bufs = ArrayList[ByteBuffer]() + bufs.add(ByteBuffer.wrap(resp.toBytes())) + mock.recv.put(InetSocketAddress("127.0.0.1", 2222), bufs) + + val respActual = respFuture.get(1, TimeUnit.SECONDS) + + assertEquals(resp.payloadBytes().toSeq, respActual.payloadBytes().toSeq) } } diff --git a/src/test/scala/Glue.scala b/src/test/scala/Glue.scala index b236720..a917f62 100644 --- a/src/test/scala/Glue.scala +++ b/src/test/scala/Glue.scala @@ -2,6 +2,10 @@ import sys.process._ class Glue extends munit.FunSuite { test("cargo test") { - Seq("sh", "-c", "cd glue; RUST_BACKTRACE=full cargo test --quiet --features e2e").!! + Seq( + "sh", + "-c", + "cd glue; RUST_BACKTRACE=full cargo test --quiet --features e2e" + ).!! } }