lib: refactor jni helpers

This commit is contained in:
Oscar Mira 2024-02-18 22:30:13 +01:00
parent 979e547346
commit 5c4cfd9d92
21 changed files with 732 additions and 637 deletions

View File

@ -52,6 +52,7 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN true)
set(COMMON_SOURCES
common/jvm.cc
common/java_native.cc
)
set(WALLET_SOURCES

View File

@ -0,0 +1,192 @@
#include "java_native.h"
#include "debug.h"
namespace monero {
extern ScopedJavaGlobalRef<jclass> StringClass;
jclass GetClass(JNIEnv* env, const char* name) {
jclass clazz = env->FindClass(name);
LOG_FATAL_IF(!clazz, "Java class not found: %s", name);
return clazz;
}
jmethodID GetMethodId(JNIEnv* env,
jclass clazz,
const char* name,
const char* signature) {
jmethodID ret = env->GetMethodID(clazz, name, signature);
LOG_FATAL_IF(CheckException(env),
"Error during GetMethodID: %s, %s", name, signature);
return ret;
}
jmethodID GetStaticMethodId(JNIEnv* env,
jclass clazz,
const char* name,
const char* signature) {
jmethodID ret = env->GetStaticMethodID(clazz, name, signature);
LOG_FATAL_IF(CheckException(env),
"Error during GetStaticMethodID: %s, %s", name, signature);
return ret;
}
void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
env->CallVoidMethodV(obj, method_id, args);
ThrowRuntimeErrorOnException(env);
}
jint CallIntMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
jint ret = env->CallIntMethodV(obj, method_id, args);
ThrowRuntimeErrorOnException(env);
return ret;
}
jboolean CallBooleanMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
jboolean ret = env->CallBooleanMethodV(obj, method_id, args);
ThrowRuntimeErrorOnException(env);
return ret;
}
jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
jobject ret = env->CallObjectMethodV(obj, method_id, args);
ThrowRuntimeErrorOnException(env);
return ret;
}
jstring CallStringMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
jstring ret = (jstring) env->CallObjectMethodV(obj, method_id, args);
ThrowRuntimeErrorOnException(env);
return ret;
}
void CallStaticVoidMethod(JNIEnv* env, jclass clazz, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
env->CallStaticVoidMethodV(clazz, method_id, args);
ThrowRuntimeErrorOnException(env);
}
jobject CallStaticObjectMethod(JNIEnv* env, jclass clazz, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
jobject ret = env->CallStaticObjectMethodV(clazz, method_id, args);
ThrowRuntimeErrorOnException(env);
return ret;
}
jobject NewObject(JNIEnv* env, jclass clazz, jmethodID method_id, ...) {
va_list args;
va_start(args, method_id);
jobject new_obj = env->NewObjectV(clazz, method_id, args);
ThrowRuntimeErrorOnException(env);
LOG_ASSERT(new_obj);
return new_obj;
}
std::string JavaToNativeString(JNIEnv* env, jstring j_string) {
const char* chars = env->GetStringUTFChars(j_string, /*isCopy=*/nullptr);
LOG_FATAL_IF(CheckException(env));
const jsize j_len = env->GetStringUTFLength(j_string);
LOG_FATAL_IF(CheckException(env));
std::string str(chars, j_len);
env->ReleaseStringUTFChars(j_string, chars);
LOG_FATAL_IF(CheckException(env));
return str;
}
std::vector<char> JavaToNativeByteArray(JNIEnv* env, jbyteArray j_array) {
const jsize len = env->GetArrayLength(j_array);
LOG_FATAL_IF(CheckException(env));
std::vector<char> v(len);
env->GetByteArrayRegion(j_array, 0, len,
reinterpret_cast<jbyte*>(&v[0]));
LOG_FATAL_IF(CheckException(env));
return v;
}
std::vector<int32_t> JavaToNativeIntArray(JNIEnv* env, jintArray j_array) {
const jsize len = env->GetArrayLength(j_array);
LOG_FATAL_IF(CheckException(env));
std::vector<int32_t> v(len);
env->GetIntArrayRegion(j_array, 0, len,
reinterpret_cast<jint*>(&v[0]));
LOG_FATAL_IF(CheckException(env));
return v;
}
std::vector<int64_t> JavaToNativeLongArray(JNIEnv* env, jlongArray j_array) {
const jsize len = env->GetArrayLength(j_array);
LOG_FATAL_IF(CheckException(env));
std::vector<int64_t> v(len);
env->GetLongArrayRegion(j_array, 0, len,
reinterpret_cast<jlong*>(&v[0]));
LOG_FATAL_IF(CheckException(env));
return v;
}
jstring NativeToJavaString(JNIEnv* env, const char* str) {
jstring j_str = env->NewStringUTF(str);
LOG_FATAL_IF(CheckException(env));
return j_str;
}
jstring NativeToJavaString(JNIEnv* env, const std::string& str) {
return NativeToJavaString(env, str.c_str());
}
jbyteArray NativeToJavaByteArray(JNIEnv* env,
const char* data,
size_t size) {
LOG_FATAL_IF(size > INT_MAX);
const jsize j_len = (jsize) size;
jbyteArray j_array = env->NewByteArray(j_len);
LOG_FATAL_IF(CheckException(env));
env->SetByteArrayRegion(j_array, 0, j_len,
reinterpret_cast<const jbyte*>(data));
LOG_FATAL_IF(CheckException(env));
return j_array;
}
jlongArray NativeToJavaLongArray(JNIEnv* env,
const int64_t* data,
size_t size) {
LOG_FATAL_IF(size > INT_MAX);
const jsize j_len = (jsize) size;
jlongArray j_array = env->NewLongArray(j_len);
LOG_FATAL_IF(CheckException(env));
env->SetLongArrayRegion(j_array, 0, j_len, data);
LOG_FATAL_IF(CheckException(env));
return j_array;
}
jlongArray NativeToJavaLongArray(JNIEnv* env,
const uint64_t* data,
size_t size) {
return NativeToJavaLongArray(env, (int64_t*) data, size);
}
jlong NativeToJavaPointer(void* ptr) {
static_assert(sizeof(intptr_t) <= sizeof(jlong), "");
return reinterpret_cast<intptr_t>(ptr);
}
jobjectArray NativeToJavaStringArray(JNIEnv* env, const std::vector<std::string>& container) {
auto convert_function = [](JNIEnv* env, const std::string& str) {
return ScopedJavaLocalRef<jstring>(env, NativeToJavaString(env, str));
};
return NativeToJavaObjectArray(env, container, StringClass.obj(), convert_function);
}
} // namespace monero

View File

@ -0,0 +1,102 @@
#ifndef COMMON_JVM_NATIVE_H_
#define COMMON_JVM_NATIVE_H_
#include <string>
#include <vector>
#include "common/jvm.h"
#include "common/scoped_java_ref.h"
namespace monero {
// Returns a class local reference from a fully-qualified name.
// This method must be called on a context capable of loading Java classes.
jclass GetClass(JNIEnv* env, const char* name);
// Functions to query method IDs.
jmethodID GetMethodId(JNIEnv* env,
jclass clazz,
const char* name,
const char* signature);
jmethodID GetStaticMethodId(JNIEnv* env,
jclass clazz,
const char* name,
const char* signature);
// Call non-static functions.
void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...);
jint CallIntMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...);
jboolean CallBooleanMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...);
jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...);
jstring CallStringMethod(JNIEnv* env, jobject obj, jmethodID method_id, ...);
// Call static functions.
void CallStaticVoidMethod(JNIEnv* env, jclass clazz, jmethodID method_id, ...);
jobject CallStaticObjectMethod(JNIEnv* env, jclass clazz, jmethodID method_id, ...);
// Create a new object using a constructor.
jobject NewObject(JNIEnv* env, jclass clazz, jmethodID method_id, ...);
// --------------------------------------------------------
// -- Methods for converting Java types to native types. --
// --------------------------------------------------------
// Converts a (UTF-16) jstring to a native string in modified UTF-8 encoding.
std::string JavaToNativeString(JNIEnv* env, jstring j_string);
std::vector<char> JavaToNativeByteArray(JNIEnv* env, jbyteArray j_array);
std::vector<int32_t> JavaToNativeIntArray(JNIEnv* env, jintArray j_array);
std::vector<int64_t> JavaToNativeLongArray(JNIEnv* env, jlongArray j_array);
template<typename T, typename Java_T = jobject, typename Convert>
std::vector<T> JavaToNativeVector(JNIEnv* env,
jobjectArray j_container,
Convert convert) {
std::vector<T> container;
const jsize size = env->GetArrayLength(j_container);
ThrowRuntimeErrorOnException(env);
container.reserve(size);
for (jsize i = 0; i < size; ++i) {
container.emplace_back(convert(
env, ScopedJavaLocalRef<Java_T>(
env, (Java_T) env->GetObjectArrayElement(j_container, i)).obj()));
}
return container;
}
// --------------------------------------------------------
// -- Methods for converting native types to Java types. --
// --------------------------------------------------------
jstring NativeToJavaString(JNIEnv* env, const char* str);
jstring NativeToJavaString(JNIEnv* env, const std::string& str);
jbyteArray NativeToJavaByteArray(JNIEnv* env, const char* data, size_t size);
jlongArray NativeToJavaLongArray(JNIEnv* env, const int64_t* data, size_t size);
jlongArray NativeToJavaLongArray(JNIEnv* env, const uint64_t* data, size_t size);
jlong NativeToJavaPointer(void* ptr);
// Helper function for converting std::vector<T> into a Java array.
template<typename T, typename Convert>
jobjectArray NativeToJavaObjectArray(JNIEnv* env,
const std::vector<T>& container,
jclass clazz,
Convert convert) {
jobjectArray j_container = env->NewObjectArray(
container.size(), clazz, nullptr);
ThrowRuntimeErrorOnException(env);
int i = 0;
for (const T& element: container) {
env->SetObjectArrayElement(j_container, i,
convert(env, element).obj());
++i;
}
return j_container;
}
jobjectArray NativeToJavaStringArray(JNIEnv* env,
const std::vector<std::string>& container);
} // namespace monero
#endif // COMMON_JVM_NATIVE_H_

View File

@ -1,9 +1,12 @@
#include "jvm.h"
#include <string>
#include <sys/prctl.h>
#include <pthread.h>
#include <unistd.h>
#include "debug.h"
namespace monero {
static JavaVM* g_jvm = nullptr;
@ -16,14 +19,14 @@ static pthread_once_t g_jni_ptr_once = PTHREAD_ONCE_INIT;
static pthread_key_t g_jni_ptr;
// Return thread ID as a string.
static std::string getThreadId() {
static std::string GetThreadId() {
char buf[21]; // Big enough to hold decimal digits for 64-bits plus NULL
snprintf(buf, sizeof(buf), "%lu", static_cast<long>(gettid()));
return {buf};
}
// Return the current thread's name.
static std::string getThreadName() {
static std::string GetThreadName() {
char name[17] = {0}; // In Android thread's name can be up to 16 bytes long
if (prctl(PR_GET_NAME, name) < 0) {
return {"<noname>"};
@ -31,9 +34,9 @@ static std::string getThreadName() {
return {name};
}
static JNIEnv* attachCurrentThread() {
static JNIEnv* AttachCurrentThread() {
JNIEnv* env = nullptr;
std::string name(getThreadName() + " - " + getThreadId());
std::string name(GetThreadName() + " - " + GetThreadId());
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = name.c_str();
@ -45,7 +48,7 @@ static JNIEnv* attachCurrentThread() {
}
// Return the `JNIEnv*` for the current thread.
JNIEnv* getJniEnv() {
JNIEnv* GetJniEnv() {
void* env = pthread_getspecific(g_jni_ptr);
if (!env) {
// Call GetEnv() first to detect if the thread already is attached.
@ -56,7 +59,7 @@ JNIEnv* getJniEnv() {
"Unexpected GetEnv ret: %d:%p", status, env);
if (status != JNI_OK) {
// Can race safely. Attaching a thread that is already attached is a no-op.
return attachCurrentThread();
return AttachCurrentThread();
}
}
return reinterpret_cast<JNIEnv*>(env);
@ -65,7 +68,7 @@ JNIEnv* getJniEnv() {
// This function only runs on threads where `g_jni_ptr` is non-NULL, meaning
// we were responsible for originally attaching the thread, so are responsible
// for detaching it now.
static void threadDestructor(void* prev_jni_ptr) {
static void ThreadDestructor(void* prev_jni_ptr) {
auto* env = reinterpret_cast<JNIEnv*>(prev_jni_ptr);
if (!env) {
return;
@ -74,16 +77,16 @@ static void threadDestructor(void* prev_jni_ptr) {
LOG_FATAL_IF(status != JNI_OK, "Failed to detach thread: %d", status);
}
static void createJniPtrKey() {
LOG_FATAL_IF(pthread_key_create(&g_jni_ptr, &threadDestructor));
static void CreateJniPtrKey() {
LOG_FATAL_IF(pthread_key_create(&g_jni_ptr, &ThreadDestructor));
}
JNIEnv* initializeJvm(JavaVM* jvm, int version) {
JNIEnv* InitializeJvm(JavaVM* jvm, int version) {
LOG_ASSERT(!g_jvm);
g_jvm = jvm;
LOG_ASSERT(g_jvm);
LOG_FATAL_IF(pthread_once(&g_jni_ptr_once, &createJniPtrKey));
LOG_FATAL_IF(pthread_once(&g_jni_ptr_once, &CreateJniPtrKey));
void* env = nullptr;
if (jvm->GetEnv(&env, version) != JNI_OK) {
@ -93,173 +96,4 @@ JNIEnv* initializeJvm(JavaVM* jvm, int version) {
return static_cast<JNIEnv*>(env);
}
jclass JvmRef<jobject>::getClass(JNIEnv* env) const {
jclass clazz = env->GetObjectClass(obj());
LOG_FATAL_IF(checkException(env));
return clazz;
}
void JvmRef<jobject>::callVoidMethod(JNIEnv* env,
jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
env->CallVoidMethodV(obj(), method_id, args);
throwRuntimeErrorOnException(env);
}
jint JvmRef<jobject>::callIntMethod(JNIEnv* env,
jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
jint ret = env->CallIntMethodV(obj(), method_id, args);
throwRuntimeErrorOnException(env);
return ret;
}
jboolean JvmRef<jobject>::callBooleanMethod(JNIEnv* env,
jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
jboolean ret = env->CallBooleanMethodV(obj(), method_id, args);
throwRuntimeErrorOnException(env);
return ret;
}
jobject JvmRef<jobject>::callObjectMethod(JNIEnv* env,
jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
jobject ret = env->CallObjectMethodV(obj(), method_id, args);
throwRuntimeErrorOnException(env);
return ret;
}
jstring JvmRef<jobject>::callStringMethod(JNIEnv* env,
jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
auto ret = (jstring) env->CallObjectMethodV(obj(), method_id, args);
throwRuntimeErrorOnException(env);
return ret;
}
jmethodID JvmRef<jobject>::getMethodId(JNIEnv* env,
const char* name,
const char* signature) const {
jmethodID ret = env->GetMethodID(getClass(env), name, signature);
LOG_FATAL_IF(checkException(env),
"Error during GetMethodID: %s, %s", name, signature);
return ret;
}
jmethodID JvmRef<jobject>::getStaticMethodId(JNIEnv* env,
const char* name,
const char* signature) const {
jmethodID ret = env->GetStaticMethodID(getClass(env), name, signature);
LOG_FATAL_IF(checkException(env),
"Error during GetStaticMethodID: %s, %s", name, signature);
return ret;
}
void JvmRef<jclass>::callStaticVoidMethod(JNIEnv* env,
jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
env->CallStaticVoidMethodV(obj(), method_id, args);
}
jobject JvmRef<jclass>::callStaticObjectMethod(JNIEnv* env,
jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
return env->CallStaticObjectMethodV(obj(), method_id, args);
}
jobject JvmRef<jclass>::newObject(JNIEnv* env, jmethodID method_id, ...) const {
va_list args;
va_start(args, method_id);
jobject new_obj = env->NewObjectV(obj(), method_id, args);
LOG_FATAL_IF(checkException(env));
LOG_ASSERT(new_obj);
return new_obj;
}
ScopedJvmLocalRef<jclass> findClass(JNIEnv* env, const char* name) {
ScopedJvmLocalRef<jclass> clazz(env, env->FindClass(name));
LOG_FATAL_IF(clazz.is_null(), "%s", name);
return clazz;
}
ScopedJvmLocalRef<jstring> nativeToJvmString(JNIEnv* env, const char* str) {
jstring j_str = env->NewStringUTF(str);
LOG_FATAL_IF(checkException(env));
return {env, j_str};
}
ScopedJvmLocalRef<jstring> nativeToJvmString(JNIEnv* env, const std::string& str) {
return nativeToJvmString(env, str.c_str());
}
ScopedJvmLocalRef<jbyteArray> nativeToJvmByteArray(JNIEnv* env,
const char* bytes,
size_t len) {
LOG_FATAL_IF(len > INT_MAX);
auto j_len = (jsize) len;
jbyteArray j_array = env->NewByteArray(j_len);
LOG_FATAL_IF(checkException(env));
env->SetByteArrayRegion(j_array, 0, j_len,
reinterpret_cast<const jbyte*>(bytes));
LOG_FATAL_IF(checkException(env));
return {env, j_array};
}
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env,
const int64_t* longs,
size_t len) {
LOG_FATAL_IF(len > INT_MAX);
auto j_len = (jsize) len;
jlongArray j_array = env->NewLongArray(j_len);
LOG_FATAL_IF(checkException(env));
env->SetLongArrayRegion(j_array, 0, j_len, longs);
LOG_FATAL_IF(checkException(env));
return {env, j_array};
}
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env,
const uint64_t* longs,
size_t len) {
return nativeToJvmLongArray(env, reinterpret_cast<const int64_t*>(longs), len);
}
jlong nativeToJvmPointer(void* ptr) {
static_assert(sizeof(intptr_t) <= sizeof(jlong), "");
return reinterpret_cast<intptr_t>(ptr);
}
std::string jvmToStdString(JNIEnv* env, const JvmRef<jstring>& j_string) {
const char* chars = env->GetStringUTFChars(j_string.obj(), /*isCopy=*/nullptr);
LOG_FATAL_IF(checkException(env));
const jsize len = env->GetStringUTFLength(j_string.obj());
LOG_FATAL_IF(checkException(env));
std::string str(chars, len);
env->ReleaseStringUTFChars(j_string.obj(), chars);
LOG_FATAL_IF(checkException(env));
return str;
}
std::string jvmToStdString(JNIEnv* env, jstring j_string) {
return jvmToStdString(env, JvmParamRef<jstring>(j_string));
}
std::vector<char> jvmToNativeByteArray(JNIEnv* env,
const JvmRef<jbyteArray>& j_array) {
const jsize len = env->GetArrayLength(j_array.obj());
LOG_FATAL_IF(checkException(env));
std::vector<char> v(len);
env->GetByteArrayRegion(j_array.obj(), 0, len,
reinterpret_cast<jbyte*>(&v[0]));
LOG_FATAL_IF(checkException(env));
return v;
}
} // namespace monero

View File

@ -4,22 +4,18 @@
#include <jni.h>
#include <stdexcept>
#include <string>
#include <vector>
#include "common/debug.h"
namespace monero {
// Get a JNIEnv* usable on this thread, regardless of whether it's a native
// thread or a Java thread. Attach the thread to the JVM if necessary.
JNIEnv* getJniEnv();
JNIEnv* GetJniEnv();
// This method should be called from JNI_OnLoad.
JNIEnv* initializeJvm(JavaVM* jvm, int version);
JNIEnv* InitializeJvm(JavaVM* jvm, int version);
// Checks for any Java exception and clears currently thrown exception.
static bool inline checkException(JNIEnv* env, bool log_exception = true) {
static bool inline CheckException(JNIEnv* env, bool log_exception = true) {
if (env->ExceptionCheck()) {
if (log_exception) env->ExceptionDescribe();
env->ExceptionClear();
@ -28,295 +24,12 @@ static bool inline checkException(JNIEnv* env, bool log_exception = true) {
return false;
}
static void inline throwRuntimeErrorOnException(JNIEnv* env) {
if (checkException(env, false)) {
static void inline ThrowRuntimeErrorOnException(JNIEnv* env) {
if (CheckException(env, false)) {
throw std::runtime_error("Uncaught exception returned from Java call");
}
}
// --------------------------------------------------------------------------
// Originally these classes are from WebRTC and Chromium.
// --------------------------------------------------------------------------
// Generic base class for ScopedJvmLocalRef and ScopedJvmGlobalRef.
template<typename T>
class JvmRef;
// Template specialization of JvmRef, which acts as the base class for all
// other JvmRef<> template types. This allows you to e.g. pass JvmRef<jstring>
// into a function taking const JvmRef<jobject>&.
template<>
class JvmRef<jobject> {
public:
virtual jobject obj() const { return m_obj; }
bool is_null() const {
// This is not valid for weak references. For weak references first make a
// ScopedJvmLocalRef or ScopedJvmGlobalRef before checking if it is null.
return m_obj == nullptr;
}
virtual jclass getClass(JNIEnv* env) const;
jmethodID getMethodId(JNIEnv* env,
const char* name,
const char* signature) const;
jmethodID getStaticMethodId(JNIEnv* env,
const char* name,
const char* signature) const;
void callVoidMethod(JNIEnv* env, jmethodID method_id, ...) const;
jint callIntMethod(JNIEnv* env, jmethodID method_id, ...) const;
jboolean callBooleanMethod(JNIEnv* env, jmethodID method_id, ...) const;
jobject callObjectMethod(JNIEnv* env, jmethodID method_id, ...) const;
jstring callStringMethod(JNIEnv* env, jmethodID method_id, ...) const;
protected:
constexpr JvmRef() : m_obj(nullptr) {}
explicit JvmRef(jobject obj) : m_obj(obj) {}
jobject m_obj;
private:
JvmRef(const JvmRef&) = delete;
JvmRef& operator=(const JvmRef&) = delete;
};
template<typename T>
class JvmRef : public JvmRef<jobject> {
public:
T obj() const { return static_cast<T>(m_obj); }
protected:
JvmRef() : JvmRef<jobject>(nullptr) {}
explicit JvmRef(T obj) : JvmRef<jobject>(obj) {}
private:
JvmRef(const JvmRef&) = delete;
JvmRef& operator=(const JvmRef&) = delete;
};
// Template specialization of JvmRef for Java's Class objects.
template<>
class JvmRef<jclass> : public JvmRef<jobject> {
public:
jclass obj() const { return static_cast<jclass>(m_obj); }
jclass getClass() const { return obj(); }
jclass getClass(JNIEnv* env) const { return getClass(); }
void callStaticVoidMethod(JNIEnv* env, jmethodID method_id, ...) const;
jobject callStaticObjectMethod(JNIEnv* env, jmethodID method_id, ...) const;
jobject newObject(JNIEnv* env, jmethodID method_id, ...) const;
protected:
constexpr JvmRef() noexcept: JvmRef<jobject>() {}
explicit JvmRef(jclass obj) : JvmRef<jobject>(obj) {}
private:
JvmRef(const JvmRef&) = delete;
JvmRef& operator=(const JvmRef&) = delete;
};
// Holds a local reference to a JNI method parameter.
template<typename T>
class JvmParamRef : public JvmRef<T> {
public:
// Assumes that `obj` is a parameter passed to a JNI method from Java.
// Does not assume ownership as parameters should not be deleted.
explicit JvmParamRef(T obj) : JvmRef<T>(obj) {}
// JvmParamRef(JNIEnv*, T obj) : JvmRef<T>(obj) {}
private:
JvmParamRef(const JvmParamRef&) = delete;
JvmParamRef& operator=(const JvmParamRef&) = delete;
};
// Holds a local reference to a Java object. The local reference is scoped
// to the lifetime of this object.
// Instances of this class may hold onto any JNIEnv passed into it until
// destroyed. Therefore, since a JNIEnv is only suitable for use on a single
// thread, objects of this class must be created, used, and destroyed, on a
// single thread.
// Therefore, this class should only be used as a stack-based object and from a
// single thread. If you wish to have the reference outlive the current
// callstack (e.g. as a class member) or you wish to pass it across threads,
// use a ScopedJvmGlobalRef instead.
template<typename T>
class ScopedJvmLocalRef : public JvmRef<T> {
public:
constexpr ScopedJvmLocalRef() : m_env(nullptr) {}
explicit constexpr ScopedJvmLocalRef(std::nullptr_t) : m_env(nullptr) {}
ScopedJvmLocalRef(JNIEnv* env, const JvmRef<T>& other) : m_env(env) {
Reset(other.obj(), OwnershipPolicy::RETAIN);
}
// Allow constructing e.g. ScopedJvmLocalRef<jobject> from
// ScopedJvmLocalRef<jstring>.
template<typename G>
ScopedJvmLocalRef(ScopedJvmLocalRef<G>&& other) : m_env(other.m_env) {
Reset(other.Release(), OwnershipPolicy::ADOPT);
}
ScopedJvmLocalRef(const ScopedJvmLocalRef& other) : m_env(other.m_env) {
Reset(other.obj(), OwnershipPolicy::RETAIN);
}
// Assumes that `obj` is a reference to a Java object and takes
// ownership of this reference. This should preferably not be used
// outside of JNI helper functions.
ScopedJvmLocalRef(JNIEnv* env, T obj) : JvmRef<T>(obj), m_env(env) {}
~ScopedJvmLocalRef() {
if (m_obj != nullptr)
m_env->DeleteLocalRef(m_obj);
}
void operator=(const ScopedJvmLocalRef& other) {
Reset(other.obj(), OwnershipPolicy::RETAIN);
}
void operator=(ScopedJvmLocalRef&& other) {
Reset(other.Release(), OwnershipPolicy::ADOPT);
}
// Releases the reference to the caller. The caller *must* delete the
// reference when it is done with it. Note that calling a Java method
// is *not* a transfer of ownership and Release() should not be used.
T Release() {
T obj = static_cast<T>(m_obj);
m_obj = nullptr;
return obj;
}
private:
using JvmRef<T>::m_obj;
enum OwnershipPolicy {
// The scoped object takes ownership of an object by taking over an existing
// ownership claim.
ADOPT,
// The scoped object will retain the the object and any initial ownership is
// not changed.
RETAIN
};
void Reset(T obj, OwnershipPolicy policy) {
if (m_obj != nullptr)
m_env->DeleteLocalRef(m_obj);
m_obj = (obj != nullptr && policy == OwnershipPolicy::RETAIN)
? m_env->NewLocalRef(obj)
: obj;
}
// This class is only good for use on the thread it was created on so
// it's safe to cache the non-threadsafe JNIEnv* inside this object.
JNIEnv* const m_env;
};
// Holds a global reference to a Java object. The global reference is scoped
// to the lifetime of this object. This class does not hold onto any JNIEnv*
// passed to it, hence it is safe to use across threads (within the constraints
// imposed by the underlying Java object that it references).
template<typename T>
class ScopedJvmGlobalRef : public JvmRef<T> {
public:
using JvmRef<T>::m_obj;
ScopedJvmGlobalRef() = default;
explicit constexpr ScopedJvmGlobalRef(std::nullptr_t) {}
ScopedJvmGlobalRef(JNIEnv* env, const JvmRef<T>& other)
: JvmRef<T>(static_cast<T>(env->NewGlobalRef(other.obj()))) {}
explicit ScopedJvmGlobalRef(const ScopedJvmLocalRef<T>& other)
: ScopedJvmGlobalRef(other.env(), other) {}
ScopedJvmGlobalRef(ScopedJvmGlobalRef&& other)
: JvmRef<T>(other.Release()) {}
~ScopedJvmGlobalRef() {
if (m_obj != nullptr)
getJniEnv()->DeleteGlobalRef(m_obj);
}
void operator=(const JvmRef<T>& other) {
JNIEnv* env = getJniEnv();
if (m_obj != nullptr) {
env->DeleteGlobalRef(m_obj);
}
m_obj = other.is_null() ? nullptr : env->NewGlobalRef(other.obj());
}
void operator=(std::nullptr_t) {
if (m_obj != nullptr) {
getJniEnv()->DeleteGlobalRef(m_obj);
}
m_obj = nullptr;
}
// Releases the reference to the caller. The caller *must* delete the
// reference when it is done with it. Note that calling a Java method
// is *not* a transfer of ownership and Release() should not be used.
T Release() {
T obj = static_cast<T>(m_obj);
m_obj = nullptr;
return obj;
}
private:
ScopedJvmGlobalRef(const ScopedJvmGlobalRef&) = delete;
ScopedJvmGlobalRef& operator=(const ScopedJvmGlobalRef&) = delete;
};
// Returns a class local reference from a fully-qualified name.
// This method must be called on a context capable of loading Java classes.
ScopedJvmLocalRef<jclass> findClass(JNIEnv* env, const char* name);
// Methods for converting native types to Java types.
ScopedJvmLocalRef<jstring> nativeToJvmString(JNIEnv* env, const char* str);
ScopedJvmLocalRef<jstring> nativeToJvmString(JNIEnv* env, const std::string& str);
ScopedJvmLocalRef<jbyteArray> nativeToJvmByteArray(JNIEnv* env, const char* bytes, size_t len);
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env, const int64_t* longs, size_t len);
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env, const uint64_t* longs, size_t len);
jlong nativeToJvmPointer(void* ptr);
// Helper function for converting std::vector<T> into a Java array.
template<typename T, typename Convert>
ScopedJvmLocalRef<jobjectArray> nativeToJvmObjectArray(
JNIEnv* env,
const std::vector<T>& container,
jclass clazz,
Convert convert) {
ScopedJvmLocalRef<jobjectArray> j_container(
env, env->NewObjectArray(container.size(), clazz, nullptr));
int i = 0;
for (const T& element: container) {
env->SetObjectArrayElement(j_container.obj(), i,
convert(env, element).obj());
++i;
}
return j_container;
}
template<typename T, typename Convert>
std::vector<T> jvmToNativeVector(
JNIEnv* env,
const JvmRef<jobjectArray>& j_container,
Convert convert) {
std::vector<T> container;
const jsize size = env->GetArrayLength(j_container.obj());
container.reserve(size);
for (jsize i = 0; i < size; ++i) {
container.emplace_back(convert(
env, ScopedJvmLocalRef<jobject>(
env, env->GetObjectArrayElement(j_container.obj(), i))));
}
LOG_FATAL_IF(checkException(env));
return container;
}
// Converts a Java string to a std string in modified UTF-8 encoding
std::string jvmToStdString(JNIEnv* env, const JvmRef<jstring>& j_string);
std::string jvmToStdString(JNIEnv* env, jstring j_string);
std::vector<char> jvmToNativeByteArray(JNIEnv* env,
const JvmRef<jbyteArray>& j_array);
} // namespace monero
#endif // COMMON_JVM_H_

View File

@ -0,0 +1,221 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
// Originally these classes are from Chromium.
// https://cs.chromium.org/chromium/src/base/android/scoped_java_ref.h.
#ifndef COMMON_SCOPED_JAVA_REF_H_
#define COMMON_SCOPED_JAVA_REF_H_
#include "common/jvm.h"
namespace monero {
// Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful
// for allowing functions to accept a reference without having to mandate
// whether it is a local or global type.
template<typename T>
class JavaRef;
// Template specialization of JavaRef, which acts as the base class for all
// other JavaRef<> template types. This allows you to e.g. pass JavaRef<jstring>
// into a function taking const JavaRef<jobject>&.
template<>
class JavaRef<jobject> {
public:
JavaRef(const JavaRef&) = delete;
JavaRef& operator=(const JavaRef&) = delete;
// void callVoidMethod(JNIEnv* env, jmethodID method_id, ...) const;
// jint callIntMethod(JNIEnv* env, jmethodID method_id, ...) const;
// jboolean callBooleanMethod(JNIEnv* env, jmethodID method_id, ...) const;
// jobject callObjectMethod(JNIEnv* env, jmethodID method_id, ...) const;
// jstring callStringMethod(JNIEnv* env, jmethodID method_id, ...) const;
jobject obj() const { return m_obj; }
bool is_null() const {
// This is not valid for weak references. For weak references you need to
// use env->IsSameObject(objc_, nullptr), but that should be avoided anyway
// since it does not prevent the object from being freed immediately
// thereafter. Consequently, programmers should not use this check on weak
// references anyway and should first make a ScopedJavaLocalRef or
// ScopedJavaGlobalRef before checking if it is null.
return m_obj == nullptr;
}
protected:
constexpr JavaRef() : m_obj(nullptr) {}
explicit JavaRef(jobject obj) : m_obj(obj) {}
jobject m_obj;
};
template<typename T>
class JavaRef : public JavaRef<jobject> {
public:
JavaRef(const JavaRef&) = delete;
JavaRef& operator=(const JavaRef&) = delete;
T obj() const { return static_cast<T>(m_obj); }
protected:
JavaRef() : JavaRef<jobject>(nullptr) {}
explicit JavaRef(T obj) : JavaRef<jobject>(obj) {}
};
// Holds a local reference to a JNI method parameter.
// Method parameters should not be deleted, and so this class exists purely to
// wrap them as a JavaRef<T> in the JNI binding generator. Do not create
// instances manually.
template<typename T>
class JavaParamRef : public JavaRef<T> {
public:
// Assumes that `obj` is a parameter passed to a JNI method from Java.
// Does not assume ownership as parameters should not be deleted.
explicit JavaParamRef(T obj) : JavaRef<T>(obj) {}
JavaParamRef(JNIEnv*, T obj) : JavaRef<T>(obj) {}
JavaParamRef(const JavaParamRef&) = delete;
JavaParamRef& operator=(const JavaParamRef&) = delete;
};
// Holds a local reference to a Java object. The local reference is scoped
// to the lifetime of this object.
// Instances of this class may hold onto any JNIEnv passed into it until
// destroyed. Therefore, since a JNIEnv is only suitable for use on a single
// thread, objects of this class must be created, used, and destroyed, on a
// single thread.
// Therefore, this class should only be used as a stack-based object and from a
// single thread. If you wish to have the reference outlive the current
// callstack (e.g. as a class member) or you wish to pass it across threads,
// use a ScopedJavaGlobalRef instead.
template<typename T>
class ScopedJavaLocalRef : public JavaRef<T> {
public:
ScopedJavaLocalRef() = default;
ScopedJavaLocalRef(std::nullptr_t) {}
ScopedJavaLocalRef(JNIEnv* env, const JavaRef<T>& other) : m_env(env) {
reset(other.obj(), OwnershipPolicy::RETAIN);
}
// Allow constructing e.g. ScopedJavaLocalRef<jobject> from
// ScopedJavaLocalRef<jstring>.
template<typename G>
ScopedJavaLocalRef(ScopedJavaLocalRef<G>&& other) : m_env(other.env()) {
reset(other.release(), OwnershipPolicy::ADOPT);
}
ScopedJavaLocalRef(const ScopedJavaLocalRef& other) : m_env(other.m_env) {
reset(other.obj(), OwnershipPolicy::RETAIN);
}
// Assumes that `obj` is a reference to a Java object and takes
// ownership of this reference. This should preferably not be used
// outside of JNI helper functions.
ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef<T>(obj), m_env(env) {}
~ScopedJavaLocalRef() {
if (m_obj != nullptr)
m_env->DeleteLocalRef(m_obj);
}
void operator=(const ScopedJavaLocalRef& other) {
reset(other.obj(), OwnershipPolicy::RETAIN);
}
void operator=(ScopedJavaLocalRef&& other) {
reset(other.release(), OwnershipPolicy::ADOPT);
}
// Releases the reference to the caller. The caller *must* delete the
// reference when it is done with it. Note that calling a Java method
// is *not* a transfer of ownership and release() should not be used.
T release() {
T obj = static_cast<T>(m_obj);
m_obj = nullptr;
return obj;
}
JNIEnv* env() const { return m_env; }
private:
using JavaRef<T>::m_obj;
enum OwnershipPolicy {
// The scoped object takes ownership of an object by taking over an existing
// ownership claim.
ADOPT,
// The scoped object will retain the the object and any initial ownership is
// not changed.
RETAIN
};
void reset(T obj, OwnershipPolicy policy) {
if (m_obj != nullptr)
m_env->DeleteLocalRef(m_obj);
m_obj = (obj != nullptr && policy == OwnershipPolicy::RETAIN)
? m_env->NewLocalRef(obj)
: obj;
}
JNIEnv* const m_env = GetJniEnv();
};
// Holds a global reference to a Java object. The global reference is scoped
// to the lifetime of this object. This class does not hold onto any JNIEnv*
// passed to it, hence it is safe to use across threads (within the constraints
// imposed by the underlying Java object that it references).
template<typename T>
class ScopedJavaGlobalRef : public JavaRef<T> {
public:
using JavaRef<T>::m_obj;
ScopedJavaGlobalRef() = default;
explicit constexpr ScopedJavaGlobalRef(std::nullptr_t) {}
ScopedJavaGlobalRef(JNIEnv* env, const JavaRef<T>& other)
: JavaRef<T>(static_cast<T>(env->NewGlobalRef(other.obj()))) {}
explicit ScopedJavaGlobalRef(const ScopedJavaLocalRef<T>& other)
: ScopedJavaGlobalRef(other.env(), other) {}
ScopedJavaGlobalRef(ScopedJavaGlobalRef&& other)
: JavaRef<T>(other.release()) {}
~ScopedJavaGlobalRef() {
if (m_obj != nullptr)
GetJniEnv()->DeleteGlobalRef(m_obj);
}
ScopedJavaGlobalRef(const ScopedJavaGlobalRef&) = delete;
ScopedJavaGlobalRef& operator=(const ScopedJavaGlobalRef&) = delete;
void operator=(const JavaRef<T>& other) {
JNIEnv* env = GetJniEnv();
if (m_obj != nullptr) {
env->DeleteGlobalRef(m_obj);
}
m_obj = other.is_null() ? nullptr : env->NewGlobalRef(other.obj());
}
void operator=(std::nullptr_t) {
if (m_obj != nullptr) {
GetJniEnv()->DeleteGlobalRef(m_obj);
}
m_obj = nullptr;
}
// Releases the reference to the caller. The caller *must* delete the
// reference when it is done with it. Note that calling a Java method
// is *not* a transfer of ownership and release() should not be used.
T release() {
T obj = static_cast<T>(m_obj);
m_obj = nullptr;
return obj;
}
};
} // namespace monero
#endif // COMMON_SCOPED_JAVA_REF_H_

View File

@ -3,19 +3,18 @@
namespace monero {
// im.molly.monero.mnemonics
ScopedJvmGlobalRef<jclass> MoneroMnemonicClass;
jmethodID MoneroMnemonic_buildMnemonicFromNative;
jmethodID MoneroMnemonic_buildMnemonicFromJNI;
ScopedJavaGlobalRef<jclass> MoneroMnemonicClass;
void initializeJniCache(JNIEnv* env) {
// im.molly.monero.mnemonics
auto moneroMnemonicClass = findClass(env, "im/molly/monero/mnemonics/MoneroMnemonic");
void InitializeJniCache(JNIEnv* env) {
jclass moneroMnemonic = GetClass(env, "im/molly/monero/mnemonics/MoneroMnemonic");
MoneroMnemonic_buildMnemonicFromNative = moneroMnemonicClass
.getStaticMethodId(env,
"buildMnemonicFromNative",
"([B[BLjava/lang/String;)Lim/molly/monero/mnemonics/MnemonicCode;");
MoneroMnemonic_buildMnemonicFromJNI = GetStaticMethodId(
env, moneroMnemonic,
"buildMnemonicFromJNI",
"([B[BLjava/lang/String;)Lim/molly/monero/mnemonics/MnemonicCode;");
MoneroMnemonicClass = moneroMnemonicClass;
MoneroMnemonicClass = ScopedJavaLocalRef<jclass>(env, moneroMnemonic);
}
} // namespace monero

View File

@ -1,16 +1,16 @@
#ifndef MNEMONICS_JNI_CACHE_H__
#define MNEMONICS_JNI_CACHE_H__
#include "common/jvm.h"
#include "common/java_native.h"
namespace monero {
// Initialize various classes and method pointers cached for use in JNI.
void initializeJniCache(JNIEnv* env);
void InitializeJniCache(JNIEnv* env);
// im.molly.monero.mnemonics
extern ScopedJvmGlobalRef<jclass> MoneroMnemonicClass;
extern jmethodID MoneroMnemonic_buildMnemonicFromNative;
extern jmethodID MoneroMnemonic_buildMnemonicFromJNI;
extern ScopedJavaGlobalRef<jclass> MoneroMnemonicClass;
} // namespace monero

View File

@ -7,12 +7,12 @@ namespace monero {
extern "C"
JNIEXPORT jint
JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = initializeJvm(vm, JNI_VERSION_1_6);
JNIEnv* env = InitializeJvm(vm, JNI_VERSION_1_6);
if (env == nullptr) {
return JNI_ERR;
}
initializeJniCache(env);
InitializeJniCache(env);
return JNI_VERSION_1_6;
}

View File

@ -13,31 +13,27 @@ Java_im_molly_monero_mnemonics_MoneroMnemonicKt_nativeElectrumWordsGenerateMnemo
jclass clazz,
jbyteArray j_entropy,
jstring j_language) {
std::vector<char> entropy = jvmToNativeByteArray(env, JvmParamRef<jbyteArray>(j_entropy));
std::vector<char> entropy = JavaToNativeByteArray(env, j_entropy);
Eraser entropy_eraser(entropy);
std::string language = jvmToStdString(env, j_language);
epee::wipeable_string words;
std::string language = JavaToNativeString(env, j_language);
epee::wipeable_string words;
bool success =
crypto::ElectrumWords::bytes_to_words(entropy.data(),
entropy.size(),
words,
language);
crypto::ElectrumWords::bytes_to_words(entropy.data(), entropy.size(),
words, language);
if (!success) {
return nullptr;
}
ScopedJvmLocalRef<jobject> j_mnemonic_code(
env, MoneroMnemonicClass.callStaticObjectMethod(
env, MoneroMnemonic_buildMnemonicFromNative,
j_entropy,
nativeToJvmByteArray(env, words.data(), words.size()).obj(),
j_language
)
);
jobject j_mnemonic_code = CallStaticObjectMethod(
env, MoneroMnemonicClass.obj(),
MoneroMnemonic_buildMnemonicFromJNI,
j_entropy,
NativeToJavaByteArray(env, words.data(), words.size()),
j_language);
return j_mnemonic_code.Release();
return j_mnemonic_code;
}
extern "C"
@ -47,32 +43,29 @@ Java_im_molly_monero_mnemonics_MoneroMnemonicKt_nativeElectrumWordsRecoverEntrop
jclass clazz,
jbyteArray j_source
) {
std::vector<char> words = jvmToNativeByteArray(env, JvmParamRef<jbyteArray>(j_source));
std::vector<char> words = JavaToNativeByteArray(env, j_source);
Eraser words_eraser(words);
std::string language;
epee::wipeable_string entropy, w_words(words.data(), words.size());
std::string language;
bool success =
crypto::ElectrumWords::words_to_bytes(w_words,
entropy,
0 /* len */,
true /* duplicate */,
0, /* len */
true, /* duplicate */
language);
if (!success) {
return nullptr;
}
ScopedJvmLocalRef<jobject> j_mnemonic_code(
env, MoneroMnemonicClass.callStaticObjectMethod(
env, MoneroMnemonic_buildMnemonicFromNative,
nativeToJvmByteArray(env, entropy.data(), entropy.size()).obj(),
j_source,
nativeToJvmString(env, language).obj()
)
);
jobject j_mnemonic_code = CallStaticObjectMethod(
env, MoneroMnemonicClass.obj(),
MoneroMnemonic_buildMnemonicFromJNI,
NativeToJavaByteArray(env, entropy.data(), entropy.size()),
j_source,
NativeToJavaString(env, language));
return j_mnemonic_code.Release();
return j_mnemonic_code;
}
} // namespace monero

View File

@ -26,9 +26,12 @@ class ScopedFd {
other.m_fd = -1;
}
ScopedFd(JNIEnv* env, const JvmRef<jobject>& parcel_file_descriptor) : m_fd(-1) {
ScopedFd(JNIEnv* env, const JavaRef<jobject>& parcel_file_descriptor) {
if (!parcel_file_descriptor.is_null()) {
m_fd = parcel_file_descriptor.callIntMethod(env, ParcelFileDescriptor_detachFd);
m_fd = CallIntMethod(env, parcel_file_descriptor.obj(),
ParcelFd_detachFd);
} else {
m_fd = -1;
}
}

View File

@ -1,5 +1,7 @@
#include "http_client.h"
#include "common/debug.h"
#include "jni_cache.h"
namespace monero {
@ -32,15 +34,15 @@ bool RemoteNodeClient::is_connected(bool* ssl) {
return false;
}
RemoteNodeClient::HttpResponse jvmToHttpResponse(JNIEnv* env, JvmRef<jobject>& j_http_response) {
jint code = j_http_response.callIntMethod(env, HttpResponse_getCode);
jstring mime_type = j_http_response.callStringMethod(env, HttpResponse_getContentType);
jobject body = j_http_response.callObjectMethod(env, HttpResponse_getBody);
return {
code,
(mime_type != nullptr) ? jvmToStdString(env, mime_type) : "",
ScopedFd(env, ScopedJvmLocalRef<jobject>(env, body))
};
RemoteNodeClient::HttpResponse JavaToHttpResponse(JNIEnv* env, jobject obj) {
jint code = CallIntMethod(env, obj, HttpResponse_getCode);
ScopedJavaLocalRef<jstring>
j_mime_type(env, CallStringMethod(env, obj, HttpResponse_getContentType));
ScopedJavaLocalRef<jobject>
j_body(env, CallObjectMethod(env, obj, HttpResponse_getBody));
return {code, j_mime_type.is_null() ? ""
: JavaToNativeString(env, j_mime_type.obj()),
ScopedFd(env, j_body)};
}
bool RemoteNodeClient::invoke(const boost::string_ref uri,
@ -49,26 +51,30 @@ bool RemoteNodeClient::invoke(const boost::string_ref uri,
std::chrono::milliseconds timeout,
const epee::net_utils::http::http_response_info** ppresponse_info,
const epee::net_utils::http::fields_list& additional_params) {
JNIEnv* env = getJniEnv();
std::ostringstream header;
for (const auto& p: additional_params) {
header << p.first << ": " << p.second << "\r\n";
}
JNIEnv* env = GetJniEnv();
try {
ScopedJvmLocalRef<jobject> j_response(
env, m_wallet_native.callObjectMethod(
env, WalletNative_callRemoteNode,
nativeToJvmString(env, method.data()).obj(),
nativeToJvmString(env, uri.data()).obj(),
nativeToJvmString(env, header.str()).obj(),
nativeToJvmByteArray(env, body.data(), body.length()).obj()
)
);
ScopedJavaLocalRef<jstring> j_method(env, NativeToJavaString(env, method.data()));
ScopedJavaLocalRef<jstring> j_uri(env, NativeToJavaString(env, uri.data()));
ScopedJavaLocalRef<jstring> j_hdr(env, NativeToJavaString(env, header.str()));
ScopedJavaLocalRef<jbyteArray>
j_body(env, NativeToJavaByteArray(env, body.data(), body.length()));
ScopedJavaLocalRef<jobject>
j_response = {env, CallObjectMethod(env,
m_wallet_native.obj(),
WalletNative_callRemoteNode,
j_method.obj(),
j_uri.obj(),
j_hdr.obj(),
j_body.obj())};
m_response_info.clear();
if (j_response.is_null()) {
return false;
}
HttpResponse http_response = jvmToHttpResponse(env, j_response);
HttpResponse http_response = JavaToHttpResponse(env, j_response.obj());
if (http_response.code == 401) {
// Handle HTTP unauthorized in the same way as http_simple_client_template.
return false;

View File

@ -13,10 +13,8 @@ using AbstractHttpClient = epee::net_utils::http::abstract_http_client;
class RemoteNodeClient : public AbstractHttpClient {
public:
RemoteNodeClient(
JNIEnv* env,
const JvmRef<jobject>& wallet_native)
: m_wallet_native(env, wallet_native) {}
RemoteNodeClient(JNIEnv* env, const JavaRef<jobject>& wallet_native) :
m_wallet_native(env, wallet_native) {}
bool set_proxy(const std::string& address) override;
void set_server(std::string host,
@ -54,7 +52,7 @@ class RemoteNodeClient : public AbstractHttpClient {
};
private:
const ScopedJvmGlobalRef<jobject> m_wallet_native;
const ScopedJavaGlobalRef<jobject> m_wallet_native;
epee::net_utils::http::http_response_info m_response_info;
};
@ -62,18 +60,16 @@ using HttpClientFactory = epee::net_utils::http::http_client_factory;
class RemoteNodeClientFactory : public HttpClientFactory {
public:
RemoteNodeClientFactory(
JNIEnv* env,
const JvmRef<jobject>& wallet_native)
: m_wallet_native(env, wallet_native) {}
RemoteNodeClientFactory(JNIEnv* env, const JavaRef<jobject>& wallet_native) :
m_wallet_native(env, wallet_native) {}
std::unique_ptr<AbstractHttpClient> create() override {
return std::unique_ptr<AbstractHttpClient>(new RemoteNodeClient(getJniEnv(),
m_wallet_native));
return std::unique_ptr<AbstractHttpClient>(
new RemoteNodeClient(GetJniEnv(), m_wallet_native));
}
private:
const ScopedJvmGlobalRef<jobject> m_wallet_native;
const ScopedJavaGlobalRef<jobject> m_wallet_native;
};
} // namespace monero

View File

@ -3,54 +3,71 @@
namespace monero {
// im.molly.monero
ScopedJvmGlobalRef<jclass> TxInfoClass;
jmethodID HttpResponse_getBody;
jmethodID HttpResponse_getCode;
jmethodID HttpResponse_getContentType;
jmethodID ITransferRequestCb_onTransferCreated;
jmethodID Logger_logFromNative;
jmethodID TxInfo_ctor;
jmethodID WalletNative_createPendingTransfer;
jmethodID WalletNative_callRemoteNode;
jmethodID WalletNative_onRefresh;
jmethodID WalletNative_onSuspendRefresh;
ScopedJavaGlobalRef<jclass> TxInfoClass;
// android.os
jmethodID ParcelFileDescriptor_detachFd;
jmethodID ParcelFd_detachFd;
void initializeJniCache(JNIEnv* env) {
// im.molly.monero
auto httpResponse = findClass(env, "im/molly/monero/HttpResponse");
auto logger = findClass(env, "im/molly/monero/Logger");
auto txInfoClass = findClass(env, "im/molly/monero/internal/TxInfo");
auto walletNative = findClass(env, "im/molly/monero/WalletNative");
// java.lang
ScopedJavaGlobalRef<jclass> StringClass;
TxInfoClass = txInfoClass;
void InitializeJniCache(JNIEnv* env) {
jclass httpResponse = GetClass(env, "im/molly/monero/HttpResponse");
jclass iTransferRequestCb = GetClass(env, "im/molly/monero/ITransferRequestCallback");
jclass logger = GetClass(env, "im/molly/monero/Logger");
jclass txInfo = GetClass(env, "im/molly/monero/internal/TxInfo");
jclass walletNative = GetClass(env, "im/molly/monero/WalletNative");
jclass parcelFd = GetClass(env, "android/os/ParcelFileDescriptor");
HttpResponse_getBody = httpResponse
.getMethodId(env, "getBody", "()Landroid/os/ParcelFileDescriptor;");
HttpResponse_getCode = httpResponse
.getMethodId(env, "getCode", "()I");
HttpResponse_getContentType = httpResponse
.getMethodId(env, "getContentType", "()Ljava/lang/String;");
Logger_logFromNative = logger
.getMethodId(env, "logFromNative", "(ILjava/lang/String;Ljava/lang/String;)V");
TxInfo_ctor = txInfoClass
.getMethodId(env,
"<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;JIIJJJJZZ)V");
WalletNative_callRemoteNode = walletNative
.getMethodId(env,
"callRemoteNode",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)Lim/molly/monero/HttpResponse;");
WalletNative_onRefresh = walletNative
.getMethodId(env, "onRefresh", "(IJZ)V");
WalletNative_onSuspendRefresh = walletNative
.getMethodId(env, "onSuspendRefresh", "(Z)V");
HttpResponse_getBody = GetMethodId(
env, httpResponse,
"getBody", "()Landroid/os/ParcelFileDescriptor;");
HttpResponse_getCode = GetMethodId(
env, httpResponse,
"getCode", "()I");
HttpResponse_getContentType = GetMethodId(
env, httpResponse,
"getContentType", "()Ljava/lang/String;");
ITransferRequestCb_onTransferCreated = GetMethodId(
env, iTransferRequestCb,
"onTransferCreated", "(Lim/molly/monero/IPendingTransfer;)V");
Logger_logFromNative = GetMethodId(
env, logger,
"logFromNative", "(ILjava/lang/String;Ljava/lang/String;)V");
TxInfo_ctor = GetMethodId(
env, txInfo,
"<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;JIIJJJJZZ)V");
WalletNative_createPendingTransfer = GetMethodId(
env, walletNative,
"createPendingTransfer",
"(J)Lim/molly/monero/WalletNative$NativePendingTransfer;");
WalletNative_callRemoteNode = GetMethodId(
env, walletNative,
"callRemoteNode",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)Lim/molly/monero/HttpResponse;");
WalletNative_onRefresh = GetMethodId(
env, walletNative,
"onRefresh", "(IJZ)V");
WalletNative_onSuspendRefresh = GetMethodId(
env, walletNative,
"onSuspendRefresh", "(Z)V");
// android.os
auto parcelFileDescriptor = findClass(env, "android/os/ParcelFileDescriptor");
TxInfoClass = ScopedJavaLocalRef<jclass>(env, txInfo);
ParcelFileDescriptor_detachFd = parcelFileDescriptor
.getMethodId(env, "detachFd", "()I");
ParcelFd_detachFd = GetMethodId(env, parcelFd, "detachFd", "()I");
StringClass = ScopedJavaLocalRef<jclass>(env, GetClass(env, "java/lang/String"));
}
} // namespace monero

View File

@ -2,14 +2,14 @@
#define WALLET_JNI_CACHE_H__
#include "common/jvm.h"
#include "common/java_native.h"
namespace monero {
// Initialize various classes and method pointers cached for use in JNI.
void initializeJniCache(JNIEnv* env);
void InitializeJniCache(JNIEnv* env);
// im.molly.monero
extern ScopedJvmGlobalRef<jclass> TxInfoClass;
extern jmethodID HttpResponse_getBody;
extern jmethodID HttpResponse_getCode;
extern jmethodID HttpResponse_getContentType;
@ -18,9 +18,13 @@ extern jmethodID TxInfo_ctor;
extern jmethodID WalletNative_callRemoteNode;
extern jmethodID WalletNative_onRefresh;
extern jmethodID WalletNative_onSuspendRefresh;
extern ScopedJavaGlobalRef<jclass> TxInfoClass;
// android.os
extern jmethodID ParcelFileDescriptor_detachFd;
extern jmethodID ParcelFd_detachFd;
// java.lang
extern ScopedJavaGlobalRef<jclass> StringClass;
} // namespace monero

View File

@ -9,13 +9,13 @@ namespace monero {
extern "C"
JNIEXPORT jint
JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = initializeJvm(vm, JNI_VERSION_1_6);
JNIEnv* env = InitializeJvm(vm, JNI_VERSION_1_6);
if (env == nullptr) {
return JNI_ERR;
}
initializeJniCache(env);
initializeEasyLogging();
InitializeJniCache(env);
InitializeEasyLogging();
return JNI_VERSION_1_6;
}

View File

@ -1,5 +1,7 @@
#include "logging.h"
#include "common/debug.h"
#include "jni_cache.h"
#include "easylogging++.h"
@ -41,7 +43,7 @@ class JvmEasyLoggingDispatcher : public el::LogDispatchCallback {
#define EL_BASE_FORMAT "%msg"
#define EL_TRACE_FORMAT "[%fbase:%line] " EL_BASE_FORMAT
void initializeEasyLogging() {
void InitializeEasyLogging() {
el::Configurations c;
c.setGlobally(el::ConfigurationType::ToFile, "false");
c.setGlobally(el::ConfigurationType::ToStandardOutput, "false");
@ -69,14 +71,15 @@ void JvmLogSink::write(const std::string& tag,
const std::string& msg) {
LOG_FATAL_IF(m_logger.is_null(), "Logger not set");
const int pri_idx = static_cast<int>(priority);
JNIEnv* env = getJniEnv();
ScopedJvmLocalRef<jstring> j_tag = nativeToJvmString(env, tag);
ScopedJvmLocalRef<jstring> j_msg = nativeToJvmString(env, msg);
m_logger.callVoidMethod(env, Logger_logFromNative,
pri_idx, j_tag.obj(), j_msg.obj());
JNIEnv* env = GetJniEnv();
ScopedJavaLocalRef<jstring> j_tag(env, NativeToJavaString(env, tag));
ScopedJavaLocalRef<jstring> j_msg(env, NativeToJavaString(env, msg));
CallVoidMethod(env, m_logger.obj(), Logger_logFromNative,
pri_idx, j_tag.obj(), j_msg.obj());
}
void JvmLogSink::set_logger(JNIEnv* env, const JvmRef<jobject>& logger) {
void JvmLogSink::set_logger(JNIEnv* env, const JavaRef<jobject>& logger) {
m_logger = logger;
}
@ -86,7 +89,7 @@ Java_im_molly_monero_NativeLoaderKt_nativeSetLogger(
JNIEnv* env,
jclass clazz,
jobject j_logger) {
JvmLogSink::instance()->set_logger(env, JvmParamRef<jobject>(j_logger));
JvmLogSink::instance()->set_logger(env, JavaParamRef<jobject>(j_logger));
}
} // namespace monero

View File

@ -5,7 +5,7 @@
#include <string>
#include "common/jvm.h"
#include "common/scoped_java_ref.h"
namespace monero {
@ -20,7 +20,7 @@ enum LoggingLevel {
};
// Register easylogging++ post dispatcher and configure log format.
void initializeEasyLogging();
void InitializeEasyLogging();
// Log sink to send logs to JVM via Logging.kt API.
class JvmLogSink {
@ -36,13 +36,13 @@ class JvmLogSink {
// This is called when a log message is dispatched by easylogging++.
void write(const std::string& tag, LoggingLevel priority, const std::string& msg);
void set_logger(JNIEnv* env, const JvmRef<jobject>& logger);
void set_logger(JNIEnv* env, const JavaRef<jobject>& logger);
protected:
JvmLogSink() = default;
private:
ScopedJvmGlobalRef<jobject> m_logger;
ScopedJavaGlobalRef<jobject> m_logger;
};
} // namespace monero

View File

@ -36,7 +36,7 @@ static_assert(PER_KB_FEE_QUANTIZATION_DECIMALS == 8,
Wallet::Wallet(
JNIEnv* env,
int network_id,
const JvmRef<jobject>& wallet_native)
const JavaRef<jobject>& wallet_native)
: m_wallet(static_cast<cryptonote::network_type>(network_id),
0, /* kdf_rounds */
true, /* unattended */
@ -57,7 +57,7 @@ Wallet::Wallet(
// Generate keypairs deterministically. Account creation time will be set
// to Monero epoch.
void generateAccountKeys(cryptonote::account_base& account,
void GenerateAccountKeys(cryptonote::account_base& account,
const std::vector<char>& secret_scalar) {
crypto::secret_key secret_key;
LOG_FATAL_IF(secret_scalar.size() != sizeof(secret_key.data),
@ -71,7 +71,7 @@ void Wallet::restoreAccount(const std::vector<char>& secret_scalar, uint64_t res
LOG_FATAL_IF(m_account_ready, "Account should not be reinitialized");
std::lock_guard<std::mutex> lock(m_wallet_mutex);
auto& account = m_wallet.get_account();
generateAccountKeys(account, secret_scalar);
GenerateAccountKeys(account, secret_scalar);
if (restore_point < CRYPTONOTE_MAX_BLOCK_NUMBER) {
m_restore_height = restore_point;
m_last_block_timestamp = 0;
@ -384,8 +384,8 @@ void Wallet::notifyRefreshState(bool debounce) {
last_time = std::chrono::steady_clock::now();
}
if (!debounce) {
m_callback.callVoidMethod(getJniEnv(), WalletNative_onRefresh,
height, ts, m_balance_changed);
CallVoidMethod(GetJniEnv(), m_callback.obj(), WalletNative_onRefresh,
height, ts, m_balance_changed);
}
}
@ -429,18 +429,20 @@ template<typename T>
auto Wallet::suspendRefreshAndRunLocked(T block) -> decltype(block()) {
std::unique_lock<std::mutex> wallet_lock(m_wallet_mutex, std::try_to_lock);
if (!wallet_lock.owns_lock()) {
JNIEnv* env = getJniEnv();
JNIEnv* env = GetJniEnv();
for (;;) {
if (!m_wallet.stopped()) {
m_wallet.stop();
m_callback.callVoidMethod(env, WalletNative_onSuspendRefresh, true);
CallVoidMethod(env, m_callback.obj(),
WalletNative_onSuspendRefresh, true);
}
if (wallet_lock.try_lock()) {
break;
}
std::this_thread::yield();
}
m_callback.callVoidMethod(env, WalletNative_onSuspendRefresh, false);
CallVoidMethod(env, m_callback.obj(),
WalletNative_onSuspendRefresh, false);
m_refresh_cond.notify_one();
}
// Call the lambda and release the mutex upon completion.
@ -469,8 +471,8 @@ Java_im_molly_monero_WalletNative_nativeCreate(
JNIEnv* env,
jobject thiz,
jint network_id) {
auto wallet = new Wallet(env, network_id, JvmParamRef<jobject>(thiz));
return nativeToJvmPointer(wallet);
auto* wallet = new Wallet(env, network_id, JavaParamRef<jobject>(thiz));
return NativeToJavaPointer(wallet);
}
extern "C"
@ -480,7 +482,7 @@ Java_im_molly_monero_WalletNative_nativeDispose(
jobject thiz,
jlong handle) {
auto* wallet = reinterpret_cast<Wallet*>(handle);
free(wallet);
delete wallet;
}
extern "C"
@ -489,11 +491,10 @@ Java_im_molly_monero_WalletNative_nativeRestoreAccount(
JNIEnv* env,
jobject thiz,
jlong handle,
jbyteArray p_secret_scalar,
jbyteArray j_secret_scalar,
jlong restore_point) {
auto* wallet = reinterpret_cast<Wallet*>(handle);
std::vector<char> secret_scalar = jvmToNativeByteArray(
env, JvmParamRef<jbyteArray>(p_secret_scalar));
std::vector<char> secret_scalar = JavaToNativeByteArray(env, j_secret_scalar);
Eraser secret_eraser(secret_scalar);
wallet->restoreAccount(secret_scalar, restore_point);
}
@ -582,7 +583,7 @@ Java_im_molly_monero_WalletNative_nativeGetAccountPrimaryAddress(
jobject thiz,
jlong handle) {
auto* wallet = reinterpret_cast<Wallet*>(handle);
return nativeToJvmString(env, wallet->public_address()).Release();
return NativeToJavaString(env, wallet->public_address());
}
extern "C"
@ -605,18 +606,27 @@ Java_im_molly_monero_WalletNative_nativeGetCurrentBlockchainTimestamp(
return wallet->current_blockchain_timestamp();
}
ScopedJvmLocalRef<jobject> nativeToJvmTxInfo(JNIEnv* env,
const TxInfo& tx) {
LOG_FATAL_IF(tx.m_height >= CRYPTONOTE_MAX_BLOCK_NUMBER, "Blockchain max height reached");
ScopedJavaLocalRef<jobject> NativeToJavaTxInfo(JNIEnv* env,
const TxInfo& tx) {
LOG_FATAL_IF(tx.m_height >= CRYPTONOTE_MAX_BLOCK_NUMBER,
"Blockchain max height reached");
// TODO: Check amount overflow
return {env, TxInfoClass.newObject(
env, TxInfo_ctor,
nativeToJvmString(env, pod_to_hex(tx.m_tx_hash)).obj(),
tx.m_public_key_known ? nativeToJvmString(env, pod_to_hex(tx.m_public_key)).obj() : nullptr,
tx.m_key_image_known ? nativeToJvmString(env, pod_to_hex(tx.m_key_image)).obj() : nullptr,
return {env, NewObject(
env,
TxInfoClass.obj(), TxInfo_ctor,
ScopedJavaLocalRef<jstring>(
env, NativeToJavaString(env, pod_to_hex(tx.m_tx_hash))).obj(),
tx.m_public_key_known ? ScopedJavaLocalRef<jstring>(
env, NativeToJavaString(env, pod_to_hex(tx.m_public_key))).obj()
: nullptr,
tx.m_key_image_known ? ScopedJavaLocalRef<jstring>(
env, NativeToJavaString(env, pod_to_hex(tx.m_key_image))).obj()
: nullptr,
tx.m_subaddress_major,
tx.m_subaddress_minor,
(!tx.m_recipient.empty()) ? nativeToJvmString(env, tx.m_recipient).obj() : nullptr,
(!tx.m_recipient.empty()) ? ScopedJavaLocalRef<jstring>(
env, NativeToJavaString(env, tx.m_recipient)).obj()
: nullptr,
tx.m_amount,
static_cast<jint>(tx.m_height),
tx.m_state,
@ -636,14 +646,14 @@ Java_im_molly_monero_WalletNative_nativeGetTxHistory(
jobject thiz,
jlong handle) {
auto* wallet = reinterpret_cast<Wallet*>(handle);
ScopedJvmLocalRef<jobjectArray> j_array;
jobjectArray j_array;
wallet->withTxHistory([env, &j_array](std::vector<TxInfo> const& txs) {
j_array = nativeToJvmObjectArray(env,
txs,
TxInfoClass.getClass(),
&nativeToJvmTxInfo);
j_array = NativeToJavaObjectArray<TxInfo>(env,
txs,
TxInfoClass.obj(),
&NativeToJavaTxInfo);
});
return j_array.Release();
return j_array;
}
extern "C"
@ -654,7 +664,7 @@ Java_im_molly_monero_WalletNative_nativeFetchBaseFeeEstimate(
jlong handle) {
auto* wallet = reinterpret_cast<Wallet*>(handle);
std::vector<uint64_t> fees = wallet->fetchBaseFeeEstimate();
return nativeToJvmLongArray(env, fees.data(), fees.size()).Release();
return NativeToJavaLongArray(env, fees.data(), fees.size());
}
} // namespace monero

View File

@ -79,9 +79,10 @@ class Wallet : tools::i_wallet2_callback {
REFRESH_ERROR = 3,
};
public:
Wallet(JNIEnv* env,
int network_id,
const JvmRef<jobject>& wallet_native);
const JavaRef<jobject>& wallet_native);
void restoreAccount(const std::vector<char>& secret_scalar, uint64_t restore_point);
uint64_t estimateRestoreHeight(uint64_t timestamp);
@ -130,7 +131,7 @@ class Wallet : tools::i_wallet2_callback {
std::mutex m_refresh_mutex;
// Reference to Kotlin wallet instance.
const ScopedJvmGlobalRef<jobject> m_callback;
const ScopedJavaGlobalRef<jobject> m_callback;
std::condition_variable m_refresh_cond;
std::atomic<bool> m_refresh_running;

View File

@ -63,7 +63,7 @@ object MoneroMnemonic {
@CalledByNative("mnemonics/mnemonics.cc")
@JvmStatic
private fun buildMnemonicFromNative(
private fun buildMnemonicFromJNI(
entropy: ByteArray,
wordsBytes: ByteArray,
language: String,