mirror of https://github.com/roytam1/UXP
Issue #1053 - Remove android support from mozglue
Yes, I checked for unsaved files this time...pull/24/head
parent
bb8b180863
commit
a890ecd06f
26 changed files with 19 additions and 2748 deletions
@ -1,465 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* This custom library loading code is only meant to be called |
||||
* during initialization. As a result, it takes no special
|
||||
* precautions to be threadsafe. Any of the library loading functions |
||||
* like mozload should not be available to other code. |
||||
*/ |
||||
|
||||
#include <jni.h> |
||||
#include <android/log.h> |
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/mman.h> |
||||
#include <sys/limits.h> |
||||
#include <errno.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <fcntl.h> |
||||
#include <unistd.h> |
||||
#include <zlib.h> |
||||
#include "dlfcn.h" |
||||
#include "APKOpen.h" |
||||
#include <sys/time.h> |
||||
#include <sys/resource.h> |
||||
#include <sys/prctl.h> |
||||
#include "sqlite3.h" |
||||
#include "SQLiteBridge.h" |
||||
#include "NSSBridge.h" |
||||
#include "ElfLoader.h" |
||||
#include "application.ini.h" |
||||
|
||||
#include "mozilla/TimeStamp.h" |
||||
#include "mozilla/UniquePtr.h" |
||||
#include "XREChildData.h" |
||||
|
||||
/* Android headers don't define RUSAGE_THREAD */ |
||||
#ifndef RUSAGE_THREAD |
||||
#define RUSAGE_THREAD 1 |
||||
#endif |
||||
|
||||
#ifndef RELEASE_OR_BETA |
||||
/* Official builds have the debuggable flag set to false, which disables
|
||||
* the backtrace dumper from bionic. However, as it is useful for native |
||||
* crashes happening before the crash reporter is registered, re-enable |
||||
* it on non release builds (i.e. nightly and aurora). |
||||
* Using a constructor so that it is re-enabled as soon as libmozglue.so |
||||
* is loaded. |
||||
*/ |
||||
__attribute__((constructor)) |
||||
void make_dumpable() { |
||||
prctl(PR_SET_DUMPABLE, 1); |
||||
} |
||||
#endif |
||||
|
||||
extern "C" { |
||||
/*
|
||||
* To work around http://code.google.com/p/android/issues/detail?id=23203
|
||||
* we don't link with the crt objects. In some configurations, this means |
||||
* a lack of the __dso_handle symbol because it is defined there, and |
||||
* depending on the android platform and ndk versions used, it may or may |
||||
* not be defined in libc.so. In the latter case, we fail to link. Defining |
||||
* it here as weak makes us provide the symbol when it's not provided by |
||||
* the crt objects, making the change transparent for future NDKs that |
||||
* would fix the original problem. On older NDKs, it is not a problem |
||||
* either because the way __dso_handle was used was already broken (and |
||||
* the custom linker works around it). |
||||
*/ |
||||
NS_EXPORT __attribute__((weak)) void *__dso_handle; |
||||
} |
||||
|
||||
typedef int mozglueresult; |
||||
|
||||
enum StartupEvent { |
||||
#define mozilla_StartupTimeline_Event(ev, z) ev, |
||||
#include "StartupTimeline.h" |
||||
#undef mozilla_StartupTimeline_Event |
||||
MAX_STARTUP_EVENT_ID |
||||
}; |
||||
|
||||
using namespace mozilla; |
||||
|
||||
static const int MAX_MAPPING_INFO = 32; |
||||
static mapping_info lib_mapping[MAX_MAPPING_INFO]; |
||||
|
||||
NS_EXPORT const struct mapping_info * |
||||
getLibraryMapping() |
||||
{ |
||||
return lib_mapping; |
||||
} |
||||
|
||||
void |
||||
JNI_Throw(JNIEnv* jenv, const char* classname, const char* msg) |
||||
{ |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Throw\n"); |
||||
jclass cls = jenv->FindClass(classname); |
||||
if (cls == nullptr) { |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't find exception class (or exception pending) %s\n", classname); |
||||
exit(FAILURE); |
||||
} |
||||
int rc = jenv->ThrowNew(cls, msg); |
||||
if (rc < 0) { |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Error throwing exception %s\n", msg); |
||||
exit(FAILURE); |
||||
} |
||||
jenv->DeleteLocalRef(cls); |
||||
} |
||||
|
||||
namespace { |
||||
JavaVM* sJavaVM; |
||||
pthread_t sJavaUiThread; |
||||
} |
||||
|
||||
void |
||||
abortThroughJava(const char* msg) |
||||
{ |
||||
struct sigaction sigact = {}; |
||||
if (SEGVHandler::__wrap_sigaction(SIGSEGV, nullptr, &sigact)) { |
||||
return; // sigaction call failed.
|
||||
} |
||||
|
||||
Dl_info info = {}; |
||||
if ((sigact.sa_flags & SA_SIGINFO) && |
||||
__wrap_dladdr(reinterpret_cast<void*>(sigact.sa_sigaction), &info) && |
||||
info.dli_fname && strstr(info.dli_fname, "libxul.so")) { |
||||
|
||||
return; // Existing signal handler is in libxul (i.e. we have crash reporter).
|
||||
} |
||||
|
||||
JNIEnv* env = nullptr; |
||||
if (!sJavaVM || sJavaVM->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { |
||||
return; |
||||
} |
||||
|
||||
if (!env || env->PushLocalFrame(2) != JNI_OK) { |
||||
return; |
||||
} |
||||
|
||||
jclass loader = env->FindClass("org/mozilla/gecko/mozglue/GeckoLoader"); |
||||
if (!loader) { |
||||
return; |
||||
} |
||||
|
||||
jmethodID method = env->GetStaticMethodID(loader, "abort", "(Ljava/lang/String;)V"); |
||||
jstring str = env->NewStringUTF(msg); |
||||
|
||||
if (method && str) { |
||||
env->CallStaticVoidMethod(loader, method, str); |
||||
} |
||||
|
||||
env->PopLocalFrame(nullptr); |
||||
} |
||||
|
||||
NS_EXPORT pthread_t |
||||
getJavaUiThread() |
||||
{ |
||||
return sJavaUiThread; |
||||
} |
||||
|
||||
extern "C" NS_EXPORT void MOZ_JNICALL |
||||
Java_org_mozilla_gecko_GeckoThread_registerUiThread(JNIEnv*, jclass) |
||||
{ |
||||
sJavaUiThread = pthread_self(); |
||||
} |
||||
|
||||
static void * xul_handle = nullptr; |
||||
#ifndef MOZ_FOLD_LIBS |
||||
static void * sqlite_handle = nullptr; |
||||
static void * nspr_handle = nullptr; |
||||
static void * plc_handle = nullptr; |
||||
#else |
||||
#define sqlite_handle nss_handle |
||||
#define nspr_handle nss_handle |
||||
#define plc_handle nss_handle |
||||
#endif |
||||
static void * nss_handle = nullptr; |
||||
|
||||
template <typename T> inline void |
||||
xul_dlsym(const char *symbolName, T *value) |
||||
{ |
||||
*value = (T) (uintptr_t) __wrap_dlsym(xul_handle, symbolName); |
||||
} |
||||
|
||||
static int mapping_count = 0; |
||||
|
||||
extern "C" void |
||||
report_mapping(char *name, void *base, uint32_t len, uint32_t offset) |
||||
{ |
||||
if (mapping_count >= MAX_MAPPING_INFO) |
||||
return; |
||||
|
||||
struct mapping_info *info = &lib_mapping[mapping_count++]; |
||||
info->name = strdup(name); |
||||
info->base = (uintptr_t)base; |
||||
info->len = len; |
||||
info->offset = offset; |
||||
} |
||||
|
||||
extern "C" void |
||||
delete_mapping(const char *name) |
||||
{ |
||||
for (int pos = 0; pos < mapping_count; ++pos) { |
||||
struct mapping_info *info = &lib_mapping[pos]; |
||||
if (!strcmp(info->name, name)) { |
||||
struct mapping_info *last = &lib_mapping[mapping_count - 1]; |
||||
free(info->name); |
||||
*info = *last; |
||||
--mapping_count; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void* |
||||
dlopenAPKLibrary(const char* apkName, const char* libraryName) |
||||
{ |
||||
#define APK_ASSETS_PATH "!/assets/" ANDROID_CPU_ARCH "/" |
||||
size_t filenameLength = strlen(apkName) + |
||||
sizeof(APK_ASSETS_PATH) + // includes \0 terminator
|
||||
strlen(libraryName); |
||||
auto file = MakeUnique<char[]>(filenameLength); |
||||
snprintf(file.get(), filenameLength, "%s" APK_ASSETS_PATH "%s", |
||||
apkName, libraryName); |
||||
return __wrap_dlopen(file.get(), RTLD_GLOBAL | RTLD_LAZY); |
||||
#undef APK_ASSETS_PATH |
||||
} |
||||
static mozglueresult |
||||
loadGeckoLibs(const char *apkName) |
||||
{ |
||||
TimeStamp t0 = TimeStamp::Now(); |
||||
struct rusage usage1_thread, usage1; |
||||
getrusage(RUSAGE_THREAD, &usage1_thread); |
||||
getrusage(RUSAGE_SELF, &usage1); |
||||
|
||||
xul_handle = dlopenAPKLibrary(apkName, "libxul.so"); |
||||
if (!xul_handle) { |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libxul!"); |
||||
return FAILURE; |
||||
} |
||||
|
||||
void (*XRE_StartupTimelineRecord)(int, TimeStamp); |
||||
xul_dlsym("XRE_StartupTimelineRecord", &XRE_StartupTimelineRecord); |
||||
|
||||
TimeStamp t1 = TimeStamp::Now(); |
||||
struct rusage usage2_thread, usage2; |
||||
getrusage(RUSAGE_THREAD, &usage2_thread); |
||||
getrusage(RUSAGE_SELF, &usage2); |
||||
|
||||
#define RUSAGE_TIMEDIFF(u1, u2, field) \ |
||||
((u2.ru_ ## field.tv_sec - u1.ru_ ## field.tv_sec) * 1000 + \
|
||||
(u2.ru_ ## field.tv_usec - u1.ru_ ## field.tv_usec) / 1000) |
||||
|
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loaded libs in %fms total, %ldms(%ldms) user, %ldms(%ldms) system, %ld(%ld) faults", |
||||
(t1 - t0).ToMilliseconds(), |
||||
RUSAGE_TIMEDIFF(usage1_thread, usage2_thread, utime), |
||||
RUSAGE_TIMEDIFF(usage1, usage2, utime), |
||||
RUSAGE_TIMEDIFF(usage1_thread, usage2_thread, stime), |
||||
RUSAGE_TIMEDIFF(usage1, usage2, stime), |
||||
usage2_thread.ru_majflt - usage1_thread.ru_majflt, |
||||
usage2.ru_majflt - usage1.ru_majflt); |
||||
|
||||
XRE_StartupTimelineRecord(LINKER_INITIALIZED, t0); |
||||
XRE_StartupTimelineRecord(LIBRARIES_LOADED, t1); |
||||
return SUCCESS; |
||||
} |
||||
|
||||
static mozglueresult loadNSSLibs(const char *apkName); |
||||
|
||||
static mozglueresult |
||||
loadSQLiteLibs(const char *apkName) |
||||
{ |
||||
if (sqlite_handle) |
||||
return SUCCESS; |
||||
|
||||
#ifdef MOZ_FOLD_LIBS |
||||
if (loadNSSLibs(apkName) != SUCCESS) |
||||
return FAILURE; |
||||
#else |
||||
|
||||
sqlite_handle = dlopenAPKLibrary(apkName, "libmozsqlite3.so"); |
||||
if (!sqlite_handle) { |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libmozsqlite3!"); |
||||
return FAILURE; |
||||
} |
||||
#endif |
||||
|
||||
setup_sqlite_functions(sqlite_handle); |
||||
return SUCCESS; |
||||
} |
||||
|
||||
static mozglueresult |
||||
loadNSSLibs(const char *apkName) |
||||
{ |
||||
if (nss_handle && nspr_handle && plc_handle) |
||||
return SUCCESS; |
||||
|
||||
nss_handle = dlopenAPKLibrary(apkName, "libnss3.so"); |
||||
|
||||
#ifndef MOZ_FOLD_LIBS |
||||
nspr_handle = dlopenAPKLibrary(apkName, "libnspr4.so"); |
||||
|
||||
plc_handle = dlopenAPKLibrary(apkName, "libplc4.so"); |
||||
#endif |
||||
|
||||
if (!nss_handle) { |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libnss3!"); |
||||
return FAILURE; |
||||
} |
||||
|
||||
#ifndef MOZ_FOLD_LIBS |
||||
if (!nspr_handle) { |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libnspr4!"); |
||||
return FAILURE; |
||||
} |
||||
|
||||
if (!plc_handle) { |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libplc4!"); |
||||
return FAILURE; |
||||
} |
||||
#endif |
||||
|
||||
return setup_nss_functions(nss_handle, nspr_handle, plc_handle); |
||||
} |
||||
|
||||
extern "C" NS_EXPORT void MOZ_JNICALL |
||||
Java_org_mozilla_gecko_mozglue_GeckoLoader_extractGeckoLibsNative( |
||||
JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) |
||||
{ |
||||
MOZ_ALWAYS_TRUE(!jenv->GetJavaVM(&sJavaVM)); |
||||
|
||||
const char* apkName = jenv->GetStringUTFChars(jApkName, nullptr); |
||||
if (apkName == nullptr) { |
||||
return; |
||||
} |
||||
|
||||
// Extract and cache native lib to allow for efficient startup from cache.
|
||||
void* handle = dlopenAPKLibrary(apkName, "libxul.so"); |
||||
if (handle) { |
||||
__android_log_print(ANDROID_LOG_INFO, "GeckoLibLoad", |
||||
"Extracted and cached libxul.so."); |
||||
// We have extracted and cached the lib, we can close it now.
|
||||
__wrap_dlclose(handle); |
||||
} else { |
||||
JNI_Throw(jenv, "java/lang/Exception", "Error extracting gecko libraries"); |
||||
} |
||||
|
||||
jenv->ReleaseStringUTFChars(jApkName, apkName); |
||||
} |
||||
|
||||
extern "C" NS_EXPORT void MOZ_JNICALL |
||||
Java_org_mozilla_gecko_mozglue_GeckoLoader_loadGeckoLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) |
||||
{ |
||||
jenv->GetJavaVM(&sJavaVM); |
||||
|
||||
const char* str; |
||||
// XXX: java doesn't give us true UTF8, we should figure out something
|
||||
// better to do here
|
||||
str = jenv->GetStringUTFChars(jApkName, nullptr); |
||||
if (str == nullptr) |
||||
return; |
||||
|
||||
int res = loadGeckoLibs(str); |
||||
if (res != SUCCESS) { |
||||
JNI_Throw(jenv, "java/lang/Exception", "Error loading gecko libraries"); |
||||
} |
||||
jenv->ReleaseStringUTFChars(jApkName, str); |
||||
} |
||||
|
||||
extern "C" NS_EXPORT void MOZ_JNICALL |
||||
Java_org_mozilla_gecko_mozglue_GeckoLoader_loadSQLiteLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) { |
||||
const char* str; |
||||
// XXX: java doesn't give us true UTF8, we should figure out something
|
||||
// better to do here
|
||||
str = jenv->GetStringUTFChars(jApkName, nullptr); |
||||
if (str == nullptr) |
||||
return; |
||||
|
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load sqlite start\n"); |
||||
mozglueresult rv = loadSQLiteLibs(str); |
||||
if (rv != SUCCESS) { |
||||
JNI_Throw(jenv, "java/lang/Exception", "Error loading sqlite libraries"); |
||||
} |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load sqlite done\n"); |
||||
jenv->ReleaseStringUTFChars(jApkName, str); |
||||
} |
||||
|
||||
extern "C" NS_EXPORT void MOZ_JNICALL |
||||
Java_org_mozilla_gecko_mozglue_GeckoLoader_loadNSSLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) { |
||||
const char* str; |
||||
// XXX: java doesn't give us true UTF8, we should figure out something
|
||||
// better to do here
|
||||
str = jenv->GetStringUTFChars(jApkName, nullptr); |
||||
if (str == nullptr) |
||||
return; |
||||
|
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load nss start\n"); |
||||
mozglueresult rv = loadNSSLibs(str); |
||||
if (rv != SUCCESS) { |
||||
JNI_Throw(jenv, "java/lang/Exception", "Error loading nss libraries"); |
||||
} |
||||
__android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load nss done\n"); |
||||
jenv->ReleaseStringUTFChars(jApkName, str); |
||||
} |
||||
|
||||
typedef void (*GeckoStart_t)(JNIEnv*, char*, const nsXREAppData*); |
||||
|
||||
extern "C" NS_EXPORT void MOZ_JNICALL |
||||
Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jstring jargs) |
||||
{ |
||||
GeckoStart_t GeckoStart; |
||||
xul_dlsym("GeckoStart", &GeckoStart); |
||||
if (GeckoStart == nullptr) |
||||
return; |
||||
// XXX: java doesn't give us true UTF8, we should figure out something
|
||||
// better to do here
|
||||
int len = jenv->GetStringUTFLength(jargs); |
||||
// GeckoStart needs to write in the args buffer, so we need a copy.
|
||||
char *args = (char *) malloc(len + 1); |
||||
jenv->GetStringUTFRegion(jargs, 0, len, args); |
||||
args[len] = '\0'; |
||||
ElfLoader::Singleton.ExpectShutdown(false); |
||||
GeckoStart(jenv, args, &sAppData); |
||||
ElfLoader::Singleton.ExpectShutdown(true); |
||||
free(args); |
||||
} |
||||
|
||||
typedef int GeckoProcessType; |
||||
|
||||
extern "C" NS_EXPORT mozglueresult |
||||
ChildProcessInit(int argc, char* argv[]) |
||||
{ |
||||
int i; |
||||
for (i = 0; i < (argc - 1); i++) { |
||||
if (strcmp(argv[i], "-greomni")) |
||||
continue; |
||||
|
||||
i = i + 1; |
||||
break; |
||||
} |
||||
|
||||
if (loadNSSLibs(argv[i]) != SUCCESS) { |
||||
return FAILURE; |
||||
} |
||||
if (loadSQLiteLibs(argv[i]) != SUCCESS) { |
||||
return FAILURE; |
||||
} |
||||
if (loadGeckoLibs(argv[i]) != SUCCESS) { |
||||
return FAILURE; |
||||
} |
||||
|
||||
void (*fXRE_SetProcessType)(char*); |
||||
xul_dlsym("XRE_SetProcessType", &fXRE_SetProcessType); |
||||
|
||||
mozglueresult (*fXRE_InitChildProcess)(int, char**, void*); |
||||
xul_dlsym("XRE_InitChildProcess", &fXRE_InitChildProcess); |
||||
|
||||
fXRE_SetProcessType(argv[--argc]); |
||||
|
||||
XREChildData childData; |
||||
return fXRE_InitChildProcess(argc, argv, &childData); |
||||
} |
||||
|
@ -1,39 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef APKOpen_h |
||||
#define APKOpen_h |
||||
|
||||
#include <jni.h> |
||||
#include <pthread.h> |
||||
|
||||
#ifndef NS_EXPORT |
||||
#define NS_EXPORT __attribute__ ((visibility("default"))) |
||||
#endif |
||||
|
||||
struct mapping_info { |
||||
char * name; |
||||
uintptr_t base; |
||||
size_t len; |
||||
size_t offset; |
||||
}; |
||||
|
||||
NS_EXPORT const struct mapping_info * getLibraryMapping(); |
||||
NS_EXPORT void abortThroughJava(const char* msg); |
||||
NS_EXPORT pthread_t getJavaUiThread(); |
||||
|
||||
static const int SUCCESS = 0; |
||||
static const int FAILURE = 1; |
||||
void JNI_Throw(JNIEnv* jenv, const char* classname, const char* msg); |
||||
|
||||
// Bug 1207642 - Work around Dalvik bug by realigning stack on JNI entry
|
||||
#ifndef MOZ_JNICALL |
||||
# ifdef __i386__ |
||||
# define MOZ_JNICALL JNICALL __attribute__((force_align_arg_pointer)) |
||||
# else |
||||
# define MOZ_JNICALL JNICALL |
||||
# endif |
||||
#endif |
||||
|
||||
#endif /* APKOpen_h */ |
@ -1,285 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file, |
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <stdlib.h> |
||||
#include "dlfcn.h" |
||||
#include "NSSBridge.h" |
||||
#include "APKOpen.h" |
||||
#ifdef ANDROID |
||||
#include <jni.h> |
||||
#include <android/log.h> |
||||
#endif |
||||
|
||||
#include "ElfLoader.h" |
||||
|
||||
#ifdef DEBUG |
||||
#define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x) |
||||
#else |
||||
#define LOG(x...) |
||||
#endif |
||||
|
||||
static bool initialized = false; |
||||
|
||||
#define NSS_WRAPPER_INT(name) name ## _t f_ ## name; |
||||
NSS_WRAPPER_INT(NSS_Initialize) |
||||
NSS_WRAPPER_INT(NSS_Shutdown) |
||||
NSS_WRAPPER_INT(SECITEM_ZfreeItem) |
||||
NSS_WRAPPER_INT(PK11SDR_Encrypt) |
||||
NSS_WRAPPER_INT(PK11SDR_Decrypt) |
||||
NSS_WRAPPER_INT(PK11_GetInternalKeySlot) |
||||
NSS_WRAPPER_INT(PK11_NeedUserInit) |
||||
NSS_WRAPPER_INT(PK11_InitPin) |
||||
NSS_WRAPPER_INT(PR_ErrorToString) |
||||
NSS_WRAPPER_INT(PR_GetError) |
||||
NSS_WRAPPER_INT(PR_Free) |
||||
NSS_WRAPPER_INT(PL_Base64Encode) |
||||
NSS_WRAPPER_INT(PL_Base64Decode) |
||||
NSS_WRAPPER_INT(PL_strfree) |
||||
|
||||
SECStatus doCrypto(JNIEnv* jenv, const char *path, const char *value, char** result, bool doEncrypt); |
||||
SECStatus encode(const uint8_t* data, uint32_t srclen, char** result); |
||||
SECStatus decode(const char* data, uint8_t** result, uint32_t* length); |
||||
|
||||
int |
||||
setup_nss_functions(void *nss_handle, |
||||
void *nspr_handle, |
||||
void *plc_handle) |
||||
{ |
||||
if (nss_handle == nullptr || nspr_handle == nullptr || plc_handle == nullptr) { |
||||
LOG("Missing handle\n"); |
||||
return FAILURE; |
||||
} |
||||
#define GETFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(nss_handle, #name); \ |
||||
if (!f_ ##name) { __android_log_print(ANDROID_LOG_ERROR, "GeckoJNI", "missing %s", #name); return FAILURE; } |
||||
GETFUNC(NSS_Initialize); |
||||
GETFUNC(NSS_Shutdown); |
||||
GETFUNC(PK11SDR_Encrypt); |
||||
GETFUNC(PK11SDR_Decrypt); |
||||
GETFUNC(PK11_GetInternalKeySlot); |
||||
GETFUNC(PK11_NeedUserInit); |
||||
GETFUNC(PK11_InitPin); |
||||
GETFUNC(SECITEM_ZfreeItem); |
||||
#undef GETFUNC |
||||
#define NSPRFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(nspr_handle, #name); \ |
||||
if (!f_ ##name) { __android_log_print(ANDROID_LOG_ERROR, "GeckoJNI", "missing %s", #name); return FAILURE; } |
||||
NSPRFUNC(PR_ErrorToString); |
||||
NSPRFUNC(PR_GetError); |
||||
NSPRFUNC(PR_Free); |
||||
#undef NSPRFUNC |
||||
#define PLCFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(plc_handle, #name); \ |
||||
if (!f_ ##name) { __android_log_print(ANDROID_LOG_ERROR, "GeckoJNI", "missing %s", #name); return FAILURE; } |
||||
PLCFUNC(PL_Base64Encode); |
||||
PLCFUNC(PL_Base64Decode); |
||||
PLCFUNC(PL_strfree); |
||||
#undef PLCFUNC |
||||
|
||||
return SUCCESS; |
||||
} |
||||
|
||||
/* Throws the current NSS error. */ |
||||
static void |
||||
throwError(JNIEnv* jenv, const char * funcString) { |
||||
char *msg; |
||||
|
||||
PRErrorCode perr = f_PR_GetError(); |
||||
char * errString = f_PR_ErrorToString(perr, 0); |
||||
asprintf(&msg, "%s returned error %d: %s\n", funcString, perr, errString); |
||||
LOG("Throwing error: %s\n", msg); |
||||
|
||||
JNI_Throw(jenv, "java/lang/Exception", msg); |
||||
free(msg); |
||||
LOG("Error thrown\n"); |
||||
} |
||||
|
||||
extern "C" NS_EXPORT jstring MOZ_JNICALL |
||||
Java_org_mozilla_gecko_NSSBridge_nativeEncrypt(JNIEnv* jenv, jclass, |
||||
jstring jPath, |
||||
jstring jValue) |
||||
{ |
||||
jstring ret = jenv->NewStringUTF(""); |
||||
|
||||
const char* path; |
||||
path = jenv->GetStringUTFChars(jPath, nullptr); |
||||
|
||||
const char* value; |
||||
value = jenv->GetStringUTFChars(jValue, nullptr); |
||||
|
||||
char* result; |
||||
SECStatus rv = doCrypto(jenv, path, value, &result, true); |
||||
if (rv == SECSuccess) { |
||||
ret = jenv->NewStringUTF(result); |
||||
free(result); |
||||
} |
||||
|
||||
jenv->ReleaseStringUTFChars(jValue, value); |
||||
jenv->ReleaseStringUTFChars(jPath, path); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
extern "C" NS_EXPORT jstring MOZ_JNICALL |
||||
Java_org_mozilla_gecko_NSSBridge_nativeDecrypt(JNIEnv* jenv, jclass, |
||||
jstring jPath, |
||||
jstring jValue) |
||||
{ |
||||
jstring ret = jenv->NewStringUTF(""); |
||||
|
||||
const char* path; |
||||
path = jenv->GetStringUTFChars(jPath, nullptr); |
||||
|
||||
const char* value; |
||||
value = jenv->GetStringUTFChars(jValue, nullptr); |
||||
|
||||
char* result; |
||||
SECStatus rv = doCrypto(jenv, path, value, &result, false); |
||||
if (rv == SECSuccess) { |
||||
ret = jenv->NewStringUTF(result); |
||||
free(result); |
||||
} |
||||
|
||||
jenv->ReleaseStringUTFChars(jValue, value); |
||||
jenv->ReleaseStringUTFChars(jPath, path); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
|
||||
/* Encrypts or decrypts a string. result should be freed with free() when done */ |
||||
SECStatus |
||||
doCrypto(JNIEnv* jenv, const char *path, const char *value, char** result, bool encrypt) |
||||
{ |
||||
SECStatus rv; |
||||
PK11SlotInfo *slot; |
||||
if (!initialized) { |
||||
LOG("Initialize crypto in %s\n", path); |
||||
rv = f_NSS_Initialize(path, "", "", "secmod.db", NSS_INIT_NOROOTINIT); |
||||
if (rv != SECSuccess) { |
||||
throwError(jenv, "NSS_Initialize"); |
||||
return rv; |
||||
} |
||||
initialized = true; |
||||
} |
||||
|
||||
slot = f_PK11_GetInternalKeySlot(); |
||||
if (!slot) { |
||||
throwError(jenv, "PK11_GetInternalKeySlot"); |
||||
return SECFailure; |
||||
} |
||||
|
||||
if (f_PK11_NeedUserInit(slot)) { |
||||
LOG("Initializing key3.db with default blank password.\n"); |
||||
rv = f_PK11_InitPin(slot, nullptr, nullptr); |
||||
if (rv != SECSuccess) { |
||||
throwError(jenv, "PK11_InitPin"); |
||||
return rv; |
||||
} |
||||
} |
||||
|
||||
SECItem request; |
||||
SECItem reply; |
||||
|
||||
reply.data = 0; |
||||
reply.len = 0; |
||||
|
||||
if (encrypt) { |
||||
// This can print sensitive data. Uncomment if you need it.
|
||||
// LOG("Encrypting: %s\n", value);
|
||||
request.data = (unsigned char*)value; |
||||
request.len = strlen(value); |
||||
|
||||
SECItem keyid; |
||||
keyid.data = 0; |
||||
keyid.len = 0; |
||||
rv = f_PK11SDR_Encrypt(&keyid, &request, &reply, nullptr); |
||||
|
||||
if (rv == SECSuccess) { |
||||
rv = encode(reply.data, reply.len, result); |
||||
if (rv == SECSuccess) { |
||||
LOG("Encrypted: %s\n", *result); |
||||
} else { |
||||
throwError(jenv, "encode"); |
||||
} |
||||
} else { |
||||
throwError(jenv, "PK11SDR_Encrypt"); |
||||
} |
||||
|
||||
} else { |
||||
LOG("Decoding: %s\n", value); |
||||
rv = decode(value, &request.data, &request.len); |
||||
if (rv != SECSuccess) { |
||||
throwError(jenv, "decode"); |
||||
return rv; |
||||
} |
||||
|
||||
rv = f_PK11SDR_Decrypt(&request, &reply, nullptr); |
||||
|
||||
if (rv == SECSuccess) { |
||||
*result = static_cast<char*>(malloc(reply.len + 1)); |
||||
strncpy(*result, reinterpret_cast<char*>(reply.data), reply.len); |
||||
(*result)[reply.len] = '\0'; |
||||
|
||||
// This can print sensitive data. Uncomment if you need it.
|
||||
// LOG("Decoded %i letters: %s\n", reply.len, *result);
|
||||
} else { |
||||
throwError(jenv, "PK11SDR_Decrypt"); |
||||
} |
||||
free(request.data); |
||||
} |
||||
|
||||
f_SECITEM_ZfreeItem(&reply, false); |
||||
return rv; |
||||
} |
||||
|
||||
/*
|
||||
* Base64 encodes the data passed in. The caller must deallocate _retval using free(); |
||||
*/ |
||||
SECStatus |
||||
encode(const uint8_t* data, uint32_t srclen, char** result) |
||||
{ |
||||
if (srclen > (PR_UINT32_MAX / 4) * 3) { |
||||
return SECFailure; |
||||
} |
||||
|
||||
const uint32_t dstlen = ((srclen + 2) / 3) * 4; |
||||
char* const buffer = static_cast<char*>(malloc(dstlen + 1)); |
||||
|
||||
if (!buffer || !f_PL_Base64Encode(reinterpret_cast<const char*>(data), srclen, buffer)) { |
||||
free(buffer); |
||||
*result = nullptr; |
||||
return SECFailure; |
||||
} |
||||
|
||||
buffer[dstlen] = '\0'; |
||||
*result = buffer; |
||||
return SECSuccess; |
||||
} |
||||
|
||||
/*
|
||||
* Base64 decodes the data passed in. The caller must deallocate result using free(); |
||||
*/ |
||||
SECStatus |
||||
decode(const char* data, uint8_t** result, uint32_t* length) |
||||
{ |
||||
uint32_t srclen = strlen(data); |
||||
while (srclen && data[srclen - 1] == '=') { |
||||
srclen--; |
||||
} |
||||
|
||||
// Avoid overflow when calculating result length.
|
||||
const uint32_t dstlen = (srclen / 4) * 3 + ((srclen % 4) * 3) / 4; |
||||
// At most 2 extra bytes due to padding in input.
|
||||
uint8_t* const buffer = static_cast<uint8_t*>(malloc(dstlen + 2)); |
||||
|
||||
if (!buffer || !f_PL_Base64Decode(data, srclen, reinterpret_cast<char*>(buffer))) { |
||||
free(buffer); |
||||
*result = nullptr; |
||||
*length = 0; |
||||
return SECFailure; |
||||
} |
||||
|
||||
buffer[dstlen] = '\0'; |
||||
*result = buffer; |
||||
*length = dstlen; |
||||
return SECSuccess; |
||||
} |
@ -1,42 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef NSSBridge_h |
||||
#define NSSBridge_h |
||||
|
||||
#include "nss.h" |
||||
#include "pk11func.h" |
||||
#include "pk11sdr.h" |
||||
#include "seccomon.h" |
||||
#include "secitem.h" |
||||
#include "secmodt.h" |
||||
|
||||
#include "prerror.h" |
||||
#include "plstr.h" |
||||
#include "prmem.h" |
||||
|
||||
#include <jni.h> |
||||
|
||||
int setup_nss_functions(void *nss_handle, void *nssutil_handle, void *plc_handle); |
||||
|
||||
#define NSS_WRAPPER(name, return_type, args...) \ |
||||
typedef return_type (*name ## _t)(args); \
|
||||
extern name ## _t f_ ## name; |
||||
|
||||
NSS_WRAPPER(NSS_Initialize, SECStatus, const char*, const char*, const char*, const char*, uint32_t) |
||||
NSS_WRAPPER(NSS_Shutdown, void, void) |
||||
NSS_WRAPPER(PK11SDR_Encrypt, SECStatus, SECItem *, SECItem *, SECItem *, void *) |
||||
NSS_WRAPPER(PK11SDR_Decrypt, SECStatus, SECItem *, SECItem *, void *) |
||||
NSS_WRAPPER(SECITEM_ZfreeItem, void, SECItem*, PRBool) |
||||
NSS_WRAPPER(PR_ErrorToString, char *, PRErrorCode, PRLanguageCode) |
||||
NSS_WRAPPER(PR_GetError, PRErrorCode, void) |
||||
NSS_WRAPPER(PR_Free, PRErrorCode, char *) |
||||
NSS_WRAPPER(PL_Base64Encode, char*, const char*, uint32_t, char*) |
||||
NSS_WRAPPER(PL_Base64Decode, char*, const char*, uint32_t, char*) |
||||
NSS_WRAPPER(PL_strfree, void, char*) |
||||
NSS_WRAPPER(PK11_GetInternalKeySlot, PK11SlotInfo *, void) |
||||
NSS_WRAPPER(PK11_NeedUserInit, PRBool, PK11SlotInfo *) |
||||
NSS_WRAPPER(PK11_InitPin, SECStatus, PK11SlotInfo*, const char*, const char*) |
||||
|
||||
#endif /* NSS_h */ |
@ -1,132 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "NativeCrypto.h" |
||||
#include "APKOpen.h" |
||||
|
||||
#include <jni.h> |
||||
|
||||
#include <errno.h> |
||||
#include <stdlib.h> |
||||
#include <inttypes.h> |
||||
|
||||
#include "mozilla/SHA1.h" |
||||
#include "pbkdf2_sha256.h" |
||||
|
||||
/**
|
||||
* Helper function to invoke native PBKDF2 function with JNI |
||||
* arguments. |
||||
*/ |
||||
extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_pbkdf2SHA256 |
||||
(JNIEnv *env, jclass jc, jbyteArray jpassword, jbyteArray jsalt, jint c, jint dkLen) { |
||||
if (dkLen < 0) { |
||||
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), |
||||
"dkLen should not be less than 0"); |
||||
return nullptr; |
||||
} |
||||
|
||||
jbyte *password = env->GetByteArrayElements(jpassword, nullptr); |
||||
size_t passwordLen = env->GetArrayLength(jpassword); |
||||
|
||||
jbyte *salt = env->GetByteArrayElements(jsalt, nullptr); |
||||
size_t saltLen = env->GetArrayLength(jsalt); |
||||
|
||||
uint8_t hashResult[dkLen]; |
||||
PBKDF2_SHA256((uint8_t *) password, passwordLen, (uint8_t *) salt, saltLen, |
||||
(uint64_t) c, hashResult, (size_t) dkLen); |
||||
|
||||
env->ReleaseByteArrayElements(jpassword, password, JNI_ABORT); |
||||
env->ReleaseByteArrayElements(jsalt, salt, JNI_ABORT); |
||||
|
||||
jbyteArray out = env->NewByteArray(dkLen); |
||||
if (out == nullptr) { |
||||
return nullptr; |
||||
} |
||||
env->SetByteArrayRegion(out, 0, dkLen, (jbyte *) hashResult); |
||||
|
||||
return out; |
||||
} |
||||
|
||||
using namespace mozilla; |
||||
|
||||
/**
|
||||
* Helper function to invoke native SHA-1 function with JNI arguments. |
||||
*/ |
||||
extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha1 |
||||
(JNIEnv *env, jclass jc, jbyteArray jstr) { |
||||
jbyte *str = env->GetByteArrayElements(jstr, nullptr); |
||||
size_t strLen = env->GetArrayLength(jstr); |
||||
|
||||
SHA1Sum sha1; |
||||
SHA1Sum::Hash hashResult; |
||||
sha1.update((void *) str, (uint32_t) strLen); |
||||
sha1.finish(hashResult); |
||||
|
||||
env->ReleaseByteArrayElements(jstr, str, JNI_ABORT); |
||||
|
||||
jbyteArray out = env->NewByteArray(SHA1Sum::kHashSize); |
||||
if (out == nullptr) { |
||||
return nullptr; |
||||
} |
||||
env->SetByteArrayRegion(out, 0, SHA1Sum::kHashSize, (jbyte *) hashResult); |
||||
|
||||
return out; |
||||
} |
||||
|
||||
/**
|
||||
* Helper function to invoke native SHA-256 init with JNI arguments. |
||||
*/ |
||||
extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256init |
||||
(JNIEnv *env, jclass jc) { |
||||
jbyteArray out = env->NewByteArray(sizeof(SHA256_CTX)); |
||||
if (nullptr == out) { |
||||
return nullptr; |
||||
} |
||||
|
||||
SHA256_CTX *shaContext = (SHA256_CTX*)env->GetByteArrayElements(out, nullptr); |
||||
SHA256_Init(shaContext); |
||||
|
||||
env->ReleaseByteArrayElements(out, (jbyte*)shaContext, 0); |
||||
|
||||
return out; |
||||
} |
||||
|
||||
/**
|
||||
* Helper function to invoke native SHA-256 update with JNI arguments. |
||||
*/ |
||||
extern "C" JNIEXPORT void MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256update |
||||
(JNIEnv *env, jclass jc, jbyteArray jctx, jbyteArray jstr, jint len) { |
||||
jbyte *str = env->GetByteArrayElements(jstr, nullptr); |
||||
|
||||
SHA256_CTX *shaContext = (SHA256_CTX*)env->GetByteArrayElements(jctx, nullptr); |
||||
|
||||
SHA256_Update(shaContext, (void*)str, (size_t) len); |
||||
|
||||
env->ReleaseByteArrayElements(jstr, str, JNI_ABORT); |
||||
env->ReleaseByteArrayElements(jctx, (jbyte*)shaContext, 0); |
||||
|
||||
return; |
||||
} |
||||
|
||||
/**
|
||||
* Helper function to invoke native SHA-256 finalize with JNI arguments. |
||||
*/ |
||||
extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256finalize |
||||
(JNIEnv *env, jclass jc, jbyteArray jctx) { |
||||
SHA256_CTX *shaContext = (SHA256_CTX*)env->GetByteArrayElements(jctx, nullptr); |
||||
|
||||
unsigned char* digest = new unsigned char[32]; |
||||
SHA256_Final(digest, shaContext); |
||||
|
||||
env->ReleaseByteArrayElements(jctx, (jbyte*)shaContext, JNI_ABORT); |
||||
|
||||
jbyteArray out = env->NewByteArray(32); |
||||
if (nullptr != out) { |
||||
env->SetByteArrayRegion(out, 0, 32, (jbyte*)digest); |
||||
} |
||||
|
||||
delete[] digest; |
||||
|
||||
return out; |
||||
} |
@ -1,53 +0,0 @@ |
||||
/* DO NOT EDIT THIS FILE - it is machine generated */ |
||||
#include <jni.h> |
||||
/* Header for class org_mozilla_gecko_background_nativecode_NativeCrypto */ |
||||
|
||||
#ifndef _Included_org_mozilla_gecko_background_nativecode_NativeCrypto |
||||
#define _Included_org_mozilla_gecko_background_nativecode_NativeCrypto |
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
/*
|
||||
* Class: org_mozilla_gecko_background_nativecode_NativeCrypto |
||||
* Method: pbkdf2SHA256 |
||||
* Signature: ([B[BII)[B |
||||
*/ |
||||
JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_pbkdf2SHA256 |
||||
(JNIEnv *, jclass, jbyteArray, jbyteArray, jint, jint); |
||||
|
||||
/*
|
||||
* Class: org_mozilla_gecko_background_nativecode_NativeCrypto |
||||
* Method: sha1 |
||||
* Signature: ([B)[B |
||||
*/ |
||||
JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha1 |
||||
(JNIEnv *, jclass, jbyteArray); |
||||
|
||||
/*
|
||||
* Class: org_mozilla_gecko_background_nativecode_NativeCrypto |
||||
* Method: sha256init |
||||
* Signature: ()[B |
||||
*/ |
||||
JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256init |
||||
(JNIEnv *, jclass); |
||||
|
||||
/*
|
||||
* Class: org_mozilla_gecko_background_nativecode_NativeCrypto |
||||
* Method: sha256update |
||||
* Signature: ([B[B)V |
||||
*/ |
||||
JNIEXPORT void JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256update |
||||
(JNIEnv *, jclass, jbyteArray, jbyteArray, jint); |
||||
|
||||
/*
|
||||
* Class: org_mozilla_gecko_background_nativecode_NativeCrypto |
||||
* Method: sha256finalize |
||||
* Signature: ([B)[B |
||||
*/ |
||||
JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256finalize |
||||
(JNIEnv *, jclass, jbyteArray); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif |
@ -1,413 +0,0 @@ |
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <jni.h> |
||||
#include <android/log.h> |
||||
#include "dlfcn.h" |
||||
#include "APKOpen.h" |
||||
#include "ElfLoader.h" |
||||
#include "SQLiteBridge.h" |
||||
|
||||
#ifdef DEBUG |
||||
#define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x) |
||||
#else |
||||
#define LOG(x...) |
||||
#endif |
||||
|
||||
#define SQLITE_WRAPPER_INT(name) name ## _t f_ ## name; |
||||
|
||||
SQLITE_WRAPPER_INT(sqlite3_open) |
||||
SQLITE_WRAPPER_INT(sqlite3_errmsg) |
||||
SQLITE_WRAPPER_INT(sqlite3_prepare_v2) |
||||
SQLITE_WRAPPER_INT(sqlite3_bind_parameter_count) |
||||
SQLITE_WRAPPER_INT(sqlite3_bind_null) |
||||
SQLITE_WRAPPER_INT(sqlite3_bind_text) |
||||
SQLITE_WRAPPER_INT(sqlite3_step) |
||||
SQLITE_WRAPPER_INT(sqlite3_column_count) |
||||
SQLITE_WRAPPER_INT(sqlite3_finalize) |
||||
SQLITE_WRAPPER_INT(sqlite3_close) |
||||
SQLITE_WRAPPER_INT(sqlite3_column_name) |
||||
SQLITE_WRAPPER_INT(sqlite3_column_type) |
||||
SQLITE_WRAPPER_INT(sqlite3_column_blob) |
||||
SQLITE_WRAPPER_INT(sqlite3_column_bytes) |
||||
SQLITE_WRAPPER_INT(sqlite3_column_text) |
||||
SQLITE_WRAPPER_INT(sqlite3_changes) |
||||
SQLITE_WRAPPER_INT(sqlite3_last_insert_rowid) |
||||
|
||||
void setup_sqlite_functions(void *sqlite_handle) |
||||
{ |
||||
#define GETFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(sqlite_handle, #name) |
||||
GETFUNC(sqlite3_open); |
||||
GETFUNC(sqlite3_errmsg); |
||||
GETFUNC(sqlite3_prepare_v2); |
||||
GETFUNC(sqlite3_bind_parameter_count); |
||||
GETFUNC(sqlite3_bind_null); |
||||
GETFUNC(sqlite3_bind_text); |
||||
GETFUNC(sqlite3_step); |
||||
GETFUNC(sqlite3_column_count); |
||||
GETFUNC(sqlite3_finalize); |
||||
GETFUNC(sqlite3_close); |
||||
GETFUNC(sqlite3_column_name); |
||||
GETFUNC(sqlite3_column_type); |
||||
GETFUNC(sqlite3_column_blob); |
||||
GETFUNC(sqlite3_column_bytes); |
||||
GETFUNC(sqlite3_column_text); |
||||
GETFUNC(sqlite3_changes); |
||||
GETFUNC(sqlite3_last_insert_rowid); |
||||
#undef GETFUNC |
||||
} |
||||
|
||||
static bool initialized = false; |
||||
static jclass stringClass; |
||||
static jclass objectClass; |
||||
static jclass byteBufferClass; |
||||
static jclass cursorClass; |
||||
static jmethodID jByteBufferAllocateDirect; |
||||
static jmethodID jCursorConstructor; |
||||
static jmethodID jCursorAddRow; |
||||
|
||||
static jobject sqliteInternalCall(JNIEnv* jenv, sqlite3 *db, jstring jQuery, |
||||
jobjectArray jParams, jlongArray jQueryRes); |
||||
|
||||
static void throwSqliteException(JNIEnv* jenv, const char* aFormat, ...) |
||||
{ |
||||
va_list ap; |
||||
va_start(ap, aFormat); |
||||
char* msg = nullptr; |
||||
vasprintf(&msg, aFormat, ap); |
||||
LOG("Error in SQLiteBridge: %s\n", msg); |
||||
JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", msg); |
||||
free(msg); |
||||
va_end(ap); |
||||
} |
||||
|
||||
static void |
||||
JNI_Setup(JNIEnv* jenv) |
||||
{ |
||||
if (initialized) return; |
||||
|
||||
jclass lObjectClass = jenv->FindClass("java/lang/Object"); |
||||
jclass lStringClass = jenv->FindClass("java/lang/String"); |
||||
jclass lByteBufferClass = jenv->FindClass("java/nio/ByteBuffer"); |
||||
jclass lCursorClass = jenv->FindClass("org/mozilla/gecko/sqlite/MatrixBlobCursor"); |
||||
|
||||
if (lStringClass == nullptr |
||||
|| lObjectClass == nullptr |
||||
|| lByteBufferClass == nullptr |
||||
|| lCursorClass == nullptr) { |
||||
throwSqliteException(jenv, "FindClass error"); |
||||
return; |
||||
} |
||||
|
||||
// Those are only local references. Make them global so they work
|
||||
// across calls and threads.
|
||||
objectClass = (jclass)jenv->NewGlobalRef(lObjectClass); |
||||
stringClass = (jclass)jenv->NewGlobalRef(lStringClass); |
||||
byteBufferClass = (jclass)jenv->NewGlobalRef(lByteBufferClass); |
||||
cursorClass = (jclass)jenv->NewGlobalRef(lCursorClass); |
||||
|
||||
if (stringClass == nullptr || objectClass == nullptr |
||||
|| byteBufferClass == nullptr |
||||
|| cursorClass == nullptr) { |
||||
throwSqliteException(jenv, "NewGlobalRef error"); |
||||
return; |
||||
} |
||||
|
||||
// public static ByteBuffer allocateDirect(int capacity)
|
||||
jByteBufferAllocateDirect = |
||||
jenv->GetStaticMethodID(byteBufferClass, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"); |
||||
// new MatrixBlobCursor(String [])
|
||||
jCursorConstructor = |
||||
jenv->GetMethodID(cursorClass, "<init>", "([Ljava/lang/String;)V"); |
||||
// public void addRow (Object[] columnValues)
|
||||
jCursorAddRow = |
||||
jenv->GetMethodID(cursorClass, "addRow", "([Ljava/lang/Object;)V"); |
||||
|
||||
if (jByteBufferAllocateDirect == nullptr |
||||
|| jCursorConstructor == nullptr |
||||
|| jCursorAddRow == nullptr) { |
||||
throwSqliteException(jenv, "GetMethodId error"); |
||||
return; |
||||
} |
||||
|
||||
initialized = true; |
||||
} |
||||
|
||||
extern "C" NS_EXPORT jobject MOZ_JNICALL |
||||
Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass, |
||||
jstring jDb, |
||||
jstring jQuery, |
||||
jobjectArray jParams, |
||||
jlongArray jQueryRes) |
||||
{ |
||||
JNI_Setup(jenv); |
||||
|
||||
int rc; |
||||
jobject jCursor = nullptr; |
||||
const char* dbPath; |
||||
sqlite3 *db; |
||||
|
||||
dbPath = jenv->GetStringUTFChars(jDb, nullptr); |
||||
rc = f_sqlite3_open(dbPath, &db); |
||||
jenv->ReleaseStringUTFChars(jDb, dbPath); |
||||
if (rc != SQLITE_OK) { |
||||
throwSqliteException(jenv, |
||||
"Can't open database: %s", f_sqlite3_errmsg(db)); |
||||
f_sqlite3_close(db); // close db even if open failed
|
||||
return nullptr; |
||||
} |
||||
jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); |
||||
f_sqlite3_close(db); |
||||
return jCursor; |
||||
} |
||||
|
||||
extern "C" NS_EXPORT jobject MOZ_JNICALL |
||||
Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCallWithDb(JNIEnv* jenv, jclass, |
||||
jlong jDb, |
||||
jstring jQuery, |
||||
jobjectArray jParams, |
||||
jlongArray jQueryRes) |
||||
{ |
||||
JNI_Setup(jenv); |
||||
|
||||
jobject jCursor = nullptr; |
||||
sqlite3 *db = (sqlite3*)jDb; |
||||
jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); |
||||
return jCursor; |
||||
} |
||||
|
||||
extern "C" NS_EXPORT jlong MOZ_JNICALL |
||||
Java_org_mozilla_gecko_sqlite_SQLiteBridge_openDatabase(JNIEnv* jenv, jclass, |
||||
jstring jDb) |
||||
{ |
||||
JNI_Setup(jenv); |
||||
|
||||
int rc; |
||||
const char* dbPath; |
||||
sqlite3 *db; |
||||
|
||||
dbPath = jenv->GetStringUTFChars(jDb, nullptr); |
||||
rc = f_sqlite3_open(dbPath, &db); |
||||
jenv->ReleaseStringUTFChars(jDb, dbPath); |
||||
if (rc != SQLITE_OK) { |
||||
throwSqliteException(jenv, |
||||
"Can't open database: %s", f_sqlite3_errmsg(db)); |
||||
f_sqlite3_close(db); // close db even if open failed
|
||||
return 0; |
||||
} |
||||
return (jlong)db; |
||||
} |
||||
|
||||
extern "C" NS_EXPORT void MOZ_JNICALL |
||||
Java_org_mozilla_gecko_sqlite_SQLiteBridge_closeDatabase(JNIEnv* jenv, jclass, |
||||
jlong jDb) |
||||
{ |
||||
JNI_Setup(jenv); |
||||
|
||||
sqlite3 *db = (sqlite3*)jDb; |
||||
f_sqlite3_close(db); |
||||
} |
||||
|
||||
static jobject |
||||
sqliteInternalCall(JNIEnv* jenv, |
||||
sqlite3 *db, |
||||
jstring jQuery, |
||||
jobjectArray jParams, |
||||
jlongArray jQueryRes) |
||||
{ |
||||
JNI_Setup(jenv); |
||||
|
||||
jobject jCursor = nullptr; |
||||
jsize numPars = 0; |
||||
|
||||
const char *pzTail; |
||||
sqlite3_stmt *ppStmt; |
||||
int rc; |
||||
|
||||
const char* queryStr; |
||||
queryStr = jenv->GetStringUTFChars(jQuery, nullptr); |
||||
|
||||
rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail); |
||||
if (rc != SQLITE_OK || ppStmt == nullptr) { |
||||
throwSqliteException(jenv, |
||||
"Can't prepare statement: %s", f_sqlite3_errmsg(db)); |
||||
return nullptr; |
||||
} |
||||
jenv->ReleaseStringUTFChars(jQuery, queryStr); |
||||
|
||||
// Check if number of parameters matches
|
||||
if (jParams != nullptr) { |
||||
numPars = jenv->GetArrayLength(jParams); |
||||
} |
||||
int sqlNumPars; |
||||
sqlNumPars = f_sqlite3_bind_parameter_count(ppStmt); |
||||
if (numPars != sqlNumPars) { |
||||
throwSqliteException(jenv, |
||||
"Passed parameter count (%d) " |
||||
"doesn't match SQL parameter count (%d)", |
||||
numPars, sqlNumPars); |
||||
return nullptr; |
||||
} |
||||
|
||||
if (jParams != nullptr) { |
||||
// Bind parameters, if any
|
||||
if (numPars > 0) { |
||||
for (int i = 0; i < numPars; i++) { |
||||
jobject jObjectParam = jenv->GetObjectArrayElement(jParams, i); |
||||
// IsInstanceOf or isAssignableFrom? String is final, so IsInstanceOf
|
||||
// should be OK.
|
||||
jboolean isString = jenv->IsInstanceOf(jObjectParam, stringClass); |
||||
if (isString != JNI_TRUE) { |
||||
throwSqliteException(jenv, |
||||
"Parameter is not of String type"); |
||||
return nullptr; |
||||
} |
||||
|
||||
// SQLite parameters index from 1.
|
||||
if (jObjectParam == nullptr) { |
||||
rc = f_sqlite3_bind_null(ppStmt, i + 1); |
||||
} else { |
||||
jstring jStringParam = (jstring) jObjectParam; |
||||
const char* paramStr = jenv->GetStringUTFChars(jStringParam, nullptr); |
||||
rc = f_sqlite3_bind_text(ppStmt, i + 1, paramStr, -1, SQLITE_TRANSIENT); |
||||
jenv->ReleaseStringUTFChars(jStringParam, paramStr); |
||||
} |
||||
|
||||
if (rc != SQLITE_OK) { |
||||
throwSqliteException(jenv, "Error binding query parameter"); |
||||
return nullptr; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Execute the query and step through the results
|
||||
rc = f_sqlite3_step(ppStmt); |
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE) { |
||||
throwSqliteException(jenv, |
||||
"Can't step statement: (%d) %s", rc, f_sqlite3_errmsg(db)); |
||||
return nullptr; |
||||
} |
||||
|
||||
// Get the column count and names
|
||||
int cols; |
||||
cols = f_sqlite3_column_count(ppStmt); |
||||
|
||||
{ |
||||
// Allocate a String[cols]
|
||||
jobjectArray jStringArray = jenv->NewObjectArray(cols, |
||||
stringClass, |
||||
nullptr); |
||||
if (jStringArray == nullptr) { |
||||
throwSqliteException(jenv, "Can't allocate String[]"); |
||||
return nullptr; |
||||
} |
||||
|
||||
// Assign column names to the String[]
|
||||
for (int i = 0; i < cols; i++) { |
||||
const char* colName = f_sqlite3_column_name(ppStmt, i); |
||||
jstring jStr = jenv->NewStringUTF(colName); |
||||
jenv->SetObjectArrayElement(jStringArray, i, jStr); |
||||
} |
||||
|
||||
// Construct the MatrixCursor(String[]) with given column names
|
||||
jCursor = jenv->NewObject(cursorClass, |
||||
jCursorConstructor, |
||||
jStringArray); |
||||
if (jCursor == nullptr) { |
||||
throwSqliteException(jenv, "Can't allocate MatrixBlobCursor"); |
||||
return nullptr; |
||||
} |
||||
} |
||||
|
||||
// Return the id and number of changed rows in jQueryRes
|
||||
{ |
||||
jlong id = f_sqlite3_last_insert_rowid(db); |
||||
jenv->SetLongArrayRegion(jQueryRes, 0, 1, &id); |
||||
|
||||
jlong changed = f_sqlite3_changes(db); |
||||
jenv->SetLongArrayRegion(jQueryRes, 1, 1, &changed); |
||||
} |
||||
|
||||
// For each row, add an Object[] to the passed ArrayList,
|
||||
// with that containing either String or ByteArray objects
|
||||
// containing the columns
|
||||
while (rc != SQLITE_DONE) { |
||||
// Process row
|
||||
// Construct Object[]
|
||||
jobjectArray jRow = jenv->NewObjectArray(cols, |
||||
objectClass, |
||||
nullptr); |
||||
if (jRow == nullptr) { |
||||
throwSqliteException(jenv, "Can't allocate jRow Object[]"); |
||||
return nullptr; |
||||
} |
||||
|
||||
for (int i = 0; i < cols; i++) { |
||||
int colType = f_sqlite3_column_type(ppStmt, i); |
||||
if (colType == SQLITE_BLOB) { |
||||
// Treat as blob
|
||||
const void* blob = f_sqlite3_column_blob(ppStmt, i); |
||||
int colLen = f_sqlite3_column_bytes(ppStmt, i); |
||||
|
||||
// Construct ByteBuffer of correct size
|
||||
jobject jByteBuffer = |
||||
|