use std::cell::{Cell, RefCell};
use jni::JNIEnv;
use jni::objects::{JClass, JObject};
use jni::sys::{jbyteArray, jint, jlong, jobject};
use std::os::raw::c_void;
use yog_abi::{YogReply, YogStatus, status};
use super::ROUTER;
use crate::jni::call::jcall;
pub(crate) const BYTES: usize = 1 << 16;
pub(crate) const HEADER: usize = 16;
pub(crate) const TOP: usize = 0;
pub(crate) const SPILLED: u32 = u32::MAX;
const STATUS_SHIFT: u32 = 32;
const TOP_BYTES: usize = 4;
thread_local! {
static ARENA: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
static SPILL: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
fn base() -> *mut u8 {
ARENA.with(|a| {
if a.get().is_null() {
a.set(Box::leak(vec![0u8; BYTES].into_boxed_slice()).as_mut_ptr());
}
a.get()
})
}
fn top(base: *mut u8) -> usize {
let mut b = [0u8; TOP_BYTES];
unsafe { std::ptr::copy_nonoverlapping(base.add(TOP), b.as_mut_ptr(), TOP_BYTES) };
u32::from_le_bytes(b) as usize
}
fn set_top(base: *mut u8, top: usize) {
let b = (top as u32).to_le_bytes();
unsafe { std::ptr::copy_nonoverlapping(b.as_ptr(), base.add(TOP), TOP_BYTES) };
}
fn fits(at: usize, len: usize) -> bool {
at.checked_add(len).is_some_and(|end| HEADER + end <= BYTES)
}
fn slice<'a>(base: *mut u8, at: usize, len: usize) -> Option<&'a [u8]> {
fits(at, len).then(|| unsafe { std::slice::from_raw_parts(base.add(HEADER + at), len) })
}
fn write(base: *mut u8, at: usize, bytes: &[u8]) {
unsafe { std::ptr::copy(bytes.as_ptr(), base.add(HEADER + at), bytes.len()) };
}
fn is_top_frame(base: *mut u8, top: usize, payload: &[u8]) -> bool {
top >= payload.len()
&& std::ptr::eq(payload.as_ptr(), unsafe {
base.add(HEADER + top - payload.len())
})
}
#[cfg(test)]
pub(crate) fn frames_top() -> usize {
top(base())
}
pub(super) fn run_java(
env: &mut JNIEnv,
handler: i32,
payload: &[u8],
reply: &YogReply,
) -> Option<YogStatus> {
let base = base();
let top0 = top(base);
let at = if is_top_frame(base, top0, payload) {
top0 - payload.len()
} else {
if !fits(top0, payload.len()) {
return None;
}
write(base, top0, payload);
top0
};
set_top(base, at + payload.len());
let answer: Option<i64> = unsafe {
jcall!(
env,
"dev/yog/YogBus",
"handleAt",
[I(handler), I(at as i32), I(payload.len() as i32)]
)
};
set_top(base, top0);
let Some(answer) = answer else {
return Some(status::FAILED);
};
let (st, len) = unpack(answer);
if len == SPILLED {
let spilled: Option<Vec<u8>> = unsafe { jcall!(env, "dev/yog/YogBus", "takeReply", []) };
unsafe { YogReply::send(reply, &spilled.unwrap_or_default()) };
} else if let Some(bytes) = slice(base, at + payload.len(), len as usize) {
unsafe { YogReply::send(reply, bytes) };
}
Some(st)
}
fn pack(st: YogStatus, len: u32) -> jlong {
((i64::from(st)) << STATUS_SHIFT) | i64::from(len)
}
fn unpack(answer: i64) -> (YogStatus, u32) {
((answer >> STATUS_SHIFT) as YogStatus, answer as u32)
}
struct ArenaSink {
at: usize,
len: usize,
spilled: bool,
}
unsafe extern "C" fn write_sink(sink: *mut c_void, data: *const u8, len: usize) {
let (Some(s), false) = (unsafe { sink.cast::<ArenaSink>().as_mut() }, data.is_null()) else {
return;
};
let bytes = unsafe { std::slice::from_raw_parts(data, len) };
if !s.spilled && fits(s.at + s.len, bytes.len()) {
write(base(), s.at + s.len, bytes);
s.len += bytes.len();
return;
}
SPILL.with(|spill| {
let mut spill = spill.borrow_mut();
if !s.spilled {
spill.clear();
spill.extend_from_slice(slice(base(), s.at, s.len).unwrap_or_default());
s.spilled = true;
}
spill.extend_from_slice(bytes);
});
}
fn from_java(at: jint, len: jint, send: impl FnOnce(&[u8], &YogReply) -> YogStatus) -> jlong {
let (Ok(at), Ok(len)) = (usize::try_from(at), usize::try_from(len)) else {
return pack(status::BAD_PAYLOAD, 0);
};
let base = base();
let Some(payload) = slice(base, at, len) else {
return pack(status::BAD_PAYLOAD, 0);
};
let mut sink = ArenaSink {
at: at + len,
len: 0,
spilled: false,
};
let reply = YogReply {
sink: (&mut sink as *mut ArenaSink).cast(),
write: write_sink,
};
let st = send(payload, &reply);
pack(
st,
if sink.spilled {
SPILLED
} else {
sink.len as u32
},
)
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_YogBus_nativeArena<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jobject {
crate::jni::util::guarded("nativeArena", std::ptr::null_mut(), || {
let buffer = unsafe { env.new_direct_byte_buffer(base(), BYTES) };
buffer.map_or(std::ptr::null_mut(), |b| JObject::from(b).into_raw())
})
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_YogBus_nativeCallAt<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
to: jint,
at: jint,
len: jint,
) -> jlong {
crate::jni::util::guarded("nativeCallAt", 0, || {
from_java(at, len, |payload, reply| {
ROUTER.call(to as u32, payload, reply, super::java::run)
})
})
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_YogBus_nativeEmitAt<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
event: jint,
at: jint,
len: jint,
) -> jlong {
crate::jni::util::guarded("nativeEmitAt", 0, || {
from_java(at, len, |payload, reply| {
ROUTER.emit(event as u32, payload, reply, super::java::run)
})
})
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_YogBus_nativeTakeReply<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jbyteArray {
crate::jni::util::guarded("nativeTakeReply", std::ptr::null_mut(), || {
let reply = SPILL.with(|s| std::mem::take(&mut *s.borrow_mut()));
env.byte_array_from_slice(&reply)
.map_or(std::ptr::null_mut(), |a| a.into_raw())
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn answers_pack_their_status_and_length() {
assert_eq!(
unpack(pack(status::CANCELLED, 1234)),
(status::CANCELLED, 1234)
);
assert_eq!(unpack(pack(status::OK, SPILLED)), (status::OK, SPILLED));
assert!(fits(0, BYTES - HEADER) && !fits(1, BYTES - HEADER) && !fits(usize::MAX, 2));
}
#[test]
fn java_knows_the_layout() {
let java = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../java-common/src/main/java/dev/yog/YogBus.java");
let Ok(src) = std::fs::read_to_string(&java) else {
return;
};
let value = |name: &str| -> Option<usize> {
let line = src
.lines()
.find(|l| l.contains(&format!("public static final int {name} =")))?;
line.split('=')
.nth(1)?
.trim()
.trim_end_matches(';')
.parse()
.ok()
};
assert_eq!(value("ARENA_HEADER"), Some(HEADER));
assert_eq!(value("ARENA_TOP"), Some(TOP));
}
#[test]
fn a_big_reply_is_kept_aside() {
let at = 8;
let mut sink = ArenaSink {
at,
len: 0,
spilled: false,
};
let reply = YogReply {
sink: (&mut sink as *mut ArenaSink).cast(),
write: write_sink,
};
unsafe { YogReply::send(&reply, &[7, 7]) };
unsafe { YogReply::send(&reply, &[8]) };
assert_eq!((sink.len, sink.spilled), (3, false));
assert_eq!(slice(base(), at, 3), Some(&[7u8, 7, 8][..]));
unsafe { YogReply::send(&reply, &vec![1; BYTES]) };
assert!(sink.spilled);
assert_eq!(
SPILL.with(|s| (s.borrow().len(), s.borrow()[..3].to_vec())),
(BYTES + 3, vec![7, 7, 8])
);
}
}