feat: e2e test
This commit is contained in:
parent
af1f249d9a
commit
c13f3b9dbc
@ -32,7 +32,7 @@ lazy val root = project
|
|||||||
"java.classTarget" -> (baseDirectory.value / "target" / "scala-3.2.2" / "classes").toString
|
"java.classTarget" -> (baseDirectory.value / "target" / "scala-3.2.2" / "classes").toString
|
||||||
),
|
),
|
||||||
Test / javaOptions ++= Seq(
|
Test / javaOptions ++= Seq(
|
||||||
"-Djava.library.path="++path.value("glue.target"),
|
"-Djava.library.path=" ++ path.value("glue.target")
|
||||||
),
|
),
|
||||||
Compile / doc / javacOptions ++= Seq(
|
Compile / doc / javacOptions ++= Seq(
|
||||||
"--enable-preview",
|
"--enable-preview",
|
||||||
|
4
glue/Cargo.lock
generated
4
glue/Cargo.lock
generated
@ -582,9 +582,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toad-jni"
|
name = "toad-jni"
|
||||||
version = "0.10.1"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e6f9b7ff8462ec97df69ef2bb01b485626fb43ce62a8fe33c6179f3537a9d38"
|
checksum = "125f835c70283545b5840dff39d9bd132324d929041d4eaec1ed9378f08ac166"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embedded-time",
|
"embedded-time",
|
||||||
"jni",
|
"jni",
|
||||||
|
@ -15,7 +15,7 @@ e2e = []
|
|||||||
jni = "0.21.1"
|
jni = "0.21.1"
|
||||||
nb = "1"
|
nb = "1"
|
||||||
toad = "0.17.3"
|
toad = "0.17.3"
|
||||||
toad-jni = "0.10.1"
|
toad-jni = "0.11.0"
|
||||||
no-std-net = "0.6"
|
no-std-net = "0.6"
|
||||||
toad-msg = "0.18.1"
|
toad-msg = "0.18.1"
|
||||||
tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]}
|
tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use core::primitive as rust;
|
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)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct u64(java::lang::Object);
|
pub struct u64(java::lang::Object);
|
||||||
@ -84,3 +86,38 @@ impl u8 {
|
|||||||
CTOR.invoke(e, u.into())
|
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::<u8>(&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::<u16>(&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::<u32>(&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::<u64>(&mut env);
|
||||||
|
let bs = u.to_rust(&mut env).to_be_bytes();
|
||||||
|
env.byte_array_from_slice(&bs).unwrap().as_raw()
|
||||||
|
}
|
||||||
|
@ -7,9 +7,11 @@ use std::net::{Ipv4Addr, SocketAddr};
|
|||||||
use jni::objects::{JClass, JObject};
|
use jni::objects::{JClass, JObject};
|
||||||
use jni::sys::jobject;
|
use jni::sys::jobject;
|
||||||
pub use retry_strategy::RetryStrategy;
|
pub use retry_strategy::RetryStrategy;
|
||||||
|
use toad::net::Addrd;
|
||||||
use toad::platform::Platform;
|
use toad::platform::Platform;
|
||||||
use toad::retry::{Attempts, Strategy};
|
use toad::retry::{Attempts, Strategy};
|
||||||
use toad::time::Millis;
|
use toad::time::Millis;
|
||||||
|
use toad_jni::java::net::InetSocketAddress;
|
||||||
use toad_jni::java::nio::channels::{DatagramChannel, PeekableDatagramChannel};
|
use toad_jni::java::nio::channels::{DatagramChannel, PeekableDatagramChannel};
|
||||||
use toad_jni::java::{self, Object};
|
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<msg::ref_::Message> {
|
fn poll_req_impl(e: &mut java::Env, addr: i64) -> java::util::Optional<msg::ref_::Message> {
|
||||||
match unsafe { Shared::deref::<Runtime>(addr).as_ref().unwrap() }.poll_req() {
|
match unsafe { Shared::deref::<Runtime>(addr).as_ref().unwrap() }.poll_req() {
|
||||||
| Ok(req) => {
|
| Ok(req) => {
|
||||||
let msg_ptr: *mut toad_msg::alloc::Message =
|
let msg_ptr = unsafe { Shared::alloc_message(req.map(Into::into)) };
|
||||||
unsafe { Shared::alloc_message(req.unwrap().into()) };
|
|
||||||
let mr = msg::ref_::Message::new(e, msg_ptr.addr() as i64);
|
let mr = msg::ref_::Message::new(e, msg_ptr.addr() as i64);
|
||||||
java::util::Optional::<msg::ref_::Message>::of(e, mr)
|
java::util::Optional::<msg::ref_::Message>::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<msg::ref_::Message> {
|
||||||
|
match unsafe { Shared::deref::<Runtime>(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::<msg::ref_::Message>::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::<msg::ref_::Message>::empty(e)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_message_impl(e: &mut java::Env,
|
||||||
|
addr: i64,
|
||||||
|
msg: msg::owned::Message)
|
||||||
|
-> java::util::Optional<IdAndToken> {
|
||||||
|
match unsafe { Shared::deref::<Runtime>(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);
|
java::object_newtype!(Toad);
|
||||||
@ -65,6 +106,21 @@ impl java::Class for Toad {
|
|||||||
const PATH: &'static str = package!(dev.toad.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<IdAndToken, fn(msg::Id, msg::Token)> = 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);
|
pub struct Config(java::lang::Object);
|
||||||
|
|
||||||
java::object_newtype!(Config);
|
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())
|
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]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_dev_toad_Toad_pollReq<'local>(mut e: java::Env<'local>,
|
pub extern "system" fn Java_dev_toad_Toad_pollReq<'local>(mut e: java::Env<'local>,
|
||||||
_: JClass<'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;
|
let e = &mut e;
|
||||||
Toad::poll_req_impl(e, addr).yield_to_java(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::<msg::Token>(e);
|
||||||
|
let sock = java::lang::Object::from_local(e, sock).upcast_to::<InetSocketAddress>(e);
|
||||||
|
Toad::poll_resp_impl(e, addr, token, sock).yield_to_java(e)
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ impl java::Class for Code {
|
|||||||
|
|
||||||
impl Code {
|
impl Code {
|
||||||
pub fn from_toad(e: &mut java::Env, code: toad_msg::Code) -> Self {
|
pub fn from_toad(e: &mut java::Env, code: toad_msg::Code) -> Self {
|
||||||
static CTOR: java::Constructor<Code, fn(i16, i16)> = java::Constructor::new();
|
static CTOR: java::Constructor<Code, fn(i32, i32)> = java::Constructor::new();
|
||||||
CTOR.invoke(e, code.class.into(), code.detail.into())
|
CTOR.invoke(e, code.class.into(), code.detail.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
use crate::dev::toad::ffi;
|
||||||
|
|
||||||
@ -42,3 +44,10 @@ mod tests {
|
|||||||
assert_eq!(toadj.to_toad(e), toad);
|
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)
|
||||||
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
|
pub mod owned;
|
||||||
pub mod ref_;
|
pub mod ref_;
|
||||||
|
|
||||||
mod ty;
|
mod ty;
|
||||||
|
use jni::objects::JObject;
|
||||||
|
use jni::sys::jobject;
|
||||||
|
use toad_jni::java;
|
||||||
pub use ty::Type;
|
pub use ty::Type;
|
||||||
|
|
||||||
mod code;
|
mod code;
|
||||||
|
8
glue/src/dev/toad/msg/owned/mod.rs
Normal file
8
glue/src/dev/toad/msg/owned/mod.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
mod msg;
|
||||||
|
pub use msg::Message;
|
||||||
|
|
||||||
|
mod opt;
|
||||||
|
pub use opt::Opt;
|
||||||
|
|
||||||
|
mod opt_value;
|
||||||
|
pub use opt_value::OptValue;
|
90
glue/src/dev/toad/msg/owned/msg.rs
Normal file
90
glue/src/dev/toad/msg/owned/msg.rs
Normal file
@ -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<Message, Id> = java::Field::new("id");
|
||||||
|
ID.get(e, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn token(&self, e: &mut java::Env) -> Token {
|
||||||
|
static TOKEN: java::Field<Message, Token> = java::Field::new("token");
|
||||||
|
TOKEN.get(e, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ty(&self, e: &mut java::Env) -> Type {
|
||||||
|
static TY: java::Field<Message, Type> = java::Field::new("type");
|
||||||
|
TY.get(e, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code(&self, e: &mut java::Env) -> Code {
|
||||||
|
static CODE: java::Field<Message, Code> = java::Field::new("code");
|
||||||
|
CODE.get(e, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options(&self, e: &mut java::Env) -> Vec<Opt> {
|
||||||
|
static OPTIONS: java::Field<Message, ArrayList<Opt>> = java::Field::new("opts");
|
||||||
|
OPTIONS.get(e, self).into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload(&self, e: &mut java::Env) -> Vec<u8> {
|
||||||
|
static PAYLOAD: java::Field<Message, Vec<i8>> = 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<InetSocketAddress> {
|
||||||
|
static ADDR: java::Field<Message, java::util::Optional<InetSocketAddress>> =
|
||||||
|
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::<BTreeMap<OptNumber, Vec<toad_msg::OptValue<Vec<u8>>>>>(),
|
||||||
|
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::<Message>(&mut env);
|
||||||
|
let message = jmsg.to_toad(&mut env);
|
||||||
|
let bytes = message.try_into_bytes::<Vec<u8>>().unwrap();
|
||||||
|
|
||||||
|
env.byte_array_from_slice(&bytes).unwrap().as_raw()
|
||||||
|
}
|
24
glue/src/dev/toad/msg/owned/opt.rs
Normal file
24
glue/src/dev/toad/msg/owned/opt.rs
Normal file
@ -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<Opt, crate::dev::toad::ffi::u32> = java::Field::new("number");
|
||||||
|
OptNumber(NUMBER.get(e, self).to_rust(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values(&self, e: &mut java::Env) -> Vec<OptValue> {
|
||||||
|
static VALUES: java::Field<Opt, ArrayList<OptValue>> = java::Field::new("values");
|
||||||
|
VALUES.get(e, self).into_iter().collect()
|
||||||
|
}
|
||||||
|
}
|
17
glue/src/dev/toad/msg/owned/opt_value.rs
Normal file
17
glue/src/dev/toad/msg/owned/opt_value.rs
Normal file
@ -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<u8> {
|
||||||
|
static BYTES: java::Field<OptValue, Vec<i8>> = java::Field::new("bytes");
|
||||||
|
BYTES.get(e, self)
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| u8::from_be_bytes(i.to_be_bytes()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use jni::objects::JClass;
|
use jni::objects::JClass;
|
||||||
use jni::sys::jobject;
|
use jni::sys::jobject;
|
||||||
|
use toad::net::Addrd;
|
||||||
|
use toad_jni::java::net::InetSocketAddress;
|
||||||
use toad_jni::java::{self, Object};
|
use toad_jni::java::{self, Object};
|
||||||
|
|
||||||
use crate::dev::toad::msg::ref_::Opt;
|
use crate::dev::toad::msg::ref_::Opt;
|
||||||
@ -21,8 +23,8 @@ impl Message {
|
|||||||
CTOR.invoke(env, msg_addr)
|
CTOR.invoke(env, msg_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_toad(&self, env: &mut java::Env) -> toad_msg::alloc::Message {
|
pub fn to_toad(&self, env: &mut java::Env) -> Addrd<toad_msg::alloc::Message> {
|
||||||
toad_msg::alloc::Message { ty: self.ty(env),
|
let msg = toad_msg::alloc::Message { ty: self.ty(env),
|
||||||
ver: toad_msg::Version::default(),
|
ver: toad_msg::Version::default(),
|
||||||
code: self.code(env),
|
code: self.code(env),
|
||||||
id: self.id(env),
|
id: self.id(env),
|
||||||
@ -38,7 +40,10 @@ impl Message {
|
|||||||
.collect())
|
.collect())
|
||||||
})
|
})
|
||||||
.collect::<BTreeMap<toad_msg::OptNumber,
|
.collect::<BTreeMap<toad_msg::OptNumber,
|
||||||
Vec<toad_msg::OptValue<Vec<u8>>>>>() }
|
Vec<toad_msg::OptValue<Vec<u8>>>>>() };
|
||||||
|
Addrd(msg,
|
||||||
|
self.addr(env)
|
||||||
|
.expect("java should have made sure the address was present"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&self, env: &mut java::Env) {
|
pub fn close(&self, env: &mut java::Env) {
|
||||||
@ -46,6 +51,14 @@ impl Message {
|
|||||||
CLOSE.invoke(env, self)
|
CLOSE.invoke(env, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn addr(&self, env: &mut java::Env) -> Option<no_std_net::SocketAddr> {
|
||||||
|
static SOURCE: java::Method<Message, fn() -> java::util::Optional<InetSocketAddress>> =
|
||||||
|
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 {
|
pub fn ty(&self, env: &mut java::Env) -> toad_msg::Type {
|
||||||
static TYPE: java::Method<Message, fn() -> Type> = java::Method::new("type");
|
static TYPE: java::Method<Message, fn() -> Type> = java::Method::new("type");
|
||||||
TYPE.invoke(env, self).to_toad(env)
|
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 {
|
-> jobject {
|
||||||
let e = &mut env;
|
let e = &mut env;
|
||||||
let msg = unsafe {
|
let msg = unsafe {
|
||||||
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
|
Shared::deref::<Addrd<toad_msg::alloc::Message>>(addr).as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
Id::from_toad(e, msg.id).yield_to_java(e)
|
Id::from_toad(e, msg.data().id).yield_to_java(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@ -100,10 +113,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_token<'local>(mut env: java
|
|||||||
-> jobject {
|
-> jobject {
|
||||||
let e = &mut env;
|
let e = &mut env;
|
||||||
let msg = unsafe {
|
let msg = unsafe {
|
||||||
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
|
Shared::deref::<Addrd<toad_msg::alloc::Message>>(addr).as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
Token::from_toad(e, msg.token).yield_to_java(e)
|
Token::from_toad(e, msg.data().token).yield_to_java(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@ -112,10 +125,12 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_payload<'local>(mut env: ja
|
|||||||
addr: i64)
|
addr: i64)
|
||||||
-> jobject {
|
-> jobject {
|
||||||
let msg = unsafe {
|
let msg = unsafe {
|
||||||
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
|
Shared::deref::<Addrd<toad_msg::alloc::Message>>(addr).as_ref()
|
||||||
.unwrap()
|
.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]
|
#[no_mangle]
|
||||||
@ -124,10 +139,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_typ<'local>(mut e: java::En
|
|||||||
addr: i64)
|
addr: i64)
|
||||||
-> jobject {
|
-> jobject {
|
||||||
let msg = unsafe {
|
let msg = unsafe {
|
||||||
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
|
Shared::deref::<Addrd<toad_msg::alloc::Message>>(addr).as_ref()
|
||||||
.unwrap()
|
.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]
|
#[no_mangle]
|
||||||
@ -136,10 +151,23 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_code<'local>(mut e: java::E
|
|||||||
addr: i64)
|
addr: i64)
|
||||||
-> jobject {
|
-> jobject {
|
||||||
let msg = unsafe {
|
let msg = unsafe {
|
||||||
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
|
Shared::deref::<Addrd<toad_msg::alloc::Message>>(addr).as_ref()
|
||||||
.unwrap()
|
.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::<Addrd<toad_msg::alloc::Message>>(addr).as_ref()
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
InetSocketAddress::from_no_std(&mut e, msg.addr()).yield_to_java(&mut e)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@ -148,10 +176,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_opts<'local>(mut e: java::E
|
|||||||
addr: i64)
|
addr: i64)
|
||||||
-> jobject {
|
-> jobject {
|
||||||
let msg = unsafe {
|
let msg = unsafe {
|
||||||
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
|
Shared::deref::<Addrd<toad_msg::alloc::Message>>(addr).as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
let opts = &msg.opts;
|
let opts = &msg.data().opts;
|
||||||
|
|
||||||
let refs = opts.into_iter()
|
let refs = opts.into_iter()
|
||||||
.map(|(n, v)| Opt::new(&mut e, v as *const _ as i64, n.0.into()))
|
.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_path("foo/bar/baz").ok();
|
||||||
toad_msg.set_payload(Payload(r#"{"id": 123, "stuff": ["abc"]}"#.as_bytes().to_vec()));
|
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<toad_msg::alloc::Message> =
|
||||||
|
Box::into_raw(Box::new(Addrd(toad_msg, "127.0.0.1:1234".parse().unwrap())));
|
||||||
|
|
||||||
let msg = Message::new(e, ptr.addr() as i64);
|
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_path("foo/bar/baz").ok();
|
||||||
toad_msg.set_payload(Payload(r#"{"id": 123, "stuff": ["abc"]}"#.as_bytes().to_vec()));
|
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<toad_msg::alloc::Message> =
|
||||||
|
Box::into_raw(Box::new(Addrd(toad_msg, "127.0.0.1:1234".parse().unwrap())));
|
||||||
|
|
||||||
let msg = Message::new(e, ptr.addr() as i64);
|
let msg = Message::new(e, ptr.addr() as i64);
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
use jni::objects::JClass;
|
||||||
|
use jni::sys::jobject;
|
||||||
use tinyvec::ArrayVec;
|
use tinyvec::ArrayVec;
|
||||||
use toad_jni::java;
|
use toad_jni::java::{self, Object};
|
||||||
|
|
||||||
use crate::dev::toad::ffi;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use tinyvec::array_vec;
|
use tinyvec::array_vec;
|
||||||
|
@ -9,14 +9,12 @@ mod runtime {
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use toad::config::Config;
|
use toad::config::Config;
|
||||||
use toad::net::Addrd;
|
|
||||||
use toad::platform::{Effect, Platform};
|
use toad::platform::{Effect, Platform};
|
||||||
use toad::req::Req;
|
|
||||||
use toad::resp::Resp;
|
|
||||||
use toad::step::runtime::Runtime as DefaultSteps;
|
use toad::step::runtime::Runtime as DefaultSteps;
|
||||||
use toad_jni::java::io::IOException;
|
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::nio::channels::PeekableDatagramChannel;
|
||||||
|
use toad_jni::java::{self, Object};
|
||||||
use toad_msg::{OptNumber, OptValue};
|
use toad_msg::{OptNumber, OptValue};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@ -55,7 +53,10 @@ mod runtime {
|
|||||||
type Error = IOException;
|
type Error = IOException;
|
||||||
|
|
||||||
fn log(&self, level: log::Level, msg: toad::todo::String<1000>) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use toad::net::Addrd;
|
||||||
use toad_msg::alloc::Message;
|
use toad_msg::alloc::Message;
|
||||||
|
|
||||||
/// global [`RuntimeAllocator`] implementation
|
/// 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,
|
/// Pass ownership of a [`Message`] to the shared memory region,
|
||||||
/// yielding a stable pointer to this message.
|
/// yielding a stable pointer to this message.
|
||||||
unsafe fn alloc_message(m: Message) -> *mut Message;
|
unsafe fn alloc_message(m: Addrd<Message>) -> *mut Addrd<Message>;
|
||||||
|
|
||||||
/// Delete a message from the shared memory region.
|
/// Delete a message from the shared memory region.
|
||||||
unsafe fn dealloc_message(m: *mut Message);
|
unsafe fn dealloc_message(m: *mut Addrd<Message>);
|
||||||
|
|
||||||
/// Teardown
|
/// Teardown
|
||||||
unsafe fn dealloc();
|
unsafe fn dealloc();
|
||||||
@ -38,7 +39,7 @@ static mut MEM: Mem = Mem { runtime: None,
|
|||||||
|
|
||||||
struct Mem {
|
struct Mem {
|
||||||
runtime: Option<crate::Runtime>,
|
runtime: Option<crate::Runtime>,
|
||||||
messages: Vec<Message>,
|
messages: Vec<Addrd<Message>>,
|
||||||
|
|
||||||
/// Lock used by `alloc_message` and `dealloc_message` to ensure
|
/// Lock used by `alloc_message` and `dealloc_message` to ensure
|
||||||
/// they are run serially.
|
/// they are run serially.
|
||||||
@ -63,7 +64,7 @@ impl SharedMemoryRegion for GlobalStatic {
|
|||||||
MEM.runtime.as_mut().unwrap() as _
|
MEM.runtime.as_mut().unwrap() as _
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn alloc_message(m: Message) -> *mut Message {
|
unsafe fn alloc_message(m: Addrd<Message>) -> *mut Addrd<Message> {
|
||||||
let Mem { ref mut messages,
|
let Mem { ref mut messages,
|
||||||
ref mut messages_lock,
|
ref mut messages_lock,
|
||||||
.. } = &mut MEM;
|
.. } = &mut MEM;
|
||||||
@ -73,7 +74,7 @@ impl SharedMemoryRegion for GlobalStatic {
|
|||||||
&mut messages[len - 1] as _
|
&mut messages[len - 1] as _
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dealloc_message(m: *mut Message) {
|
unsafe fn dealloc_message(m: *mut Addrd<Message>) {
|
||||||
let Mem { messages,
|
let Mem { messages,
|
||||||
messages_lock,
|
messages_lock,
|
||||||
.. } = &mut MEM;
|
.. } = &mut MEM;
|
||||||
|
39
src/main/java/dev.toad/Async.java
Normal file
39
src/main/java/dev.toad/Async.java
Normal file
@ -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 <T> CompletableFuture<T> pollCompletable(
|
||||||
|
Supplier<Optional<T>> 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,36 @@
|
|||||||
package dev.toad;
|
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;
|
final Toad toad;
|
||||||
|
|
||||||
Client(Toad toad) {
|
Client(Toad toad) {
|
||||||
this.toad = toad;
|
this.toad = toad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Message> 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.toad;
|
package dev.toad;
|
||||||
|
|
||||||
import dev.toad.ffi.*;
|
import dev.toad.ffi.*;
|
||||||
|
import dev.toad.msg.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
@ -28,7 +29,7 @@ public final class Toad implements AutoCloseable {
|
|||||||
Toad.loadNativeLib();
|
Toad.loadNativeLib();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadNativeLib() {
|
public static void loadNativeLib() {
|
||||||
System.loadLibrary("toad_java_glue");
|
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 long init(DatagramChannel chan, Config o);
|
||||||
|
|
||||||
|
static native Optional<IdAndToken> sendMessage(
|
||||||
|
long ptr,
|
||||||
|
dev.toad.msg.owned.Message msg
|
||||||
|
);
|
||||||
|
|
||||||
static native Optional<dev.toad.msg.ref.Message> pollReq(long ptr);
|
static native Optional<dev.toad.msg.ref.Message> pollReq(long ptr);
|
||||||
|
|
||||||
static native Optional<dev.toad.msg.ref.Message> pollResp(
|
static native Optional<dev.toad.msg.ref.Message> pollResp(
|
||||||
@ -46,12 +52,16 @@ public final class Toad implements AutoCloseable {
|
|||||||
InetSocketAddress n
|
InetSocketAddress n
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public Optional<IdAndToken> sendMessage(Message msg) {
|
||||||
|
return Toad.sendMessage(this.ptr.addr(), msg.toOwned());
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<dev.toad.msg.ref.Message> pollReq() {
|
public Optional<dev.toad.msg.ref.Message> pollReq() {
|
||||||
return Toad.pollReq(this.ptr.addr());
|
return Toad.pollReq(this.ptr.addr());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<dev.toad.msg.ref.Message> pollResp(
|
public Optional<dev.toad.msg.ref.Message> pollResp(
|
||||||
dev.toad.msg.Token regarding,
|
Token regarding,
|
||||||
InetSocketAddress from
|
InetSocketAddress from
|
||||||
) {
|
) {
|
||||||
return Toad.pollResp(this.ptr.addr(), regarding, from);
|
return Toad.pollResp(this.ptr.addr(), regarding, from);
|
||||||
@ -76,6 +86,17 @@ public final class Toad implements AutoCloseable {
|
|||||||
this.ptr.release();
|
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 {
|
public interface BuilderRequiresSocket {
|
||||||
Toad.Builder port(short port);
|
Toad.Builder port(short port);
|
||||||
Toad.Builder address(InetSocketAddress addr);
|
Toad.Builder address(InetSocketAddress addr);
|
||||||
@ -91,10 +112,11 @@ public final class Toad implements AutoCloseable {
|
|||||||
|
|
||||||
Builder() {}
|
Builder() {}
|
||||||
|
|
||||||
public Toad build() throws IOException {
|
public Client buildClient() throws IOException {
|
||||||
if (!this.ioException.isEmpty()) {
|
if (this.ioException.isEmpty()) {
|
||||||
var cfg = new Config(this.concurrency, this.msg.build());
|
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 {
|
} else {
|
||||||
throw this.ioException.get();
|
throw this.ioException.get();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package dev.toad.ffi;
|
|||||||
|
|
||||||
public final class u16 {
|
public final class u16 {
|
||||||
|
|
||||||
|
public native byte[] toBytes();
|
||||||
|
|
||||||
public static final int MAX = (int) (Math.pow(2, 16) - 1);
|
public static final int MAX = (int) (Math.pow(2, 16) - 1);
|
||||||
private final int l;
|
private final int l;
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ package dev.toad.ffi;
|
|||||||
|
|
||||||
public final class u32 {
|
public final class u32 {
|
||||||
|
|
||||||
|
public native byte[] toBytes();
|
||||||
|
|
||||||
public static final long MAX = (long) (Math.pow(2, 32) - 1);
|
public static final long MAX = (long) (Math.pow(2, 32) - 1);
|
||||||
private final long l;
|
private final long l;
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import java.math.BigInteger;
|
|||||||
|
|
||||||
public final class u64 {
|
public final class u64 {
|
||||||
|
|
||||||
|
public native byte[] toBytes();
|
||||||
|
|
||||||
public static final BigInteger MAX = BigInteger.TWO
|
public static final BigInteger MAX = BigInteger.TWO
|
||||||
.pow(64)
|
.pow(64)
|
||||||
.subtract(BigInteger.ONE);
|
.subtract(BigInteger.ONE);
|
||||||
|
@ -2,14 +2,45 @@ package dev.toad.msg;
|
|||||||
|
|
||||||
import dev.toad.ffi.u8;
|
import dev.toad.ffi.u8;
|
||||||
|
|
||||||
public class Code {
|
public final class Code {
|
||||||
|
|
||||||
final u8 clazz;
|
final u8 clazz;
|
||||||
final u8 detail;
|
final u8 detail;
|
||||||
|
|
||||||
public Code(short clazz, short detail) {
|
public static final Code EMPTY = new Code(0, 0);
|
||||||
this.clazz = new u8(clazz);
|
|
||||||
this.detail = new u8(detail);
|
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() {
|
public short codeClass() {
|
||||||
|
@ -4,6 +4,8 @@ import dev.toad.ffi.u16;
|
|||||||
|
|
||||||
public final class Id {
|
public final class Id {
|
||||||
|
|
||||||
|
public static native Id defaultId();
|
||||||
|
|
||||||
final u16 id;
|
final u16 id;
|
||||||
|
|
||||||
public Id(int id) {
|
public Id(int id) {
|
||||||
|
@ -2,9 +2,10 @@ package dev.toad.msg;
|
|||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface Message {
|
public interface Message {
|
||||||
public InetSocketAddress source();
|
public Optional<InetSocketAddress> addr();
|
||||||
|
|
||||||
public Id id();
|
public Id id();
|
||||||
|
|
||||||
@ -19,4 +20,8 @@ public interface Message {
|
|||||||
public byte[] payloadBytes();
|
public byte[] payloadBytes();
|
||||||
|
|
||||||
public String payloadString();
|
public String payloadString();
|
||||||
|
|
||||||
|
public dev.toad.msg.owned.Message toOwned();
|
||||||
|
|
||||||
|
public byte[] toBytes();
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,6 @@ public interface OptionValue {
|
|||||||
public byte[] asBytes();
|
public byte[] asBytes();
|
||||||
|
|
||||||
public String asString();
|
public String asString();
|
||||||
|
|
||||||
|
public dev.toad.msg.owned.OptionValue toOwned();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package dev.toad.msg;
|
|||||||
|
|
||||||
public final class Token {
|
public final class Token {
|
||||||
|
|
||||||
|
public static native Token defaultToken();
|
||||||
|
|
||||||
final byte[] bytes;
|
final byte[] bytes;
|
||||||
|
|
||||||
public Token(byte[] bytes) {
|
public Token(byte[] bytes) {
|
||||||
|
103
src/main/java/dev.toad/msg/build/Message.java
Normal file
103
src/main/java/dev.toad/msg/build/Message.java
Normal file
@ -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<Long, ArrayList<dev.toad.msg.owned.OptionValue>> options =
|
||||||
|
new HashMap<>();
|
||||||
|
Optional<Id> id = Optional.empty();
|
||||||
|
Optional<Token> token = Optional.empty();
|
||||||
|
Optional<InetSocketAddress> addr = Optional.empty();
|
||||||
|
Optional<Code> code = Optional.empty();
|
||||||
|
Optional<Type> 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<OptionNeeds.Number, dev.toad.msg.owned.Option> 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<dev.toad.msg.OptionValue> 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<>()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
18
src/main/java/dev.toad/msg/build/MessageNeeds.java
Normal file
18
src/main/java/dev.toad/msg/build/MessageNeeds.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
36
src/main/java/dev.toad/msg/build/Option.java
Normal file
36
src/main/java/dev.toad/msg/build/Option.java
Normal file
@ -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<Long> number = Optional.empty();
|
||||||
|
ArrayList<byte[]> 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<>()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
8
src/main/java/dev.toad/msg/build/OptionNeeds.java
Normal file
8
src/main/java/dev.toad/msg/build/OptionNeeds.java
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package dev.toad.msg.build;
|
||||||
|
|
||||||
|
public final class OptionNeeds {
|
||||||
|
|
||||||
|
public interface Number {
|
||||||
|
Option number(long num);
|
||||||
|
}
|
||||||
|
}
|
29
src/main/java/dev.toad/msg/option/Accept.java
Normal file
29
src/main/java/dev.toad/msg/option/Accept.java
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
45
src/main/java/dev.toad/msg/option/ContentFormat.java
Normal file
45
src/main/java/dev.toad/msg/option/ContentFormat.java
Normal file
@ -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<OptionValue> values() {
|
||||||
|
var list = new ArrayList<OptionValue>();
|
||||||
|
list.add(new dev.toad.msg.owned.OptionValue(this.value.toBytes()));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
@ -5,28 +5,31 @@ import java.net.InetSocketAddress;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Message implements dev.toad.msg.Message {
|
public class Message implements dev.toad.msg.Message {
|
||||||
|
|
||||||
final InetSocketAddress source;
|
public native byte[] toBytes();
|
||||||
|
|
||||||
|
final Optional<InetSocketAddress> addr;
|
||||||
final Id id;
|
final Id id;
|
||||||
final Token token;
|
final Token token;
|
||||||
final byte[] payload;
|
final byte[] payload;
|
||||||
final Code code;
|
final Code code;
|
||||||
final Type type;
|
final Type type;
|
||||||
final List<dev.toad.msg.Option> opts;
|
final ArrayList<dev.toad.msg.owned.Option> opts;
|
||||||
|
|
||||||
public Message(
|
public Message(
|
||||||
InetSocketAddress source,
|
Optional<InetSocketAddress> addr,
|
||||||
Type type,
|
Type type,
|
||||||
Code code,
|
Code code,
|
||||||
Id id,
|
Id id,
|
||||||
Token token,
|
Token token,
|
||||||
byte[] payload,
|
byte[] payload,
|
||||||
List<dev.toad.msg.Option> opts
|
ArrayList<dev.toad.msg.owned.Option> opts
|
||||||
) {
|
) {
|
||||||
this.source = source;
|
this.addr = addr;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
@ -37,7 +40,7 @@ public class Message implements dev.toad.msg.Message {
|
|||||||
|
|
||||||
public Message(dev.toad.msg.ref.Message ref) {
|
public Message(dev.toad.msg.ref.Message ref) {
|
||||||
this(
|
this(
|
||||||
ref.source(),
|
ref.addr(),
|
||||||
ref.type(),
|
ref.type(),
|
||||||
ref.code(),
|
ref.code(),
|
||||||
ref.id(),
|
ref.id(),
|
||||||
@ -46,13 +49,17 @@ public class Message implements dev.toad.msg.Message {
|
|||||||
Arrays
|
Arrays
|
||||||
.asList(ref.optionRefs())
|
.asList(ref.optionRefs())
|
||||||
.stream()
|
.stream()
|
||||||
.map(dev.toad.msg.ref.Option::clone)
|
.map(dev.toad.msg.ref.Option::toOwned)
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toCollection(() -> new ArrayList<>()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress source() {
|
public Message toOwned() {
|
||||||
return this.source;
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<InetSocketAddress> addr() {
|
||||||
|
return this.addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Id id() {
|
public Id id() {
|
||||||
@ -72,7 +79,7 @@ public class Message implements dev.toad.msg.Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<dev.toad.msg.Option> options() {
|
public List<dev.toad.msg.Option> options() {
|
||||||
return this.opts;
|
return List.copyOf(this.opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] payloadBytes() {
|
public byte[] payloadBytes() {
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
package dev.toad.msg.owned;
|
package dev.toad.msg.owned;
|
||||||
|
|
||||||
|
import dev.toad.ffi.u32;
|
||||||
import dev.toad.msg.*;
|
import dev.toad.msg.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Option implements dev.toad.msg.Option {
|
public class Option implements dev.toad.msg.Option {
|
||||||
|
|
||||||
final long number;
|
final u32 number;
|
||||||
final List<dev.toad.msg.OptionValue> values;
|
final ArrayList<dev.toad.msg.owned.OptionValue> values;
|
||||||
|
|
||||||
public Option(long number, List<dev.toad.msg.OptionValue> values) {
|
public Option(long number, ArrayList<dev.toad.msg.owned.OptionValue> values) {
|
||||||
this.number = number;
|
this.number = new u32(number);
|
||||||
this.values = values;
|
this.values = values;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,16 +23,16 @@ public class Option implements dev.toad.msg.Option {
|
|||||||
Arrays
|
Arrays
|
||||||
.asList(ref.valueRefs())
|
.asList(ref.valueRefs())
|
||||||
.stream()
|
.stream()
|
||||||
.map(dev.toad.msg.ref.OptionValue::clone)
|
.map(dev.toad.msg.ref.OptionValue::toOwned)
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toCollection(() -> new ArrayList<>()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long number() {
|
public long number() {
|
||||||
return this.number;
|
return this.number.longValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<dev.toad.msg.OptionValue> values() {
|
public List<dev.toad.msg.OptionValue> values() {
|
||||||
return this.values;
|
return List.copyOf(this.values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,4 +19,8 @@ public class OptionValue implements dev.toad.msg.OptionValue {
|
|||||||
public String asString() {
|
public String asString() {
|
||||||
return new String(this.asBytes());
|
return new String(this.asBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dev.toad.msg.owned.OptionValue toOwned() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ public final class Message implements dev.toad.msg.Message, AutoCloseable {
|
|||||||
|
|
||||||
Ptr ptr;
|
Ptr ptr;
|
||||||
|
|
||||||
Optional<InetSocketAddress> source = Optional.empty();
|
Optional<InetSocketAddress> addr = Optional.empty();
|
||||||
|
|
||||||
static native InetSocketAddress source(long addr);
|
static native InetSocketAddress addr(long addr);
|
||||||
|
|
||||||
static native Id id(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 dev.toad.msg.ref.Option[] opts(long addr);
|
||||||
|
|
||||||
|
static native byte[] toBytes(long addr);
|
||||||
|
|
||||||
Message(long addr) {
|
Message(long addr) {
|
||||||
this.ptr = Ptr.register(this.getClass(), 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);
|
return new dev.toad.msg.owned.Message(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress source() {
|
public Optional<InetSocketAddress> addr() {
|
||||||
if (this.source.isEmpty()) {
|
if (this.addr.isEmpty()) {
|
||||||
this.source = Optional.of(this.source(this.ptr.addr()));
|
this.addr = Optional.of(this.addr(this.ptr.addr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.source.get();
|
return this.addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Id id() {
|
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()));
|
return new String(this.payload(this.ptr.addr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] toBytes() {
|
||||||
|
return this.toBytes(this.ptr.addr());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
this.ptr.release();
|
this.ptr.release();
|
||||||
|
@ -29,7 +29,7 @@ public class Option implements dev.toad.msg.Option, AutoCloseable {
|
|||||||
return Arrays.asList(this.values(this.ptr.addr()));
|
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);
|
return new dev.toad.msg.owned.Option(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ public final class OptionValue
|
|||||||
return new String(this.bytes(this.ptr.addr()));
|
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);
|
return new dev.toad.msg.owned.OptionValue(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,6 @@ public class Mock {
|
|||||||
return ent.getKey();
|
return ent.getKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
src/test/scala/Async.scala
Normal file
49
src/test/scala/Async.scala
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,60 @@
|
|||||||
import dev.toad.*;
|
import dev.toad.*
|
||||||
import mock.java.nio.channels.Mock;
|
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 {
|
class E2E extends munit.FunSuite {
|
||||||
test("foo") {
|
test("minimal client and server") {
|
||||||
|
Toad.loadNativeLib()
|
||||||
|
|
||||||
val mock = Mock.Channel()
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,10 @@ import sys.process._
|
|||||||
|
|
||||||
class Glue extends munit.FunSuite {
|
class Glue extends munit.FunSuite {
|
||||||
test("cargo test") {
|
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"
|
||||||
|
).!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user