chore: rename toad-java-glue-rs -> glue, WIP ffi package with uint types

This commit is contained in:
Orion Kindel 2023-04-05 09:16:32 -07:00
parent 4c981c9c9a
commit 42c30b84dd
Signed by untrusted user who does not match committer: orion
GPG Key ID: 6D4165AE4C928719
27 changed files with 327 additions and 136 deletions

2
.gitignore vendored
View File

@ -1,5 +1,3 @@
toad-jni.so
# macOS
.DS_Store

View File

@ -5,7 +5,7 @@ import sbt.nio.file.FileTreeView
Global / onChangedBuildSource := ReloadOnSourceChanges
val scala3Version = "3.2.2"
val cargoBuild = taskKey[Unit]("cd ./toad-java-glue-rs; cargo build")
val cargoBuild = taskKey[Unit]("cd ./glue; cargo build")
val ejectHeaders = taskKey[Unit]("Generate C headers for FFI")
val fullBuild = taskKey[Unit]("cargoBuild > ejectHeaders")
val glob = settingKey[Map[String, Glob]]("globs")
@ -14,7 +14,7 @@ val path = settingKey[Map[String, String]]("paths")
fork := true
javaOptions += "--enable-preview"
javacOptions ++= Seq("--enable-preview", "--release", "20", "-Xlint:preview")
javacOptions ++= Seq("--enable-preview", "--release", "20")
lazy val root = project
.in(file("."))
@ -25,11 +25,11 @@ lazy val root = project
libraryDependencies += "org.scalameta" %% "munit" % "0.7.29" % Test,
glob := Map(
"java.sources" -> baseDirectory.value.toGlob / "src" / "main" / "java" / ** / "*.java",
"glue.sources" -> baseDirectory.value.toGlob / "toad-java-glue-rs" / "src" / ** / "*.rs"
"glue.sources" -> baseDirectory.value.toGlob / "glue" / "src" / ** / "*.rs"
),
path := Map(
"glue.base" -> (baseDirectory.value / "toad-java-glue-rs").toString,
"glue.target" -> (baseDirectory.value / "toad-java-glue-rs" / "target" / "debug").toString,
"glue.base" -> (baseDirectory.value / "glue").toString,
"glue.target" -> (baseDirectory.value / "target" / "glue" / "debug").toString,
"java.classTarget" -> (baseDirectory.value / "target" / "scala-3.2.2" / "classes").toString
),
ejectHeaders := {
@ -52,7 +52,7 @@ lazy val root = project
},
cargoBuild := {
val cmd =
Seq("sh", "-c", "cd toad-java-glue-rs; cargo rustc -- -Awarnings")
Seq("sh", "-c", "cd glue; cargo rustc -- -Awarnings")
println(cmd !!)
},
fullBuild := {

2
glue/.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[build]
target-dir = "../target/native"

View File

@ -505,6 +505,8 @@ checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
[[package]]
name = "toad"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e3e3216e542ea3b2da0d2200e498b016907b68acb288b47a487cb9fd681b42"
dependencies = [
"embedded-time",
"log",
@ -576,7 +578,9 @@ dependencies = [
[[package]]
name = "toad-jni"
version = "0.1.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8211cccf8f1dce2768a85b6f42b1d232542ecb616584bc9915da1cfc50c3d3d1"
dependencies = [
"jni",
"toad-array 0.5.0",

View File

@ -9,7 +9,7 @@ crate_type = ["cdylib"]
[dependencies]
jni = "*"
toad = {path = "../../toad/toad"}
toad-jni = {path = "../../toad/toad-jni"}
toad-msg = "*"
toad = "0.17.2"
toad-jni = "0.4.0"
toad-msg = "0.18.1"
tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]}

View File

@ -18,6 +18,7 @@ macro_rules! package {
(ext $start:ident.$($thing:ident).+) => {concat!(stringify!($start), $("/", stringify!($thing)),+)};
}
pub mod uint;
pub mod message_code;
pub mod message_opt_ref;
pub mod message_opt_value_ref;
@ -39,7 +40,10 @@ pub unsafe extern "system" fn Java_dev_toad_Runtime_init<'local>(mut env: JNIEnv
#[cfg(test)]
mod tests {
use jni::{InitArgsBuilder, JavaVM};
use toad::retry::Strategy;
use toad::time::Millis;
use crate::retry_strategy::RetryStrategy;
use crate::runtime_config::RuntimeConfig;
#[test]
@ -64,5 +68,13 @@ mod tests {
let r = RuntimeConfig::new(e);
assert_eq!(r.to_toad(e), Default::default());
let r = Strategy::Exponential { init_min: Millis::new(0),
init_max: Millis::new(100) };
assert_eq!(RetryStrategy::from_toad(e, r).to_toad(e), r);
let r = Strategy::Delay { min: Millis::new(0),
max: Millis::new(100) };
assert_eq!(RetryStrategy::from_toad(e, r).to_toad(e), r);
}
}

View File

@ -0,0 +1,87 @@
use jni::objects::{GlobalRef, JObject};
use jni::JNIEnv;
use toad::retry::Strategy;
use toad::time::Millis;
use toad_jni::cls::java;
use toad_jni::convert::Object;
use toad_jni::Sig;
pub struct RetryStrategy(GlobalRef);
impl RetryStrategy {
pub const PATH: &'static str = package!(dev.toad.RetryStrategy);
pub const EXPONENTIAL: &'static str = package!(dev.toad.RetryStrategyExponential);
pub const EXPONENTIAL_CTOR: Sig = Sig::new().arg(Sig::class(java::time::Duration::PATH))
.arg(Sig::class(java::time::Duration::PATH))
.returning(Sig::VOID);
pub const DELAY: &'static str = package!(dev.toad.RetryStrategyDelay);
pub const DELAY_CTOR: Sig = Sig::new().arg(Sig::class(java::time::Duration::PATH))
.arg(Sig::class(java::time::Duration::PATH))
.returning(Sig::VOID);
pub fn exp<'a>(&self, e: &mut JNIEnv<'a>) -> Self {
let o = e.new_object(Self::PATH, Sig::new().returning(Sig::VOID), &[])
.unwrap();
let g = e.new_global_ref(o).unwrap();
Self(g)
}
pub fn millis_field<'a>(&self, e: &mut JNIEnv<'a>, key: &str) -> Millis {
let o = e.get_field(&self.0, key, Sig::class(java::time::Duration::PATH))
.unwrap()
.l()
.unwrap();
let g = e.new_global_ref(o).unwrap();
let d = java::time::Duration::from_java(g);
Millis::new(d.to_millis(e) as u64)
}
pub fn to_toad<'a>(self, e: &mut JNIEnv<'a>) -> Strategy {
if e.is_instance_of(&self.0, Self::EXPONENTIAL).unwrap() {
Strategy::Exponential { init_min: self.millis_field(e, "initMin"),
init_max: self.millis_field(e, "initMax") }
} else {
Strategy::Delay { min: self.millis_field(e, "min"),
max: self.millis_field(e, "max") }
}
}
pub fn from_toad<'a>(e: &mut JNIEnv<'a>, s: Strategy) -> Self {
let g = match s {
| Strategy::Delay { min, max } => {
let (min, max) = (java::time::Duration::of_millis(e, min.0 as i64),
java::time::Duration::of_millis(e, max.0 as i64));
let (min, max) = (min.to_java(), max.to_java());
let o = e.new_object(Self::DELAY,
Self::DELAY_CTOR,
&[min.as_obj().into(), max.as_obj().into()])
.unwrap();
e.new_global_ref(o).unwrap()
},
| Strategy::Exponential { init_min, init_max } => {
let (init_min, init_max) = (java::time::Duration::of_millis(e, init_min.0 as i64),
java::time::Duration::of_millis(e, init_max.0 as i64));
let (init_min, init_max) = (init_min.to_java(), init_max.to_java());
let o = e.new_object(Self::EXPONENTIAL,
Self::EXPONENTIAL_CTOR,
&[init_min.as_obj().into(), init_max.as_obj().into()])
.unwrap();
e.new_global_ref(o).unwrap()
},
};
Self(g)
}
}
impl Object for RetryStrategy {
fn from_java(jobj: GlobalRef) -> Self {
Self(jobj)
}
fn to_java(self) -> GlobalRef {
self.0
}
}

58
glue/src/uint.rs Normal file
View File

@ -0,0 +1,58 @@
use jni::{JNIEnv, objects::{JObject, JByteArray}};
use toad_jni::Sig;
pub mod path {
pub const U64: &'static str = package!(dev.toad.ffi.u64);
pub const U32: &'static str = package!(dev.toad.ffi.u32);
pub const U16: &'static str = package!(dev.toad.ffi.u16);
pub const U8: &'static str = package!(dev.toad.ffi.u8);
}
pub fn u64<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u64 {
let bi = e.call_method(o, "bigintValue", Sig::new().returning(Sig::class("java.math.BigInteger")), &[]).unwrap().l().unwrap();
let barr: JByteArray<'a> = e.call_method(bi, "toByteArray", Sig::new().returning(Sig::array_of(Sig::BYTE)), &[]).unwrap().l().unwrap().try_into().unwrap();
let mut bytes = [0i8; 8];
// BigInteger is a growable two's complement integer
//
// the "growable" comes from its backing structure being a simple
// int array `int[]`, where bytes are added as needed to afford capacity.
//
// two's-complement means the most significant bit (the first bit of the first byte)
// indicates the sign of the integer, where 0 is positive and 1 is negative.
//
// The rest of the bits are unchanged, meaning the range is from `-(2^(n - 1))`
// to `2^(n - 1) - 1`.
//
// For example, a two's complement i8 would be able to represent `-128` (`0b11111111`),
// `0` (`0b00000000`) to `127` (`0b01111111`). for positive integers, the representation is
// the same as unsigned integers, meaning we simply need to make sure we don't accidentally
// interpret the first bit as part of the integer.
//
// Here we assume whoever is responsible for BigInteger made sure that it's positive,
// so converting the big-endian two's complement int
e.get_byte_array_region(&barr, 0, &mut bytes).unwrap();
// https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/math/BigInteger.html#toByteArray()
//
// BigInt.toByteArray actually returns the raw byte representation of the integer, NOT
// two's complement `byte`s as the type signature would lead you to believe.
//
// To interpret these bytes as i8 is incorrect.
let bytes = bytes.map(|i| i8::to_be_bytes(i)[0]);
u64::from_be_bytes(bytes)
}
pub fn u32<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u32 {
e.call_method(o, "longValue", Sig::new().returning(Sig::LONG), &[]).unwrap().j().unwrap() as u32
}
pub fn u16<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u16 {
e.call_method(o, "intValue", Sig::new().returning(Sig::INT), &[]).unwrap().i().unwrap() as u16
}
pub fn u8<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u8 {
e.call_method(o, "shortValue", Sig::new().returning(Sig::SHORT), &[]).unwrap().s().unwrap() as u8
}

View File

@ -1,53 +1,3 @@
package dev.toad;
import java.time.Duration;
import java.util.Optional;
public abstract sealed class RetryStrategy {
@Override
public boolean equals(Object other) {
return switch (this) {
case Exponential self -> switch (other) {
case Exponential e -> e.initMin == self.initMin &&
e.initMax == self.initMax;
default -> false;
};
case Linear self -> switch (other) {
case Linear e -> e.min == self.min && e.max == self.max;
default -> false;
};
default -> false;
};
}
public final class Exponential extends RetryStrategy {
public final Duration initMin;
public final Duration initMax;
private static native Exponential fromRust(byte[] mem);
private native byte[] toRust();
public Exponential(Duration initMin, Duration initMax) {
this.initMin = initMin;
this.initMax = initMax;
}
}
public final class Linear extends RetryStrategy {
public final Duration min;
public final Duration max;
private static native Linear fromRust(byte[] mem);
private native byte[] toRust();
public Linear(Duration min, Duration max) {
this.min = min;
this.max = max;
}
}
}
public abstract sealed class RetryStrategy permits RetryStrategyDelay, RetryStrategyExponential { }

View File

@ -0,0 +1,27 @@
package dev.toad;
import java.time.Duration;
import java.util.Optional;
public final class RetryStrategyDelay extends RetryStrategy {
public final Duration min;
public final Duration max;
private static native RetryStrategyDelay fromRust(byte[] mem);
private native byte[] toRust();
public RetryStrategyDelay(Duration min, Duration max) {
this.min = min;
this.max = max;
}
@Override
public boolean equals(Object other) {
return switch (other) {
case RetryStrategyDelay e -> e.min == this.min && e.max == this.max;
default -> false;
};
}
}

View File

@ -0,0 +1,27 @@
package dev.toad;
import java.time.Duration;
import java.util.Optional;
public final class RetryStrategyExponential extends RetryStrategy {
public final Duration initMin;
public final Duration initMax;
private static native RetryStrategyExponential fromRust(byte[] mem);
private native byte[] toRust();
public RetryStrategyExponential(Duration initMin, Duration initMax) {
this.initMin = initMin;
this.initMax = initMax;
}
@Override
public boolean equals(Object other) {
return switch (other) {
case RetryStrategyExponential e -> e.initMin == this.initMin && e.initMax == this.initMax;
default -> false;
};
}
}

View File

@ -1,5 +1,6 @@
package dev.toad;
import dev.toad.ffi.*;
import java.time.Duration;
import java.util.Optional;
import java.util.function.Function;
@ -44,13 +45,13 @@ public final class RuntimeOptions implements Cloneable {
public final class Net implements Cloneable {
private short port;
private short concurrency;
private u16 port;
private u8 concurrency;
private Msg msg;
public Net() {
this.port = 5683;
this.concurrency = 1;
this.port = new u16(5683);
this.concurrency = new u8((short)1);
this.msg = new Msg();
}
@ -77,12 +78,12 @@ public final class RuntimeOptions implements Cloneable {
return self;
}
public short port() {
return this.port;
public int port() {
return this.port.intValue();
}
public short concurrency() {
return this.concurrency;
return this.concurrency.shortValue();
}
public Msg msg() {
@ -91,14 +92,14 @@ public final class RuntimeOptions implements Cloneable {
public Net withPort(short port) {
return this.with(self -> {
self.port = port;
self.port = new u16(port);
return self;
});
}
public Net withConcurrency(short conc) {
return this.with(self -> {
self.concurrency = conc;
self.concurrency = new u8(conc);
return self;
});
}
@ -113,8 +114,8 @@ public final class RuntimeOptions implements Cloneable {
public final class Msg implements Cloneable {
private Optional<Integer> tokenSeed = Optional.empty();
private Optional<Integer> probingRateBytesPerSecond = Optional.empty();
private Optional<u16> tokenSeed = Optional.empty();
private Optional<u16> probingRateBytesPerSecond = Optional.empty();
private Optional<Duration> multicastResponseLeisure = Optional.empty();
private Con con;
private Non non;
@ -152,11 +153,11 @@ public final class RuntimeOptions implements Cloneable {
}
public Optional<Integer> tokenSeed() {
return this.tokenSeed;
return this.tokenSeed.map(u16 -> u16.intValue());
}
public Optional<Integer> probingRateBytesPerSecond() {
return this.probingRateBytesPerSecond;
return this.probingRateBytesPerSecond.map(u16 -> u16.intValue());
}
public Optional<Duration> multicastResponseLeisure() {
@ -173,14 +174,14 @@ public final class RuntimeOptions implements Cloneable {
public Msg withTokenSeed(int tokenSeed) {
return this.with(self -> {
self.tokenSeed = Optional.of(tokenSeed);
self.tokenSeed = Optional.of(new u16(tokenSeed));
return self;
});
}
public Msg withProbingRateBytesBerSecond(int bps) {
return this.with(m -> {
m.probingRateBytesPerSecond = Optional.of(bps);
m.probingRateBytesPerSecond = Optional.of(new u16(bps));
return m;
});
}
@ -210,7 +211,7 @@ public final class RuntimeOptions implements Cloneable {
private Optional<RetryStrategy> ackedRetryStrategy = Optional.empty();
private Optional<RetryStrategy> unackedRetryStrategy = Optional.empty();
private Optional<Integer> maxAttempts = Optional.empty();
private Optional<u16> maxAttempts = Optional.empty();
public Con() {}
@ -237,7 +238,7 @@ public final class RuntimeOptions implements Cloneable {
}
public Optional<Integer> maxAttempts() {
return this.maxAttempts;
return this.maxAttempts.map(u16 -> u16.intValue());
}
public Con withAckedRetryStrategy(RetryStrategy r) {
@ -256,7 +257,7 @@ public final class RuntimeOptions implements Cloneable {
public Con withMaxAttempts(int a) {
return this.with(s -> {
s.maxAttempts = Optional.of(a);
s.maxAttempts = Optional.of(new u16(a));
return s;
});
}
@ -270,7 +271,7 @@ public final class RuntimeOptions implements Cloneable {
public final class Non implements Cloneable {
private Optional<RetryStrategy> retryStrategy = Optional.empty();
private Optional<Integer> maxAttempts = Optional.empty();
private Optional<u16> maxAttempts = Optional.empty();
public Non() {}
@ -292,7 +293,7 @@ public final class RuntimeOptions implements Cloneable {
}
public Optional<Integer> maxAttempts() {
return this.maxAttempts;
return this.maxAttempts.map(u16 -> u16.intValue());
}
public Non withRetryStrategy(RetryStrategy r) {
@ -304,7 +305,7 @@ public final class RuntimeOptions implements Cloneable {
public Non withMaxAttempts(int a) {
return this.with(s -> {
s.maxAttempts = Optional.of(a);
s.maxAttempts = Optional.of(new u16(a));
return s;
});
}

View File

@ -0,0 +1,15 @@
package dev.toad.ffi;
public final class u16 {
public static final int MAX = (int)(Math.pow(2, 16) - 1);
private final int l;
public u16(int l) {
uint.assertWithinRange(this.MAX, l);
this.l = l;
}
public int intValue() {
return this.l;
}
}

View File

@ -0,0 +1,15 @@
package dev.toad.ffi;
public final class u32 {
public static final long MAX = (long)(Math.pow(2, 32) - 1);
private final long l;
public u32(long l) {
uint.assertWithinRange(this.MAX, l);
this.l = l;
}
public long longValue() {
return this.l;
}
}

View File

@ -0,0 +1,17 @@
package dev.toad.ffi;
public final class u64 {
public static final double MAX = Math.pow(2, 64) - 1;
private final double l;
public u64(double l) {
uint.assertWithinRange(this.MAX, l);
uint.assertNatural(l);
this.l = l;
}
public double doubleValue() {
return this.l;
}
}

View File

@ -0,0 +1,15 @@
package dev.toad.ffi;
public final class u8 {
public static final short MAX = (short)(Math.pow(2, 8) - 1);
private final short l;
public u8(short l) {
uint.assertWithinRange(this.MAX, l);
this.l = l;
}
public short shortValue() {
return this.l;
}
}

View File

@ -0,0 +1,15 @@
package dev.toad.ffi;
public class uint {
public static void assertWithinRange(double max, double n) {
if (n < 0 || n > max) {
throw new IllegalArgumentException(String.format("% must be between 0 and %", n, max));
}
}
public static void assertNatural(double n) {
if (n % 1 > 0.0) {
throw new IllegalArgumentException(String.format("% must be a whole integer", n));
}
}
}

View File

@ -1,52 +0,0 @@
use jni::objects::{GlobalRef, JObject};
use jni::JNIEnv;
use toad::retry::Strategy;
use toad::time::Millis;
use toad_jni::cls::java;
use toad_jni::convert::Object;
use toad_jni::Sig;
pub struct RetryStrategy(GlobalRef);
impl RetryStrategy {
pub const PATH: &'static str = package!(dev.toad.RetryStrategy);
pub const EXPONENTIAL: &'static str = package!(dev.toad.RetryStrategy.Exponential);
pub const LINEAR: &'static str = package!(dev.toad.RetryStrategy.Linear);
pub fn exp<'a>(&self, e: &mut JNIEnv<'a>) -> Self {
let o = e.new_object(Self::PATH, Sig::new().returning(Sig::VOID), &[])
.unwrap();
let g = e.new_global_ref(o).unwrap();
Self(g)
}
pub fn millis_field<'a>(&self, e: &mut JNIEnv<'a>, key: &str) -> Millis {
let o = e.get_field(&self.0, "initMax", Sig::class(java::time::Duration::PATH))
.unwrap()
.l()
.unwrap();
let g = e.new_global_ref(o).unwrap();
let d = java::time::Duration::from_java(g);
Millis::new(d.to_millis(e) as u64)
}
pub fn to_toad<'a>(self, e: &mut JNIEnv<'a>) -> Strategy {
if e.is_instance_of(&self.0, Self::EXPONENTIAL).unwrap() {
Strategy::Exponential { init_min: self.millis_field(e, "initMin"),
init_max: self.millis_field(e, "initMax") }
} else {
Strategy::Delay { min: self.millis_field(e, "min"),
max: self.millis_field(e, "max") }
}
}
}
impl Object for RetryStrategy {
fn from_java(jobj: GlobalRef) -> Self {
Self(jobj)
}
fn to_java(self) -> GlobalRef {
self.0
}
}