use std::sync::OnceLock;
use jni::objects::{JByteArray, JClass, JObject, JObjectArray, JString, JValue};
use jni::sys::{JNI_VERSION_1_6, JavaVM as RawJavaVM, jint};
use jni::{JNIEnv, JavaVM};
use crate::{Error, Result};
static VM: OnceLock<JavaVM> = OnceLock::new();
#[unsafe(no_mangle)]
pub extern "system" fn JNI_OnLoad(vm: *mut RawJavaVM, _reserved: *mut core::ffi::c_void) -> jint {
if let Ok(vm) = unsafe { JavaVM::from_raw(vm) } {
let _ = VM.set(vm);
}
JNI_VERSION_1_6
}
fn vm() -> Result<&'static JavaVM> {
VM.get().ok_or_else(|| Error::HardwareUnavailable {
reason: "the library was loaded without a Java VM, so the Android key store cannot be \
reached from it"
.to_owned(),
})
}
pub(super) fn with_env<T>(
operation: &'static str,
f: impl FnOnce(&mut JNIEnv<'_>) -> Result<T>,
) -> Result<T> {
let mut env = vm()?.attach_current_thread().map_err(|error| Error::Jni {
operation,
detail: error.to_string(),
})?;
f(&mut env)
}
pub(super) fn class<'local>(
env: &mut JNIEnv<'local>,
name: &str,
operation: &'static str,
) -> Result<JClass<'local>> {
env.find_class(name)
.map_err(|error| describe(env, operation, error))
}
pub(super) fn string<'local>(
env: &mut JNIEnv<'local>,
value: &str,
operation: &'static str,
) -> Result<JString<'local>> {
env.new_string(value)
.map_err(|error| describe(env, operation, error))
}
pub(super) fn bytes<'local>(
env: &mut JNIEnv<'local>,
value: &[u8],
operation: &'static str,
) -> Result<JByteArray<'local>> {
env.byte_array_from_slice(value)
.map_err(|error| describe(env, operation, error))
}
pub(super) fn construct<'local>(
env: &mut JNIEnv<'local>,
class: &JClass<'local>,
signature: &str,
args: &[JValue<'local, '_>],
operation: &'static str,
) -> Result<JObject<'local>> {
env.new_object(class, signature, args)
.map_err(|error| describe(env, operation, error))
}
pub(super) fn string_array<'local>(
env: &mut JNIEnv<'local>,
values: &[&str],
operation: &'static str,
) -> Result<JObjectArray<'local>> {
let class = class(env, "java/lang/String", operation)?;
let array = env
.new_object_array(values.len() as i32, &class, JObject::null())
.map_err(|error| describe(env, operation, error))?;
for (index, value) in values.iter().enumerate() {
let value = string(env, value, operation)?;
env.set_object_array_element(&array, index as i32, &value)
.map_err(|error| describe(env, operation, error))?;
}
Ok(array)
}
pub(super) fn static_object<'local>(
env: &mut JNIEnv<'local>,
class: &JClass<'local>,
name: &str,
signature: &str,
args: &[JValue<'local, '_>],
operation: &'static str,
) -> Result<JObject<'local>> {
env.call_static_method(class, name, signature, args)
.and_then(|value| value.l())
.map_err(|error| describe(env, operation, error))
}
pub(super) fn object<'local>(
env: &mut JNIEnv<'local>,
target: &JObject<'local>,
name: &str,
signature: &str,
args: &[JValue<'local, '_>],
operation: &'static str,
) -> Result<JObject<'local>> {
env.call_method(target, name, signature, args)
.and_then(|value| value.l())
.map_err(|error| describe(env, operation, error))
}
pub(super) fn object_bytes(
env: &mut JNIEnv<'_>,
array: JObject<'_>,
operation: &'static str,
) -> Result<Vec<u8>> {
env.convert_byte_array(JByteArray::from(array))
.map_err(|error| describe(env, operation, error))
}
pub(super) fn byte_array<'local>(
env: &mut JNIEnv<'local>,
target: &JObject<'local>,
name: &str,
signature: &str,
args: &[JValue<'local, '_>],
operation: &'static str,
) -> Result<Vec<u8>> {
let array = object(env, target, name, signature, args, operation)?;
object_bytes(env, array, operation)
}
pub(super) fn byte_array_authenticated<'local>(
env: &mut JNIEnv<'local>,
target: &JObject<'local>,
name: &str,
signature: &str,
args: &[JValue<'local, '_>],
operation: &'static str,
) -> Result<Vec<u8>> {
match byte_array(env, target, name, signature, args, operation) {
Ok(bytes) => Ok(bytes),
Err(Error::Jni { detail, .. }) if detail.contains("AEADBadTag") => {
Err(crate::Error::InvalidWrappedKey {
reason: "the platform rejected the authentication tag (a damaged blob, or one wrapped by a different key)",
code: 0,
})
}
Err(other) => Err(other),
}
}
pub(super) fn boolean<'local>(
env: &mut JNIEnv<'local>,
target: &JObject<'local>,
name: &str,
signature: &str,
args: &[JValue<'local, '_>],
operation: &'static str,
) -> Result<bool> {
env.call_method(target, name, signature, args)
.and_then(|value| value.z())
.map_err(|error| describe(env, operation, error))
}
pub(super) fn call_void<'local>(
env: &mut JNIEnv<'local>,
target: &JObject<'local>,
name: &str,
signature: &str,
args: &[JValue<'local, '_>],
operation: &'static str,
) -> Result<()> {
env.call_method(target, name, signature, args)
.map(|_| ())
.map_err(|error| describe(env, operation, error))
}
pub(super) fn text(
env: &mut JNIEnv<'_>,
value: &JString<'_>,
operation: &'static str,
) -> Result<String> {
let value = env
.get_string(value)
.map_err(|error| describe(env, operation, error))?;
Ok(value.to_string_lossy().into_owned())
}
fn describe(env: &mut JNIEnv<'_>, operation: &'static str, error: jni::errors::Error) -> Error {
let detail = pending_exception(env).unwrap_or_else(|| error.to_string());
Error::Jni { operation, detail }
}
fn pending_exception(env: &mut JNIEnv<'_>) -> Option<String> {
if !env.exception_check().ok()? {
return None;
}
let throwable = env.exception_occurred().ok()?;
let _ = env.exception_clear();
describe_throwable(env, &throwable)
}
fn describe_throwable<'local>(
env: &mut JNIEnv<'local>,
throwable: &JObject<'local>,
) -> Option<String> {
let class = JObject::from(env.get_object_class(throwable).ok()?);
let name = object(
env,
&class,
"getName",
"()Ljava/lang/String;",
&[],
"Throwable.getClass",
)
.ok()
.map(JString::from)?;
let name = text(env, &name, "Throwable.getClass").ok()?;
let message = object(
env,
throwable,
"getMessage",
"()Ljava/lang/String;",
&[],
"Throwable.getMessage",
)
.ok()
.filter(|value| !value.is_null())
.map(JString::from);
match message {
Some(message) => Some(match text(env, &message, "Throwable.getMessage") {
Ok(message) => format!("{name}: {message}"),
Err(_) => name,
}),
None => Some(name),
}
}