diff --git a/.github/workflows/prebuild_assets.yml b/.github/workflows/prebuild_assets.yml index 2dd05f07..73692b62 100644 --- a/.github/workflows/prebuild_assets.yml +++ b/.github/workflows/prebuild_assets.yml @@ -26,7 +26,7 @@ jobs: id: cache_prebuild with: path: internal/prebuild-binaries/build/output - key: sqlite-build-${{ hashFiles('internal/prebuild-binaries', 'plugins/build-plugin') }} + key: sqlite-build-${{ hashFiles('internal/prebuild-binaries/build.gradle.kts', 'plugins/build-plugin/src') }} - name: Validate Gradle Wrapper if: steps.cache_prebuild.outputs.cache-hit != 'true' @@ -50,10 +50,18 @@ jobs: - name: Set up XCode if: steps.cache_prebuild.outputs.cache-hit != 'true' uses: maxim-lobanov/setup-xcode@v1 + - name: Download build tools + if: steps.cache_prebuild.outputs.cache-hit != 'true' + run: | + brew install lld + cd internal/prebuild-binaries + ./download_glibc.sh + ./download_llvm_mingw.sh + - name: Compile SQLite with Gradle if: steps.cache_prebuild.outputs.cache-hit != 'true' run: | - ./gradlew --scan internal:prebuild-binaries:compileNative + ./gradlew --scan internal:prebuild-binaries:compileAll shell: bash - uses: actions/upload-artifact@v5 id: upload diff --git a/README.md b/README.md index f6c3012f..b9f9f00e 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ and API documentation [here](https://powersync-ja.github.io/powersync-kotlin/). connector provides the connection between your application backend and the PowerSync managed database. It is used to: 1. Retrieve a token to connect to the PowerSync service. 2. Apply local changes on your backend application server (and from there, to your backend database). +- [sqlite3multipleciphers](./sqlite3multipleciphers/) + + - A SQLite driver implementation based on SQLite3MultipleCiphers. ## Demo Apps / Example Projects diff --git a/build.gradle.kts b/build.gradle.kts index aab123be..612d0e74 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,6 +73,7 @@ dependencies { dokka(project(":integrations:room")) dokka(project(":integrations:sqldelight")) dokka(project(":integrations:supabase")) + dokka(projects.sqlite3multipleciphers) } dokka { diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 88318210..e211faef 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -137,6 +137,7 @@ kotlin { optIn("kotlinx.cinterop.ExperimentalForeignApi") optIn("kotlin.time.ExperimentalTime") optIn("kotlin.experimental.ExperimentalObjCRefinement") + optIn("com.powersync.PowerSyncInternal") } } @@ -239,8 +240,6 @@ android { .toInt() consumerProguardFiles("proguard-rules.pro") } - - ndkVersion = "27.1.12297006" } tasks.named(kotlin.jvm().compilations["main"].processResourcesTaskName) { diff --git a/common/src/commonMain/kotlin/com/powersync/PowerSyncInternal.kt b/common/src/commonMain/kotlin/com/powersync/PowerSyncInternal.kt new file mode 100644 index 00000000..0f2b3694 --- /dev/null +++ b/common/src/commonMain/kotlin/com/powersync/PowerSyncInternal.kt @@ -0,0 +1,12 @@ +package com.powersync + +@RequiresOptIn(message = "This API should not be used outside of PowerSync SDK packages") +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.FUNCTION, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.PROPERTY, + AnnotationTarget.VALUE_PARAMETER, +) +public annotation class PowerSyncInternal diff --git a/common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt b/common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt index e6f91923..c75717ec 100644 --- a/common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt +++ b/common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt @@ -5,4 +5,4 @@ import com.powersync.db.runWrapped @Throws(PowerSyncException::class) public actual fun resolvePowerSyncLoadableExtensionPath(): String? = runWrapped { powersyncExtension } -private val powersyncExtension: String by lazy { extractLib("powersync") } +private val powersyncExtension: String by lazy { extractLib(BuildConfig::class, "powersync") } diff --git a/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt b/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt index ee7ee44a..ce82e24d 100644 --- a/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt +++ b/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt @@ -2,10 +2,13 @@ package com.powersync import java.io.File import java.util.UUID +import kotlin.reflect.KClass -private class R - -internal fun extractLib(fileName: String): String { +@PowerSyncInternal +public fun extractLib( + reference: KClass<*>, + fileName: String, +): String { val os = System.getProperty("os.name").lowercase() val (prefix, extension) = when { @@ -34,7 +37,7 @@ internal fun extractLib(fileName: String): String { val resourcePath = "/$prefix${fileName}_$arch.$extension" - (R::class.java.getResourceAsStream(resourcePath) ?: error("Resource $resourcePath not found")).use { input -> + (reference.java.getResourceAsStream(resourcePath) ?: error("Resource $resourcePath not found")).use { input -> file.outputStream().use { output -> input.copyTo(output) } } diff --git a/core-tests-android/build.gradle.kts b/core-tests-android/build.gradle.kts index d422b643..8099a16f 100644 --- a/core-tests-android/build.gradle.kts +++ b/core-tests-android/build.gradle.kts @@ -8,6 +8,7 @@ plugins { dependencies { implementation(projects.core) + implementation(projects.sqlite3multipleciphers) implementation(libs.androidx.core) implementation(libs.androidx.appcompat) implementation(libs.androidx.material) diff --git a/core-tests-android/src/androidTest/java/com/powersync/EncryptedDatabaseTest.kt b/core-tests-android/src/androidTest/java/com/powersync/EncryptedDatabaseTest.kt new file mode 100644 index 00000000..2a2d6bdb --- /dev/null +++ b/core-tests-android/src/androidTest/java/com/powersync/EncryptedDatabaseTest.kt @@ -0,0 +1,57 @@ +package com.powersync + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.sqlite.SQLiteException +import androidx.sqlite.execSQL +import app.cash.turbine.turbineScope +import com.powersync.db.schema.Schema +import com.powersync.encryption.AndroidEncryptedDatabaseFactory +import com.powersync.encryption.Key +import com.powersync.testutils.UserRow +import kotlinx.coroutines.* +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class EncryptedDatabaseTest { + + @Test + fun testEncryptedDatabase() = + runTest { + val context = InstrumentationRegistry.getInstrumentation().targetContext + + val database = PowerSyncDatabase( + factory = AndroidEncryptedDatabaseFactory( + context, + Key.Passphrase("mykey") + ), + schema = Schema(UserRow.table), + dbFilename = "encrypted_test", + ) + + assertEquals("chacha20", database.get("PRAGMA cipher") { it.getString(0)!! }) + + database.execute( + "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)", + listOf("Test", "test@example.org"), + ) + database.close() + + val unencryptedFactory = DatabaseDriverFactory(context) + val unencrypted = unencryptedFactory.openConnection("encrypted_test", null, false) + + try { + unencrypted.execSQL("SELECT * FROM sqlite_schema") + throw IllegalStateException("Was able to read schema from encrypted database without supplying a key") + } catch (_: SQLiteException) { + // Expected + } + unencrypted.close() + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ce18001a..06a273a2 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -106,8 +106,6 @@ android { .toInt() consumerProguardFiles("proguard-rules.pro") } - - ndkVersion = "27.1.12297006" } // We want to build with recent JDKs, but need to make sure we support Java 8. https://jakewharton.com/build-on-latest-java-test-through-lowest-java/ diff --git a/internal/prebuild-binaries/README.md b/internal/prebuild-binaries/README.md index f2e8d28f..c6bcc142 100644 --- a/internal/prebuild-binaries/README.md +++ b/internal/prebuild-binaries/README.md @@ -7,9 +7,16 @@ Specifically, this builds: 1. SQLite as a static library for iOS/macOS/watchOS/tvOS (+ simulators). 2. SQLite3MultipleCiphers as a static library for iOS/macOS/watchOS/tvOS (+ simulators). +3. SQLite3MultipleCiphers plus JNI wrappers as a dynamic library for Windows, macOS and Linux. We don't want to build these assets on every build since they're included in a `cinterops` definition file, meaning that they would have to be built during Gradle sync, which slows down that process. Instead, we use a cache for GitHub actions to only recompile these when necessary. During the main build, we then use a custom property to download assets instead of recompiling. + +This build is currently configured to run on macOS hosts only. Cross-compiling requires additional dependencies: + +1. To target Windows, we use [LLVM-mingw](https://github.com/mstorsjo/llvm-mingw), which can be downloaded with the + `download_llvm_mingw.sh`. +2. To target Linux, we use clang. The `download_glibc.sh` file downloads necessary glibc headers and object files. diff --git a/internal/prebuild-binaries/build.gradle.kts b/internal/prebuild-binaries/build.gradle.kts index 2db4e0f0..1e754334 100644 --- a/internal/prebuild-binaries/build.gradle.kts +++ b/internal/prebuild-binaries/build.gradle.kts @@ -1,9 +1,13 @@ import com.powersync.compile.ClangCompile import com.powersync.compile.UnzipSqlite import de.undercouch.gradle.tasks.download.Download +import kotlin.io.path.Path import org.gradle.kotlin.dsl.register import org.jetbrains.kotlin.konan.target.KonanTarget import com.powersync.compile.CreateStaticLibrary +import com.powersync.compile.JniLibraryCompile +import com.powersync.compile.JniTarget +import kotlin.io.path.absolutePathString plugins { alias(libs.plugins.downloadPlugin) @@ -14,6 +18,13 @@ val sqlite3BaseVersion = "3.51.1" val sqlite3ReleaseYear = "2025" val sqlite3ExpandedVersion = "3510100" +data class CompiledAsset( + val output: Provider, + val fullName: String, +) + +val xCodeInstallation = ClangCompile.resolveXcode(providers) + val downloadSQLiteSources by tasks.registering(Download::class) { val zipFileName = "sqlite-amalgamation-$sqlite3ExpandedVersion.zip" src("https://www.sqlite.org/$sqlite3ReleaseYear/$zipFileName") @@ -51,6 +62,55 @@ val unzipSqlite3MultipleCipherSources by tasks.registering(UnzipSqlite::class) { ) } +val prepareAndroidBuild by tasks.registering(Copy::class) { + from(unzipSqlite3MultipleCipherSources.flatMap { it.destination }) { + include( + "sqlite3mc_amalgamation.c", + "sqlite3mc_amalgamation.h", + "sqlite3.h" + ) + } + + from("jni/CMakeLists.txt") + from("jni/sqlite_bindings.cpp") + into(layout.buildDirectory.dir("android")) +} + +fun compileJni(target: JniTarget): CompiledAsset { + val name = target.filename("sqlite3mc_jni") + + val task = tasks.register("compile${target.name}") { + this.target.set(target) + inputFiles.from( + "jni/sqlite_bindings.cpp", + unzipSqlite3MultipleCipherSources.flatMap { it.destination.file("sqlite3mc_amalgamation.c") } + ) + include.set(unzipSqlite3MultipleCipherSources.flatMap { it.destination }) + sharedLibrary.set(layout.buildDirectory.file("jni/$name")) + + when (target) { + JniTarget.LINUX_X64, JniTarget.LINUX_ARM -> {} + JniTarget.WINDOWS_X64, JniTarget.WINDOWS_ARM -> { + // For Windows, we compile with LLVM MinGW: https://github.com/mstorsjo/llvm-mingw + val clang = layout.buildDirectory.file("llvm-mingw/bin/clang").map { it.asFile.path } + clangPath.set(clang) + } + JniTarget.MACOS_X64, JniTarget.MACOS_ARM -> { + // on macOS: Compile with xcode tools + toolchain.set(xCodeInstallation.map { + val xcode = Path(it) + xcode.resolve("Toolchains/XcodeDefault.xctoolchain/usr/bin").absolutePathString() + }) + } + } + } + + return CompiledAsset( + output = task.map { it.sharedLibrary }, + fullName = name + ) +} + fun compileSqliteForKotlinNativeOnApple(library: String, abi: String): TaskProvider { val name = "$library$abi" val outputDir = layout.buildDirectory.dir("c/$abi") @@ -66,7 +126,7 @@ fun compileSqliteForKotlinNativeOnApple(library: String, abi: String): TaskProvi } inputs.dir(sourceTask.map { it.destination }) - include.set(unzipSQLiteSources.flatMap { it.destination }) + include.set(sourceTask.flatMap { it.destination }) inputFile.set(sourceTask.flatMap { it.destination.file(filename) }) konanTarget.set(abi) @@ -82,12 +142,7 @@ fun compileSqliteForKotlinNativeOnApple(library: String, abi: String): TaskProvi return createStaticLibrary } -data class CompiledAsset( - val output: Provider, - val fullName: String, -) - -val compileTasks = buildList { +val kotlinNativeCompileTasks = buildList { val targets = KonanTarget.predefinedTargets.values.filter { it.family.isAppleFamily }.map { it.name }.toList() for (library in listOf("sqlite3", "sqlite3mc")) { for (abi in targets) { @@ -101,27 +156,61 @@ val compileTasks = buildList { } val compileNative by tasks.registering(Copy::class) { - into(project.layout.buildDirectory.dir("output")) + into(project.layout.buildDirectory.dir("output/static")) - for (task in compileTasks) { + for (task in kotlinNativeCompileTasks) { from(task.output) { rename { task.fullName } } } } +val jniCompileTasks: Map = buildMap { + for (target in JniTarget.entries) { + put(target, compileJni(target)) + } +} + +val compileJni by tasks.registering(Copy::class) { + into(project.layout.buildDirectory.dir("output/jni")) + + for (task in jniCompileTasks.values) { + from(task.output) { + rename { task.fullName } + } + } +} + +val compileAll by tasks.registering { + dependsOn(compileNative) + dependsOn(compileJni) +} + val hasPrebuiltAssets = providers.gradleProperty("hasPrebuiltAssets").map { it.toBooleanStrict() } val nativeSqliteConfiguration by configurations.creating { isCanBeResolved = false } +val jniSqlite3McConfiguration by configurations.creating { + isCanBeResolved = false +} +val androidBuildSourceConfiguration by configurations.creating { + // We share the downloaded sqlite3mc sources with the sqlite3multipleciphers project, which uses it to + // setup a cmake-based NDK build. Since these work on all platforms and only run when needed, there's no + // need to prebuild them. + isCanBeResolved = false +} artifacts { if (hasPrebuiltAssets.getOrElse(false)) { // In CI builds, we set hasPrebuiltAssets=true. In that case, contents of build/output have been downloaded from // cache and don't need to be rebuilt. - add(nativeSqliteConfiguration.name, layout.buildDirectory.dir("output")) + add(nativeSqliteConfiguration.name, layout.buildDirectory.dir("output/static")) + add(jniSqlite3McConfiguration.name, layout.buildDirectory.dir("output/jni")) } else { add(nativeSqliteConfiguration.name, compileNative) + add(jniSqlite3McConfiguration.name, compileJni) } + + add(androidBuildSourceConfiguration.name, prepareAndroidBuild) } diff --git a/internal/prebuild-binaries/download_glibc.sh b/internal/prebuild-binaries/download_glibc.sh new file mode 100755 index 00000000..59936175 --- /dev/null +++ b/internal/prebuild-binaries/download_glibc.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +mkdir -p build/sysroot +cd build/sysroot + +function download_package() { + curl -L $1 | tar --extract --gzip +} + +download_package https://archlinux.org/packages/core/x86_64/glibc/download/ +download_package https://archlinux.org/packages/core/x86_64/linux-api-headers/download/ +download_package https://archlinux.org/packages/core/x86_64/gcc/download/ +download_package https://archlinux.org/packages/core/x86_64/gcc-libs/download/ + +download_package https://archlinux.org/packages/extra/any/aarch64-linux-gnu-glibc/download/ +download_package https://archlinux.org/packages/extra/any/aarch64-linux-gnu-linux-api-headers/download/ +download_package https://archlinux.org/packages/extra/x86_64/aarch64-linux-gnu-gcc/download/ diff --git a/internal/prebuild-binaries/download_llvm_mingw.sh b/internal/prebuild-binaries/download_llvm_mingw.sh new file mode 100755 index 00000000..d650226b --- /dev/null +++ b/internal/prebuild-binaries/download_llvm_mingw.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +mkdir -p build +cd build/ + +curl -L https://github.com/mstorsjo/llvm-mingw/releases/download/20251118/llvm-mingw-20251118-ucrt-macos-universal.tar.xz -o llvm-ming.tar.xz +tar --extract --gzip --file llvm-ming.tar.xz +rm llvm-ming.tar.xz + +mv llvm-mingw-20251118-ucrt-macos-universal llvm-mingw diff --git a/internal/prebuild-binaries/jni/CMakeLists.txt b/internal/prebuild-binaries/jni/CMakeLists.txt new file mode 100644 index 00000000..42f4c197 --- /dev/null +++ b/internal/prebuild-binaries/jni/CMakeLists.txt @@ -0,0 +1,24 @@ +# NOTE! This file is not used as-is. It gets copied into a shared folder with sources by the +# prepareAndroidBuild task. + +cmake_minimum_required(VERSION 3.14) +project( + powersync_sqlite3mc_bundled + VERSION 3 + LANGUAGES C CXX +) + +set(CMAKE_C_FLAGS "-O3") + +add_library(sqlite3mc_bundled SHARED "sqlite3mc_amalgamation.c" "sqlite_bindings.cpp") + +# Note: Keep in sync with the ClangCompile task used for static-sqlite-driver +target_compile_definitions(sqlite3mc_bundled PUBLIC + HAVE_GETHOSTUUID=0 + SQLITE_ENABLE_DBSTAT_VTAB + SQLITE_ENABLE_FTS5 + SQLITE_ENABLE_RTREE + SQLITE_ENABLE_SNAPSHOT + SQLITE_ENABLE_SESSION + SQLITE_ENABLE_PREUPDATE_HOOK +) diff --git a/internal/prebuild-binaries/jni/headers/common/jni.h b/internal/prebuild-binaries/jni/headers/common/jni.h new file mode 100644 index 00000000..cff1a02f --- /dev/null +++ b/internal/prebuild-binaries/jni/headers/common/jni.h @@ -0,0 +1,1961 @@ +/* + * @(#)jni.h 1.62 06/02/02 + * + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +/* + * We used part of Netscape's Java Runtime Interface (JRI) as the starting + * point of our design and implementation. + */ + +/****************************************************************************** + * Java Runtime Interface + * Copyright (c) 1996 Netscape Communications Corporation. All rights reserved. + *****************************************************************************/ + +#ifndef _JAVASOFT_JNI_H_ +#define _JAVASOFT_JNI_H_ + +#include +#include + +/* jni_md.h contains the machine-dependent typedefs for jbyte, jint + and jlong */ + +#include "jni_md.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * JNI Types + */ + +#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H + +typedef unsigned char jboolean; +typedef unsigned short jchar; +typedef short jshort; +typedef float jfloat; +typedef double jdouble; + +typedef jint jsize; + +#ifdef __cplusplus + +class _jobject {}; +class _jclass : public _jobject {}; +class _jthrowable : public _jobject {}; +class _jstring : public _jobject {}; +class _jarray : public _jobject {}; +class _jbooleanArray : public _jarray {}; +class _jbyteArray : public _jarray {}; +class _jcharArray : public _jarray {}; +class _jshortArray : public _jarray {}; +class _jintArray : public _jarray {}; +class _jlongArray : public _jarray {}; +class _jfloatArray : public _jarray {}; +class _jdoubleArray : public _jarray {}; +class _jobjectArray : public _jarray {}; + +typedef _jobject *jobject; +typedef _jclass *jclass; +typedef _jthrowable *jthrowable; +typedef _jstring *jstring; +typedef _jarray *jarray; +typedef _jbooleanArray *jbooleanArray; +typedef _jbyteArray *jbyteArray; +typedef _jcharArray *jcharArray; +typedef _jshortArray *jshortArray; +typedef _jintArray *jintArray; +typedef _jlongArray *jlongArray; +typedef _jfloatArray *jfloatArray; +typedef _jdoubleArray *jdoubleArray; +typedef _jobjectArray *jobjectArray; + +#else + +struct _jobject; + +typedef struct _jobject *jobject; +typedef jobject jclass; +typedef jobject jthrowable; +typedef jobject jstring; +typedef jobject jarray; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jarray jobjectArray; + +#endif + +typedef jobject jweak; + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +struct _jfieldID; +typedef struct _jfieldID *jfieldID; + +struct _jmethodID; +typedef struct _jmethodID *jmethodID; + +/* Return values from jobjectRefType */ +typedef enum _jobjectType { + JNIInvalidRefType = 0, + JNILocalRefType = 1, + JNIGlobalRefType = 2, + JNIWeakGlobalRefType = 3 +} jobjectRefType; + + +#endif /* JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H */ + +/* + * jboolean constants + */ + +#define JNI_FALSE 0 +#define JNI_TRUE 1 + +/* + * possible return values for JNI functions. + */ + +#define JNI_OK 0 /* success */ +#define JNI_ERR (-1) /* unknown error */ +#define JNI_EDETACHED (-2) /* thread detached from the VM */ +#define JNI_EVERSION (-3) /* JNI version error */ +#define JNI_ENOMEM (-4) /* not enough memory */ +#define JNI_EEXIST (-5) /* VM already created */ +#define JNI_EINVAL (-6) /* invalid arguments */ + +/* + * used in ReleaseScalarArrayElements + */ + +#define JNI_COMMIT 1 +#define JNI_ABORT 2 + +/* + * used in RegisterNatives to describe native method name, signature, + * and function pointer. + */ + +typedef struct { + char *name; + char *signature; + void *fnPtr; +} JNINativeMethod; + +/* + * JNI Native Method Interface. + */ + +struct JNINativeInterface_; + +struct JNIEnv_; + +#ifdef __cplusplus +typedef JNIEnv_ JNIEnv; +#else +typedef const struct JNINativeInterface_ *JNIEnv; +#endif + +/* + * JNI Invocation Interface. + */ + +struct JNIInvokeInterface_; + +struct JavaVM_; + +#ifdef __cplusplus +typedef JavaVM_ JavaVM; +#else +typedef const struct JNIInvokeInterface_ *JavaVM; +#endif + +struct JNINativeInterface_ { + void *reserved0; + void *reserved1; + void *reserved2; + + void *reserved3; + +#if !TARGET_RT_MAC_CFM && defined(__ppc__) + void* cfm_vectors[225]; +#endif /* !TARGET_RT_MAC_CFM && defined(__ppc__) */ + + jint (JNICALL *GetVersion)(JNIEnv *env); + + jclass (JNICALL *DefineClass) + (JNIEnv *env, const char *name, jobject loader, const jbyte *buf, + jsize len); + jclass (JNICALL *FindClass) + (JNIEnv *env, const char *name); + + jmethodID (JNICALL *FromReflectedMethod) + (JNIEnv *env, jobject method); + jfieldID (JNICALL *FromReflectedField) + (JNIEnv *env, jobject field); + + jobject (JNICALL *ToReflectedMethod) + (JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic); + + jclass (JNICALL *GetSuperclass) + (JNIEnv *env, jclass sub); + jboolean (JNICALL *IsAssignableFrom) + (JNIEnv *env, jclass sub, jclass sup); + + jobject (JNICALL *ToReflectedField) + (JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic); + + jint (JNICALL *Throw) + (JNIEnv *env, jthrowable obj); + jint (JNICALL *ThrowNew) + (JNIEnv *env, jclass clazz, const char *msg); + jthrowable (JNICALL *ExceptionOccurred) + (JNIEnv *env); + void (JNICALL *ExceptionDescribe) + (JNIEnv *env); + void (JNICALL *ExceptionClear) + (JNIEnv *env); + void (JNICALL *FatalError) + (JNIEnv *env, const char *msg); + + jint (JNICALL *PushLocalFrame) + (JNIEnv *env, jint capacity); + jobject (JNICALL *PopLocalFrame) + (JNIEnv *env, jobject result); + + jobject (JNICALL *NewGlobalRef) + (JNIEnv *env, jobject lobj); + void (JNICALL *DeleteGlobalRef) + (JNIEnv *env, jobject gref); + void (JNICALL *DeleteLocalRef) + (JNIEnv *env, jobject obj); + jboolean (JNICALL *IsSameObject) + (JNIEnv *env, jobject obj1, jobject obj2); + jobject (JNICALL *NewLocalRef) + (JNIEnv *env, jobject ref); + jint (JNICALL *EnsureLocalCapacity) + (JNIEnv *env, jint capacity); + + jobject (JNICALL *AllocObject) + (JNIEnv *env, jclass clazz); + jobject (JNICALL *NewObject) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *NewObjectV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jobject (JNICALL *NewObjectA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jclass (JNICALL *GetObjectClass) + (JNIEnv *env, jobject obj); + jboolean (JNICALL *IsInstanceOf) + (JNIEnv *env, jobject obj, jclass clazz); + + jmethodID (JNICALL *GetMethodID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *CallObjectMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jobject (JNICALL *CallObjectMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jobject (JNICALL *CallObjectMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jboolean (JNICALL *CallBooleanMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jboolean (JNICALL *CallBooleanMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jboolean (JNICALL *CallBooleanMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jbyte (JNICALL *CallByteMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jbyte (JNICALL *CallByteMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jbyte (JNICALL *CallByteMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jchar (JNICALL *CallCharMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jchar (JNICALL *CallCharMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jchar (JNICALL *CallCharMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jshort (JNICALL *CallShortMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jshort (JNICALL *CallShortMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jshort (JNICALL *CallShortMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jint (JNICALL *CallIntMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jint (JNICALL *CallIntMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jint (JNICALL *CallIntMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jlong (JNICALL *CallLongMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jlong (JNICALL *CallLongMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jlong (JNICALL *CallLongMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jfloat (JNICALL *CallFloatMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jfloat (JNICALL *CallFloatMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jfloat (JNICALL *CallFloatMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jdouble (JNICALL *CallDoubleMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + jdouble (JNICALL *CallDoubleMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jdouble (JNICALL *CallDoubleMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + void (JNICALL *CallVoidMethod) + (JNIEnv *env, jobject obj, jmethodID methodID, ...); + void (JNICALL *CallVoidMethodV) + (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + void (JNICALL *CallVoidMethodA) + (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jobject (JNICALL *CallNonvirtualObjectMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *CallNonvirtualObjectMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jobject (JNICALL *CallNonvirtualObjectMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue * args); + + jboolean (JNICALL *CallNonvirtualBooleanMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jboolean (JNICALL *CallNonvirtualBooleanMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jboolean (JNICALL *CallNonvirtualBooleanMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue * args); + + jbyte (JNICALL *CallNonvirtualByteMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jbyte (JNICALL *CallNonvirtualByteMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jbyte (JNICALL *CallNonvirtualByteMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jchar (JNICALL *CallNonvirtualCharMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jchar (JNICALL *CallNonvirtualCharMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jchar (JNICALL *CallNonvirtualCharMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jshort (JNICALL *CallNonvirtualShortMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jshort (JNICALL *CallNonvirtualShortMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jshort (JNICALL *CallNonvirtualShortMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jint (JNICALL *CallNonvirtualIntMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jint (JNICALL *CallNonvirtualIntMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jint (JNICALL *CallNonvirtualIntMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jlong (JNICALL *CallNonvirtualLongMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jlong (JNICALL *CallNonvirtualLongMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jlong (JNICALL *CallNonvirtualLongMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jfloat (JNICALL *CallNonvirtualFloatMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jfloat (JNICALL *CallNonvirtualFloatMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jfloat (JNICALL *CallNonvirtualFloatMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + jdouble (JNICALL *CallNonvirtualDoubleMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jdouble (JNICALL *CallNonvirtualDoubleMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + jdouble (JNICALL *CallNonvirtualDoubleMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + void (JNICALL *CallNonvirtualVoidMethod) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + void (JNICALL *CallNonvirtualVoidMethodV) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + va_list args); + void (JNICALL *CallNonvirtualVoidMethodA) + (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue * args); + + jfieldID (JNICALL *GetFieldID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *GetObjectField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jboolean (JNICALL *GetBooleanField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jbyte (JNICALL *GetByteField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jchar (JNICALL *GetCharField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jshort (JNICALL *GetShortField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jint (JNICALL *GetIntField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jlong (JNICALL *GetLongField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jfloat (JNICALL *GetFloatField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + jdouble (JNICALL *GetDoubleField) + (JNIEnv *env, jobject obj, jfieldID fieldID); + + void (JNICALL *SetObjectField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jobject val); + void (JNICALL *SetBooleanField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jboolean val); + void (JNICALL *SetByteField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jbyte val); + void (JNICALL *SetCharField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jchar val); + void (JNICALL *SetShortField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jshort val); + void (JNICALL *SetIntField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jint val); + void (JNICALL *SetLongField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jlong val); + void (JNICALL *SetFloatField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jfloat val); + void (JNICALL *SetDoubleField) + (JNIEnv *env, jobject obj, jfieldID fieldID, jdouble val); + + jmethodID (JNICALL *GetStaticMethodID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *CallStaticObjectMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *CallStaticObjectMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jobject (JNICALL *CallStaticObjectMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jboolean (JNICALL *CallStaticBooleanMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jboolean (JNICALL *CallStaticBooleanMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jboolean (JNICALL *CallStaticBooleanMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jbyte (JNICALL *CallStaticByteMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jbyte (JNICALL *CallStaticByteMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jbyte (JNICALL *CallStaticByteMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jchar (JNICALL *CallStaticCharMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jchar (JNICALL *CallStaticCharMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jchar (JNICALL *CallStaticCharMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jshort (JNICALL *CallStaticShortMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jshort (JNICALL *CallStaticShortMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jshort (JNICALL *CallStaticShortMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jint (JNICALL *CallStaticIntMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jint (JNICALL *CallStaticIntMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jint (JNICALL *CallStaticIntMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jlong (JNICALL *CallStaticLongMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jlong (JNICALL *CallStaticLongMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jlong (JNICALL *CallStaticLongMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jfloat (JNICALL *CallStaticFloatMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jfloat (JNICALL *CallStaticFloatMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jfloat (JNICALL *CallStaticFloatMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jdouble (JNICALL *CallStaticDoubleMethod) + (JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jdouble (JNICALL *CallStaticDoubleMethodV) + (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jdouble (JNICALL *CallStaticDoubleMethodA) + (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + void (JNICALL *CallStaticVoidMethod) + (JNIEnv *env, jclass cls, jmethodID methodID, ...); + void (JNICALL *CallStaticVoidMethodV) + (JNIEnv *env, jclass cls, jmethodID methodID, va_list args); + void (JNICALL *CallStaticVoidMethodA) + (JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args); + + jfieldID (JNICALL *GetStaticFieldID) + (JNIEnv *env, jclass clazz, const char *name, const char *sig); + jobject (JNICALL *GetStaticObjectField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jboolean (JNICALL *GetStaticBooleanField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jbyte (JNICALL *GetStaticByteField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jchar (JNICALL *GetStaticCharField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jshort (JNICALL *GetStaticShortField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jint (JNICALL *GetStaticIntField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jlong (JNICALL *GetStaticLongField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jfloat (JNICALL *GetStaticFloatField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + jdouble (JNICALL *GetStaticDoubleField) + (JNIEnv *env, jclass clazz, jfieldID fieldID); + + void (JNICALL *SetStaticObjectField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jobject value); + void (JNICALL *SetStaticBooleanField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jboolean value); + void (JNICALL *SetStaticByteField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jbyte value); + void (JNICALL *SetStaticCharField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jchar value); + void (JNICALL *SetStaticShortField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jshort value); + void (JNICALL *SetStaticIntField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jint value); + void (JNICALL *SetStaticLongField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jlong value); + void (JNICALL *SetStaticFloatField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jfloat value); + void (JNICALL *SetStaticDoubleField) + (JNIEnv *env, jclass clazz, jfieldID fieldID, jdouble value); + + jstring (JNICALL *NewString) + (JNIEnv *env, const jchar *unicode, jsize len); + jsize (JNICALL *GetStringLength) + (JNIEnv *env, jstring str); + const jchar *(JNICALL *GetStringChars) + (JNIEnv *env, jstring str, jboolean *isCopy); + void (JNICALL *ReleaseStringChars) + (JNIEnv *env, jstring str, const jchar *chars); + + jstring (JNICALL *NewStringUTF) + (JNIEnv *env, const char *utf); + jsize (JNICALL *GetStringUTFLength) + (JNIEnv *env, jstring str); + const char* (JNICALL *GetStringUTFChars) + (JNIEnv *env, jstring str, jboolean *isCopy); + void (JNICALL *ReleaseStringUTFChars) + (JNIEnv *env, jstring str, const char* chars); + + + jsize (JNICALL *GetArrayLength) + (JNIEnv *env, jarray array); + + jobjectArray (JNICALL *NewObjectArray) + (JNIEnv *env, jsize len, jclass clazz, jobject init); + jobject (JNICALL *GetObjectArrayElement) + (JNIEnv *env, jobjectArray array, jsize index); + void (JNICALL *SetObjectArrayElement) + (JNIEnv *env, jobjectArray array, jsize index, jobject val); + + jbooleanArray (JNICALL *NewBooleanArray) + (JNIEnv *env, jsize len); + jbyteArray (JNICALL *NewByteArray) + (JNIEnv *env, jsize len); + jcharArray (JNICALL *NewCharArray) + (JNIEnv *env, jsize len); + jshortArray (JNICALL *NewShortArray) + (JNIEnv *env, jsize len); + jintArray (JNICALL *NewIntArray) + (JNIEnv *env, jsize len); + jlongArray (JNICALL *NewLongArray) + (JNIEnv *env, jsize len); + jfloatArray (JNICALL *NewFloatArray) + (JNIEnv *env, jsize len); + jdoubleArray (JNICALL *NewDoubleArray) + (JNIEnv *env, jsize len); + + jboolean * (JNICALL *GetBooleanArrayElements) + (JNIEnv *env, jbooleanArray array, jboolean *isCopy); + jbyte * (JNICALL *GetByteArrayElements) + (JNIEnv *env, jbyteArray array, jboolean *isCopy); + jchar * (JNICALL *GetCharArrayElements) + (JNIEnv *env, jcharArray array, jboolean *isCopy); + jshort * (JNICALL *GetShortArrayElements) + (JNIEnv *env, jshortArray array, jboolean *isCopy); + jint * (JNICALL *GetIntArrayElements) + (JNIEnv *env, jintArray array, jboolean *isCopy); + jlong * (JNICALL *GetLongArrayElements) + (JNIEnv *env, jlongArray array, jboolean *isCopy); + jfloat * (JNICALL *GetFloatArrayElements) + (JNIEnv *env, jfloatArray array, jboolean *isCopy); + jdouble * (JNICALL *GetDoubleArrayElements) + (JNIEnv *env, jdoubleArray array, jboolean *isCopy); + + void (JNICALL *ReleaseBooleanArrayElements) + (JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode); + void (JNICALL *ReleaseByteArrayElements) + (JNIEnv *env, jbyteArray array, jbyte *elems, jint mode); + void (JNICALL *ReleaseCharArrayElements) + (JNIEnv *env, jcharArray array, jchar *elems, jint mode); + void (JNICALL *ReleaseShortArrayElements) + (JNIEnv *env, jshortArray array, jshort *elems, jint mode); + void (JNICALL *ReleaseIntArrayElements) + (JNIEnv *env, jintArray array, jint *elems, jint mode); + void (JNICALL *ReleaseLongArrayElements) + (JNIEnv *env, jlongArray array, jlong *elems, jint mode); + void (JNICALL *ReleaseFloatArrayElements) + (JNIEnv *env, jfloatArray array, jfloat *elems, jint mode); + void (JNICALL *ReleaseDoubleArrayElements) + (JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode); + + void (JNICALL *GetBooleanArrayRegion) + (JNIEnv *env, jbooleanArray array, jsize start, jsize l, jboolean *buf); + void (JNICALL *GetByteArrayRegion) + (JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); + void (JNICALL *GetCharArrayRegion) + (JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); + void (JNICALL *GetShortArrayRegion) + (JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); + void (JNICALL *GetIntArrayRegion) + (JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); + void (JNICALL *GetLongArrayRegion) + (JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); + void (JNICALL *GetFloatArrayRegion) + (JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); + void (JNICALL *GetDoubleArrayRegion) + (JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf); + + void (JNICALL *SetBooleanArrayRegion) + (JNIEnv *env, jbooleanArray array, jsize start, jsize l, const jboolean *buf); + void (JNICALL *SetByteArrayRegion) + (JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte *buf); + void (JNICALL *SetCharArrayRegion) + (JNIEnv *env, jcharArray array, jsize start, jsize len, const jchar *buf); + void (JNICALL *SetShortArrayRegion) + (JNIEnv *env, jshortArray array, jsize start, jsize len, const jshort *buf); + void (JNICALL *SetIntArrayRegion) + (JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf); + void (JNICALL *SetLongArrayRegion) + (JNIEnv *env, jlongArray array, jsize start, jsize len, const jlong *buf); + void (JNICALL *SetFloatArrayRegion) + (JNIEnv *env, jfloatArray array, jsize start, jsize len, const jfloat *buf); + void (JNICALL *SetDoubleArrayRegion) + (JNIEnv *env, jdoubleArray array, jsize start, jsize len, const jdouble *buf); + + jint (JNICALL *RegisterNatives) + (JNIEnv *env, jclass clazz, const JNINativeMethod *methods, + jint nMethods); + jint (JNICALL *UnregisterNatives) + (JNIEnv *env, jclass clazz); + + jint (JNICALL *MonitorEnter) + (JNIEnv *env, jobject obj); + jint (JNICALL *MonitorExit) + (JNIEnv *env, jobject obj); + + jint (JNICALL *GetJavaVM) + (JNIEnv *env, JavaVM **vm); + + void (JNICALL *GetStringRegion) + (JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); + void (JNICALL *GetStringUTFRegion) + (JNIEnv *env, jstring str, jsize start, jsize len, char *buf); + + void * (JNICALL *GetPrimitiveArrayCritical) + (JNIEnv *env, jarray array, jboolean *isCopy); + void (JNICALL *ReleasePrimitiveArrayCritical) + (JNIEnv *env, jarray array, void *carray, jint mode); + + const jchar * (JNICALL *GetStringCritical) + (JNIEnv *env, jstring string, jboolean *isCopy); + void (JNICALL *ReleaseStringCritical) + (JNIEnv *env, jstring string, const jchar *cstring); + + jweak (JNICALL *NewWeakGlobalRef) + (JNIEnv *env, jobject obj); + void (JNICALL *DeleteWeakGlobalRef) + (JNIEnv *env, jweak ref); + + jboolean (JNICALL *ExceptionCheck) + (JNIEnv *env); + + jobject (JNICALL *NewDirectByteBuffer) + (JNIEnv* env, void* address, jlong capacity); + void* (JNICALL *GetDirectBufferAddress) + (JNIEnv* env, jobject buf); + jlong (JNICALL *GetDirectBufferCapacity) + (JNIEnv* env, jobject buf); + + /* New JNI 1.6 Features */ + + jobjectRefType (JNICALL *GetObjectRefType) + (JNIEnv* env, jobject obj); + + #if TARGET_RT_MAC_CFM && defined(__ppc__) + void* real_functions[228]; + #endif /* TARGET_RT_MAC_CFM && defined(__ppc__) */ +}; + +/* + * We use inlined functions for C++ so that programmers can write: + * + * env->FindClass("java/lang/String") + * + * in C++ rather than: + * + * (*env)->FindClass(env, "java/lang/String") + * + * in C. + */ + +struct JNIEnv_ { + const struct JNINativeInterface_ *functions; +#ifdef __cplusplus + + jint GetVersion() { + return functions->GetVersion(this); + } + jclass DefineClass(const char *name, jobject loader, const jbyte *buf, + jsize len) { + return functions->DefineClass(this, name, loader, buf, len); + } + jclass FindClass(const char *name) { + return functions->FindClass(this, name); + } + jmethodID FromReflectedMethod(jobject method) { + return functions->FromReflectedMethod(this,method); + } + jfieldID FromReflectedField(jobject field) { + return functions->FromReflectedField(this,field); + } + + jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) { + return functions->ToReflectedMethod(this, cls, methodID, isStatic); + } + + jclass GetSuperclass(jclass sub) { + return functions->GetSuperclass(this, sub); + } + jboolean IsAssignableFrom(jclass sub, jclass sup) { + return functions->IsAssignableFrom(this, sub, sup); + } + + jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) { + return functions->ToReflectedField(this,cls,fieldID,isStatic); + } + + jint Throw(jthrowable obj) { + return functions->Throw(this, obj); + } + jint ThrowNew(jclass clazz, const char *msg) { + return functions->ThrowNew(this, clazz, msg); + } + jthrowable ExceptionOccurred() { + return functions->ExceptionOccurred(this); + } + void ExceptionDescribe() { + functions->ExceptionDescribe(this); + } + void ExceptionClear() { + functions->ExceptionClear(this); + } + void FatalError(const char *msg) { + functions->FatalError(this, msg); + } + + jint PushLocalFrame(jint capacity) { + return functions->PushLocalFrame(this,capacity); + } + jobject PopLocalFrame(jobject result) { + return functions->PopLocalFrame(this,result); + } + + jobject NewGlobalRef(jobject lobj) { + return functions->NewGlobalRef(this,lobj); + } + void DeleteGlobalRef(jobject gref) { + functions->DeleteGlobalRef(this,gref); + } + void DeleteLocalRef(jobject obj) { + functions->DeleteLocalRef(this, obj); + } + + jboolean IsSameObject(jobject obj1, jobject obj2) { + return functions->IsSameObject(this,obj1,obj2); + } + + jobject NewLocalRef(jobject ref) { + return functions->NewLocalRef(this,ref); + } + jint EnsureLocalCapacity(jint capacity) { + return functions->EnsureLocalCapacity(this,capacity); + } + + jobject AllocObject(jclass clazz) { + return functions->AllocObject(this,clazz); + } + jobject NewObject(jclass clazz, jmethodID methodID, ...) { + va_list args; + jobject result; + va_start(args, methodID); + result = functions->NewObjectV(this,clazz,methodID,args); + va_end(args); + return result; + } + jobject NewObjectV(jclass clazz, jmethodID methodID, + va_list args) { + return functions->NewObjectV(this,clazz,methodID,args); + } + jobject NewObjectA(jclass clazz, jmethodID methodID, + const jvalue *args) { + return functions->NewObjectA(this,clazz,methodID,args); + } + + jclass GetObjectClass(jobject obj) { + return functions->GetObjectClass(this,obj); + } + jboolean IsInstanceOf(jobject obj, jclass clazz) { + return functions->IsInstanceOf(this,obj,clazz); + } + + jmethodID GetMethodID(jclass clazz, const char *name, + const char *sig) { + return functions->GetMethodID(this,clazz,name,sig); + } + + jobject CallObjectMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jobject result; + va_start(args,methodID); + result = functions->CallObjectMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jobject CallObjectMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallObjectMethodV(this,obj,methodID,args); + } + jobject CallObjectMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallObjectMethodA(this,obj,methodID,args); + } + + jboolean CallBooleanMethod(jobject obj, + jmethodID methodID, ...) { + va_list args; + jboolean result; + va_start(args,methodID); + result = functions->CallBooleanMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jboolean CallBooleanMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallBooleanMethodV(this,obj,methodID,args); + } + jboolean CallBooleanMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallBooleanMethodA(this,obj,methodID, args); + } + + jbyte CallByteMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jbyte result; + va_start(args,methodID); + result = functions->CallByteMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jbyte CallByteMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallByteMethodV(this,obj,methodID,args); + } + jbyte CallByteMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallByteMethodA(this,obj,methodID,args); + } + + jchar CallCharMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jchar result; + va_start(args,methodID); + result = functions->CallCharMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jchar CallCharMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallCharMethodV(this,obj,methodID,args); + } + jchar CallCharMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallCharMethodA(this,obj,methodID,args); + } + + jshort CallShortMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jshort result; + va_start(args,methodID); + result = functions->CallShortMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jshort CallShortMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallShortMethodV(this,obj,methodID,args); + } + jshort CallShortMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallShortMethodA(this,obj,methodID,args); + } + + jint CallIntMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jint result; + va_start(args,methodID); + result = functions->CallIntMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jint CallIntMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallIntMethodV(this,obj,methodID,args); + } + jint CallIntMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallIntMethodA(this,obj,methodID,args); + } + + jlong CallLongMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jlong result; + va_start(args,methodID); + result = functions->CallLongMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jlong CallLongMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallLongMethodV(this,obj,methodID,args); + } + jlong CallLongMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallLongMethodA(this,obj,methodID,args); + } + + jfloat CallFloatMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jfloat result; + va_start(args,methodID); + result = functions->CallFloatMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jfloat CallFloatMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallFloatMethodV(this,obj,methodID,args); + } + jfloat CallFloatMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallFloatMethodA(this,obj,methodID,args); + } + + jdouble CallDoubleMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + jdouble result; + va_start(args,methodID); + result = functions->CallDoubleMethodV(this,obj,methodID,args); + va_end(args); + return result; + } + jdouble CallDoubleMethodV(jobject obj, jmethodID methodID, + va_list args) { + return functions->CallDoubleMethodV(this,obj,methodID,args); + } + jdouble CallDoubleMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + return functions->CallDoubleMethodA(this,obj,methodID,args); + } + + void CallVoidMethod(jobject obj, jmethodID methodID, ...) { + va_list args; + va_start(args,methodID); + functions->CallVoidMethodV(this,obj,methodID,args); + va_end(args); + } + void CallVoidMethodV(jobject obj, jmethodID methodID, + va_list args) { + functions->CallVoidMethodV(this,obj,methodID,args); + } + void CallVoidMethodA(jobject obj, jmethodID methodID, + const jvalue * args) { + functions->CallVoidMethodA(this,obj,methodID,args); + } + + jobject CallNonvirtualObjectMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jobject result; + va_start(args,methodID); + result = functions->CallNonvirtualObjectMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jobject CallNonvirtualObjectMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualObjectMethodV(this,obj,clazz, + methodID,args); + } + jobject CallNonvirtualObjectMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualObjectMethodA(this,obj,clazz, + methodID,args); + } + + jboolean CallNonvirtualBooleanMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jboolean result; + va_start(args,methodID); + result = functions->CallNonvirtualBooleanMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jboolean CallNonvirtualBooleanMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualBooleanMethodV(this,obj,clazz, + methodID,args); + } + jboolean CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualBooleanMethodA(this,obj,clazz, + methodID, args); + } + + jbyte CallNonvirtualByteMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jbyte result; + va_start(args,methodID); + result = functions->CallNonvirtualByteMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jbyte CallNonvirtualByteMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualByteMethodV(this,obj,clazz, + methodID,args); + } + jbyte CallNonvirtualByteMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualByteMethodA(this,obj,clazz, + methodID,args); + } + + jchar CallNonvirtualCharMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jchar result; + va_start(args,methodID); + result = functions->CallNonvirtualCharMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jchar CallNonvirtualCharMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualCharMethodV(this,obj,clazz, + methodID,args); + } + jchar CallNonvirtualCharMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualCharMethodA(this,obj,clazz, + methodID,args); + } + + jshort CallNonvirtualShortMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jshort result; + va_start(args,methodID); + result = functions->CallNonvirtualShortMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jshort CallNonvirtualShortMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualShortMethodV(this,obj,clazz, + methodID,args); + } + jshort CallNonvirtualShortMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualShortMethodA(this,obj,clazz, + methodID,args); + } + + jint CallNonvirtualIntMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jint result; + va_start(args,methodID); + result = functions->CallNonvirtualIntMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jint CallNonvirtualIntMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualIntMethodV(this,obj,clazz, + methodID,args); + } + jint CallNonvirtualIntMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualIntMethodA(this,obj,clazz, + methodID,args); + } + + jlong CallNonvirtualLongMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jlong result; + va_start(args,methodID); + result = functions->CallNonvirtualLongMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jlong CallNonvirtualLongMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallNonvirtualLongMethodV(this,obj,clazz, + methodID,args); + } + jlong CallNonvirtualLongMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue * args) { + return functions->CallNonvirtualLongMethodA(this,obj,clazz, + methodID,args); + } + + jfloat CallNonvirtualFloatMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jfloat result; + va_start(args,methodID); + result = functions->CallNonvirtualFloatMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jfloat CallNonvirtualFloatMethodV(jobject obj, jclass clazz, + jmethodID methodID, + va_list args) { + return functions->CallNonvirtualFloatMethodV(this,obj,clazz, + methodID,args); + } + jfloat CallNonvirtualFloatMethodA(jobject obj, jclass clazz, + jmethodID methodID, + const jvalue * args) { + return functions->CallNonvirtualFloatMethodA(this,obj,clazz, + methodID,args); + } + + jdouble CallNonvirtualDoubleMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + jdouble result; + va_start(args,methodID); + result = functions->CallNonvirtualDoubleMethodV(this,obj,clazz, + methodID,args); + va_end(args); + return result; + } + jdouble CallNonvirtualDoubleMethodV(jobject obj, jclass clazz, + jmethodID methodID, + va_list args) { + return functions->CallNonvirtualDoubleMethodV(this,obj,clazz, + methodID,args); + } + jdouble CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, + jmethodID methodID, + const jvalue * args) { + return functions->CallNonvirtualDoubleMethodA(this,obj,clazz, + methodID,args); + } + + void CallNonvirtualVoidMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) { + va_list args; + va_start(args,methodID); + functions->CallNonvirtualVoidMethodV(this,obj,clazz,methodID,args); + va_end(args); + } + void CallNonvirtualVoidMethodV(jobject obj, jclass clazz, + jmethodID methodID, + va_list args) { + functions->CallNonvirtualVoidMethodV(this,obj,clazz,methodID,args); + } + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, + jmethodID methodID, + const jvalue * args) { + functions->CallNonvirtualVoidMethodA(this,obj,clazz,methodID,args); + } + + jfieldID GetFieldID(jclass clazz, const char *name, + const char *sig) { + return functions->GetFieldID(this,clazz,name,sig); + } + + jobject GetObjectField(jobject obj, jfieldID fieldID) { + return functions->GetObjectField(this,obj,fieldID); + } + jboolean GetBooleanField(jobject obj, jfieldID fieldID) { + return functions->GetBooleanField(this,obj,fieldID); + } + jbyte GetByteField(jobject obj, jfieldID fieldID) { + return functions->GetByteField(this,obj,fieldID); + } + jchar GetCharField(jobject obj, jfieldID fieldID) { + return functions->GetCharField(this,obj,fieldID); + } + jshort GetShortField(jobject obj, jfieldID fieldID) { + return functions->GetShortField(this,obj,fieldID); + } + jint GetIntField(jobject obj, jfieldID fieldID) { + return functions->GetIntField(this,obj,fieldID); + } + jlong GetLongField(jobject obj, jfieldID fieldID) { + return functions->GetLongField(this,obj,fieldID); + } + jfloat GetFloatField(jobject obj, jfieldID fieldID) { + return functions->GetFloatField(this,obj,fieldID); + } + jdouble GetDoubleField(jobject obj, jfieldID fieldID) { + return functions->GetDoubleField(this,obj,fieldID); + } + + void SetObjectField(jobject obj, jfieldID fieldID, jobject val) { + functions->SetObjectField(this,obj,fieldID,val); + } + void SetBooleanField(jobject obj, jfieldID fieldID, + jboolean val) { + functions->SetBooleanField(this,obj,fieldID,val); + } + void SetByteField(jobject obj, jfieldID fieldID, + jbyte val) { + functions->SetByteField(this,obj,fieldID,val); + } + void SetCharField(jobject obj, jfieldID fieldID, + jchar val) { + functions->SetCharField(this,obj,fieldID,val); + } + void SetShortField(jobject obj, jfieldID fieldID, + jshort val) { + functions->SetShortField(this,obj,fieldID,val); + } + void SetIntField(jobject obj, jfieldID fieldID, + jint val) { + functions->SetIntField(this,obj,fieldID,val); + } + void SetLongField(jobject obj, jfieldID fieldID, + jlong val) { + functions->SetLongField(this,obj,fieldID,val); + } + void SetFloatField(jobject obj, jfieldID fieldID, + jfloat val) { + functions->SetFloatField(this,obj,fieldID,val); + } + void SetDoubleField(jobject obj, jfieldID fieldID, + jdouble val) { + functions->SetDoubleField(this,obj,fieldID,val); + } + + jmethodID GetStaticMethodID(jclass clazz, const char *name, + const char *sig) { + return functions->GetStaticMethodID(this,clazz,name,sig); + } + + jobject CallStaticObjectMethod(jclass clazz, jmethodID methodID, + ...) { + va_list args; + jobject result; + va_start(args,methodID); + result = functions->CallStaticObjectMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jobject CallStaticObjectMethodV(jclass clazz, jmethodID methodID, + va_list args) { + return functions->CallStaticObjectMethodV(this,clazz,methodID,args); + } + jobject CallStaticObjectMethodA(jclass clazz, jmethodID methodID, + const jvalue *args) { + return functions->CallStaticObjectMethodA(this,clazz,methodID,args); + } + + jboolean CallStaticBooleanMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jboolean result; + va_start(args,methodID); + result = functions->CallStaticBooleanMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jboolean CallStaticBooleanMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticBooleanMethodV(this,clazz,methodID,args); + } + jboolean CallStaticBooleanMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticBooleanMethodA(this,clazz,methodID,args); + } + + jbyte CallStaticByteMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jbyte result; + va_start(args,methodID); + result = functions->CallStaticByteMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jbyte CallStaticByteMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticByteMethodV(this,clazz,methodID,args); + } + jbyte CallStaticByteMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticByteMethodA(this,clazz,methodID,args); + } + + jchar CallStaticCharMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jchar result; + va_start(args,methodID); + result = functions->CallStaticCharMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jchar CallStaticCharMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticCharMethodV(this,clazz,methodID,args); + } + jchar CallStaticCharMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticCharMethodA(this,clazz,methodID,args); + } + + jshort CallStaticShortMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jshort result; + va_start(args,methodID); + result = functions->CallStaticShortMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jshort CallStaticShortMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticShortMethodV(this,clazz,methodID,args); + } + jshort CallStaticShortMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticShortMethodA(this,clazz,methodID,args); + } + + jint CallStaticIntMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jint result; + va_start(args,methodID); + result = functions->CallStaticIntMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jint CallStaticIntMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticIntMethodV(this,clazz,methodID,args); + } + jint CallStaticIntMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticIntMethodA(this,clazz,methodID,args); + } + + jlong CallStaticLongMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jlong result; + va_start(args,methodID); + result = functions->CallStaticLongMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jlong CallStaticLongMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticLongMethodV(this,clazz,methodID,args); + } + jlong CallStaticLongMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticLongMethodA(this,clazz,methodID,args); + } + + jfloat CallStaticFloatMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jfloat result; + va_start(args,methodID); + result = functions->CallStaticFloatMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jfloat CallStaticFloatMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticFloatMethodV(this,clazz,methodID,args); + } + jfloat CallStaticFloatMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticFloatMethodA(this,clazz,methodID,args); + } + + jdouble CallStaticDoubleMethod(jclass clazz, + jmethodID methodID, ...) { + va_list args; + jdouble result; + va_start(args,methodID); + result = functions->CallStaticDoubleMethodV(this,clazz,methodID,args); + va_end(args); + return result; + } + jdouble CallStaticDoubleMethodV(jclass clazz, + jmethodID methodID, va_list args) { + return functions->CallStaticDoubleMethodV(this,clazz,methodID,args); + } + jdouble CallStaticDoubleMethodA(jclass clazz, + jmethodID methodID, const jvalue *args) { + return functions->CallStaticDoubleMethodA(this,clazz,methodID,args); + } + + void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...) { + va_list args; + va_start(args,methodID); + functions->CallStaticVoidMethodV(this,cls,methodID,args); + va_end(args); + } + void CallStaticVoidMethodV(jclass cls, jmethodID methodID, + va_list args) { + functions->CallStaticVoidMethodV(this,cls,methodID,args); + } + void CallStaticVoidMethodA(jclass cls, jmethodID methodID, + const jvalue * args) { + functions->CallStaticVoidMethodA(this,cls,methodID,args); + } + + jfieldID GetStaticFieldID(jclass clazz, const char *name, + const char *sig) { + return functions->GetStaticFieldID(this,clazz,name,sig); + } + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticObjectField(this,clazz,fieldID); + } + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticBooleanField(this,clazz,fieldID); + } + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticByteField(this,clazz,fieldID); + } + jchar GetStaticCharField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticCharField(this,clazz,fieldID); + } + jshort GetStaticShortField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticShortField(this,clazz,fieldID); + } + jint GetStaticIntField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticIntField(this,clazz,fieldID); + } + jlong GetStaticLongField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticLongField(this,clazz,fieldID); + } + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticFloatField(this,clazz,fieldID); + } + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID) { + return functions->GetStaticDoubleField(this,clazz,fieldID); + } + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, + jobject value) { + functions->SetStaticObjectField(this,clazz,fieldID,value); + } + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, + jboolean value) { + functions->SetStaticBooleanField(this,clazz,fieldID,value); + } + void SetStaticByteField(jclass clazz, jfieldID fieldID, + jbyte value) { + functions->SetStaticByteField(this,clazz,fieldID,value); + } + void SetStaticCharField(jclass clazz, jfieldID fieldID, + jchar value) { + functions->SetStaticCharField(this,clazz,fieldID,value); + } + void SetStaticShortField(jclass clazz, jfieldID fieldID, + jshort value) { + functions->SetStaticShortField(this,clazz,fieldID,value); + } + void SetStaticIntField(jclass clazz, jfieldID fieldID, + jint value) { + functions->SetStaticIntField(this,clazz,fieldID,value); + } + void SetStaticLongField(jclass clazz, jfieldID fieldID, + jlong value) { + functions->SetStaticLongField(this,clazz,fieldID,value); + } + void SetStaticFloatField(jclass clazz, jfieldID fieldID, + jfloat value) { + functions->SetStaticFloatField(this,clazz,fieldID,value); + } + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, + jdouble value) { + functions->SetStaticDoubleField(this,clazz,fieldID,value); + } + + jstring NewString(const jchar *unicode, jsize len) { + return functions->NewString(this,unicode,len); + } + jsize GetStringLength(jstring str) { + return functions->GetStringLength(this,str); + } + const jchar *GetStringChars(jstring str, jboolean *isCopy) { + return functions->GetStringChars(this,str,isCopy); + } + void ReleaseStringChars(jstring str, const jchar *chars) { + functions->ReleaseStringChars(this,str,chars); + } + + jstring NewStringUTF(const char *utf) { + return functions->NewStringUTF(this,utf); + } + jsize GetStringUTFLength(jstring str) { + return functions->GetStringUTFLength(this,str); + } + const char* GetStringUTFChars(jstring str, jboolean *isCopy) { + return functions->GetStringUTFChars(this,str,isCopy); + } + void ReleaseStringUTFChars(jstring str, const char* chars) { + functions->ReleaseStringUTFChars(this,str,chars); + } + + jsize GetArrayLength(jarray array) { + return functions->GetArrayLength(this,array); + } + + jobjectArray NewObjectArray(jsize len, jclass clazz, + jobject init) { + return functions->NewObjectArray(this,len,clazz,init); + } + jobject GetObjectArrayElement(jobjectArray array, jsize index) { + return functions->GetObjectArrayElement(this,array,index); + } + void SetObjectArrayElement(jobjectArray array, jsize index, + jobject val) { + functions->SetObjectArrayElement(this,array,index,val); + } + + jbooleanArray NewBooleanArray(jsize len) { + return functions->NewBooleanArray(this,len); + } + jbyteArray NewByteArray(jsize len) { + return functions->NewByteArray(this,len); + } + jcharArray NewCharArray(jsize len) { + return functions->NewCharArray(this,len); + } + jshortArray NewShortArray(jsize len) { + return functions->NewShortArray(this,len); + } + jintArray NewIntArray(jsize len) { + return functions->NewIntArray(this,len); + } + jlongArray NewLongArray(jsize len) { + return functions->NewLongArray(this,len); + } + jfloatArray NewFloatArray(jsize len) { + return functions->NewFloatArray(this,len); + } + jdoubleArray NewDoubleArray(jsize len) { + return functions->NewDoubleArray(this,len); + } + + jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy) { + return functions->GetBooleanArrayElements(this,array,isCopy); + } + jbyte * GetByteArrayElements(jbyteArray array, jboolean *isCopy) { + return functions->GetByteArrayElements(this,array,isCopy); + } + jchar * GetCharArrayElements(jcharArray array, jboolean *isCopy) { + return functions->GetCharArrayElements(this,array,isCopy); + } + jshort * GetShortArrayElements(jshortArray array, jboolean *isCopy) { + return functions->GetShortArrayElements(this,array,isCopy); + } + jint * GetIntArrayElements(jintArray array, jboolean *isCopy) { + return functions->GetIntArrayElements(this,array,isCopy); + } + jlong * GetLongArrayElements(jlongArray array, jboolean *isCopy) { + return functions->GetLongArrayElements(this,array,isCopy); + } + jfloat * GetFloatArrayElements(jfloatArray array, jboolean *isCopy) { + return functions->GetFloatArrayElements(this,array,isCopy); + } + jdouble * GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy) { + return functions->GetDoubleArrayElements(this,array,isCopy); + } + + void ReleaseBooleanArrayElements(jbooleanArray array, + jboolean *elems, + jint mode) { + functions->ReleaseBooleanArrayElements(this,array,elems,mode); + } + void ReleaseByteArrayElements(jbyteArray array, + jbyte *elems, + jint mode) { + functions->ReleaseByteArrayElements(this,array,elems,mode); + } + void ReleaseCharArrayElements(jcharArray array, + jchar *elems, + jint mode) { + functions->ReleaseCharArrayElements(this,array,elems,mode); + } + void ReleaseShortArrayElements(jshortArray array, + jshort *elems, + jint mode) { + functions->ReleaseShortArrayElements(this,array,elems,mode); + } + void ReleaseIntArrayElements(jintArray array, + jint *elems, + jint mode) { + functions->ReleaseIntArrayElements(this,array,elems,mode); + } + void ReleaseLongArrayElements(jlongArray array, + jlong *elems, + jint mode) { + functions->ReleaseLongArrayElements(this,array,elems,mode); + } + void ReleaseFloatArrayElements(jfloatArray array, + jfloat *elems, + jint mode) { + functions->ReleaseFloatArrayElements(this,array,elems,mode); + } + void ReleaseDoubleArrayElements(jdoubleArray array, + jdouble *elems, + jint mode) { + functions->ReleaseDoubleArrayElements(this,array,elems,mode); + } + + void GetBooleanArrayRegion(jbooleanArray array, + jsize start, jsize len, jboolean *buf) { + functions->GetBooleanArrayRegion(this,array,start,len,buf); + } + void GetByteArrayRegion(jbyteArray array, + jsize start, jsize len, jbyte *buf) { + functions->GetByteArrayRegion(this,array,start,len,buf); + } + void GetCharArrayRegion(jcharArray array, + jsize start, jsize len, jchar *buf) { + functions->GetCharArrayRegion(this,array,start,len,buf); + } + void GetShortArrayRegion(jshortArray array, + jsize start, jsize len, jshort *buf) { + functions->GetShortArrayRegion(this,array,start,len,buf); + } + void GetIntArrayRegion(jintArray array, + jsize start, jsize len, jint *buf) { + functions->GetIntArrayRegion(this,array,start,len,buf); + } + void GetLongArrayRegion(jlongArray array, + jsize start, jsize len, jlong *buf) { + functions->GetLongArrayRegion(this,array,start,len,buf); + } + void GetFloatArrayRegion(jfloatArray array, + jsize start, jsize len, jfloat *buf) { + functions->GetFloatArrayRegion(this,array,start,len,buf); + } + void GetDoubleArrayRegion(jdoubleArray array, + jsize start, jsize len, jdouble *buf) { + functions->GetDoubleArrayRegion(this,array,start,len,buf); + } + + void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + const jboolean *buf) { + functions->SetBooleanArrayRegion(this,array,start,len,buf); + } + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, + const jbyte *buf) { + functions->SetByteArrayRegion(this,array,start,len,buf); + } + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, + const jchar *buf) { + functions->SetCharArrayRegion(this,array,start,len,buf); + } + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, + const jshort *buf) { + functions->SetShortArrayRegion(this,array,start,len,buf); + } + void SetIntArrayRegion(jintArray array, jsize start, jsize len, + const jint *buf) { + functions->SetIntArrayRegion(this,array,start,len,buf); + } + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, + const jlong *buf) { + functions->SetLongArrayRegion(this,array,start,len,buf); + } + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + const jfloat *buf) { + functions->SetFloatArrayRegion(this,array,start,len,buf); + } + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + const jdouble *buf) { + functions->SetDoubleArrayRegion(this,array,start,len,buf); + } + + jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, + jint nMethods) { + return functions->RegisterNatives(this,clazz,methods,nMethods); + } + jint UnregisterNatives(jclass clazz) { + return functions->UnregisterNatives(this,clazz); + } + + jint MonitorEnter(jobject obj) { + return functions->MonitorEnter(this,obj); + } + jint MonitorExit(jobject obj) { + return functions->MonitorExit(this,obj); + } + + jint GetJavaVM(JavaVM **vm) { + return functions->GetJavaVM(this,vm); + } + + void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf) { + functions->GetStringRegion(this,str,start,len,buf); + } + void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) { + functions->GetStringUTFRegion(this,str,start,len,buf); + } + + void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) { + return functions->GetPrimitiveArrayCritical(this,array,isCopy); + } + void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode) { + functions->ReleasePrimitiveArrayCritical(this,array,carray,mode); + } + + const jchar * GetStringCritical(jstring string, jboolean *isCopy) { + return functions->GetStringCritical(this,string,isCopy); + } + void ReleaseStringCritical(jstring string, const jchar *cstring) { + functions->ReleaseStringCritical(this,string,cstring); + } + + jweak NewWeakGlobalRef(jobject obj) { + return functions->NewWeakGlobalRef(this,obj); + } + void DeleteWeakGlobalRef(jweak ref) { + functions->DeleteWeakGlobalRef(this,ref); + } + + jboolean ExceptionCheck() { + return functions->ExceptionCheck(this); + } + + jobject NewDirectByteBuffer(void* address, jlong capacity) { + return functions->NewDirectByteBuffer(this, address, capacity); + } + void* GetDirectBufferAddress(jobject buf) { + return functions->GetDirectBufferAddress(this, buf); + } + jlong GetDirectBufferCapacity(jobject buf) { + return functions->GetDirectBufferCapacity(this, buf); + } + jobjectRefType GetObjectRefType(jobject obj) { + return functions->GetObjectRefType(this, obj); + } + +#endif /* __cplusplus */ +}; + +typedef struct JavaVMOption { + char *optionString; + void *extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; + + jint nOptions; + JavaVMOption *options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +typedef struct JavaVMAttachArgs { + jint version; + + char *name; + jobject group; +} JavaVMAttachArgs; + +/* These will be VM-specific. */ + +#define JDK1_2 +#define JDK1_4 + +/* End VM-specific. */ + +struct JNIInvokeInterface_ { + void *reserved0; + void *reserved1; + void *reserved2; + +#if !TARGET_RT_MAC_CFM && defined(__ppc__) + void* cfm_vectors[4]; +#endif /* !TARGET_RT_MAC_CFM && defined(__ppc__) */ + + jint (JNICALL *DestroyJavaVM)(JavaVM *vm); + + jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args); + + jint (JNICALL *DetachCurrentThread)(JavaVM *vm); + + jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version); + + jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args); + +#if TARGET_RT_MAC_CFM && defined(__ppc__) + void* real_functions[5]; +#endif /* TARGET_RT_MAC_CFM && defined(__ppc__) */ +}; + +struct JavaVM_ { + const struct JNIInvokeInterface_ *functions; +#ifdef __cplusplus + + jint DestroyJavaVM() { + return functions->DestroyJavaVM(this); + } + jint AttachCurrentThread(void **penv, void *args) { + return functions->AttachCurrentThread(this, penv, args); + } + jint DetachCurrentThread() { + return functions->DetachCurrentThread(this); + } + + jint GetEnv(void **penv, jint version) { + return functions->GetEnv(this, penv, version); + } + jint AttachCurrentThreadAsDaemon(void **penv, void *args) { + return functions->AttachCurrentThreadAsDaemon(this, penv, args); + } +#endif +}; + +#ifdef _JNI_IMPLEMENTATION_ +#define _JNI_IMPORT_OR_EXPORT_ JNIEXPORT +#else +#define _JNI_IMPORT_OR_EXPORT_ JNIIMPORT +#endif +_JNI_IMPORT_OR_EXPORT_ __attribute__((deprecated)) jint JNICALL +JNI_GetDefaultJavaVMInitArgs(void *args); + +_JNI_IMPORT_OR_EXPORT_ __attribute__((deprecated)) jint JNICALL +JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args); + +_JNI_IMPORT_OR_EXPORT_ __attribute__((deprecated)) jint JNICALL +JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *); + +/* Defined by native libraries. */ +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *vm, void *reserved); + +JNIEXPORT void JNICALL +JNI_OnUnload(JavaVM *vm, void *reserved); + +#define JNI_VERSION_1_1 0x00010001 +#define JNI_VERSION_1_2 0x00010002 +#define JNI_VERSION_1_4 0x00010004 +#define JNI_VERSION_1_6 0x00010006 + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_JAVASOFT_JNI_H_ */ + + + diff --git a/internal/prebuild-binaries/jni/headers/inc_linux/jni_md.h b/internal/prebuild-binaries/jni/headers/inc_linux/jni_md.h new file mode 100644 index 00000000..9b5d1a8a --- /dev/null +++ b/internal/prebuild-binaries/jni/headers/inc_linux/jni_md.h @@ -0,0 +1,24 @@ +/* + * %W% %E% + * + * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#ifndef _JAVASOFT_JNI_MD_H_ +#define _JAVASOFT_JNI_MD_H_ + +#define JNIEXPORT __attribute__((__visibility__("default"))) +#define JNIIMPORT +#define JNICALL + +typedef int jint; +#ifdef _LP64 /* 64-bit Solaris */ +typedef long jlong; +#else +typedef long long jlong; +#endif + +typedef signed char jbyte; + +#endif /* !_JAVASOFT_JNI_MD_H_ */ diff --git a/internal/prebuild-binaries/jni/headers/inc_mac/jni_md.h b/internal/prebuild-binaries/jni/headers/inc_mac/jni_md.h new file mode 100644 index 00000000..21cc90b3 --- /dev/null +++ b/internal/prebuild-binaries/jni/headers/inc_mac/jni_md.h @@ -0,0 +1,23 @@ +/* + * @(#)jni_md.h 1.19 05/11/17 + * + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#ifndef _JAVASOFT_JNI_MD_H_ +#define _JAVASOFT_JNI_MD_H_ + +#define JNIEXPORT __attribute__((visibility("default"))) +#define JNIIMPORT +#define JNICALL + +#if defined(__LP64__) && __LP64__ /* for -Wundef */ +typedef int jint; +#else +typedef long jint; +#endif +typedef long long jlong; +typedef signed char jbyte; + +#endif /* !_JAVASOFT_JNI_MD_H_ */ diff --git a/internal/prebuild-binaries/jni/headers/inc_win/jni_md.h b/internal/prebuild-binaries/jni/headers/inc_win/jni_md.h new file mode 100644 index 00000000..26a733df --- /dev/null +++ b/internal/prebuild-binaries/jni/headers/inc_win/jni_md.h @@ -0,0 +1,19 @@ +/* + * @(#)jni_md.h 1.14 03/12/19 + * + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#ifndef _JAVASOFT_JNI_MD_H_ +#define _JAVASOFT_JNI_MD_H_ + +#define JNIEXPORT __declspec(dllexport) +#define JNIIMPORT __declspec(dllimport) +#define JNICALL __stdcall + +typedef long jint; +typedef __int64 jlong; +typedef signed char jbyte; + +#endif /* !_JAVASOFT_JNI_MD_H_ */ diff --git a/internal/prebuild-binaries/jni/sqlite_bindings.cpp b/internal/prebuild-binaries/jni/sqlite_bindings.cpp new file mode 100644 index 00000000..67647da2 --- /dev/null +++ b/internal/prebuild-binaries/jni/sqlite_bindings.cpp @@ -0,0 +1,440 @@ +// Upstream source: https://github.com/androidx/androidx/blob/androidx-main/sqlite/sqlite-bundled/src/jvmAndroidMain/jni/sqlite_bindings.cpp + +#include +#include "sqlite3.h" +#include +#include +#include + +/** + * Throws SQLiteException with the given error code and message. + * + * @return true if the exception was thrown, otherwise false. + */ +static bool throwSQLiteException(JNIEnv *env, int errorCode, const char *errorMsg) { + jclass exceptionClass = env->FindClass("androidx/sqlite/SQLiteException"); + if (exceptionClass == nullptr) { + // If androidx's exception isn't found we are likely in Android's native where the + // actual exception is type aliased. Clear the ClassNotFoundException and instead find + // and throw Android's exception. + env->ExceptionClear(); + exceptionClass = env->FindClass("android/database/SQLException"); + } + int codeLength = snprintf(nullptr, 0, "%d", errorCode); + size_t prefixLength = strlen("Error code: "); + size_t msgLength = 0; + if (errorMsg != nullptr) { + msgLength = strlen(", message: ") + strlen(errorMsg); + } + size_t totalSize = prefixLength + codeLength + msgLength + 1; + char* message = (char*) malloc(totalSize); + if (errorMsg != nullptr) { + snprintf(message, totalSize, "Error code: %d, message: %s", errorCode, errorMsg); + } else { + snprintf(message, totalSize, "Error code: %d", errorCode); + } + int throwResult = env->ThrowNew(exceptionClass, message); + free(message); + return throwResult == 0; +} + +static bool throwIfNoRow(JNIEnv *env, sqlite3_stmt *stmt) { + if (sqlite3_stmt_busy(stmt) == 0) { + return throwSQLiteException(env, SQLITE_MISUSE, "no row"); + } + return false; +} + +static bool throwIfInvalidColumn(JNIEnv *env, sqlite3_stmt *stmt, int index) { + if (index < 0 || index >= sqlite3_column_count(stmt)) { + return throwSQLiteException(env, SQLITE_RANGE, "column index out of range"); + } + return false; +} + +static bool throwOutOfMemoryError(JNIEnv *env) { + jclass exceptionClass = env->FindClass("java/lang/OutOfMemoryError"); + int throwResult = env->ThrowNew(exceptionClass, nullptr); + return throwResult == 0; +} + +static bool throwIfOutOfMemory(JNIEnv *env, sqlite3_stmt *stmt) { + int lastRc = sqlite3_errcode(sqlite3_db_handle(stmt)); + if (lastRc == SQLITE_NOMEM) { + return throwOutOfMemoryError(env); + } + return false; +} + +static jlong JNICALL nativeOpen( + JNIEnv *env, + jclass clazz, + jstring name, + int openFlags) { + const char *path = env->GetStringUTFChars(name, nullptr); + sqlite3 *db; + int rc = sqlite3_open_v2(path, &db, openFlags, nullptr); + env->ReleaseStringUTFChars(name, path); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, nullptr); + return 0; + } + + // Enable extended error codes + rc = sqlite3_extended_result_codes(db, 1); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, nullptr); + return 0; + } + + // Enable the C function to load extensions but not the load_extension() SQL function. + rc = sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, 0); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, nullptr); + return 0; + } + + return reinterpret_cast(db); +} + +static jboolean JNICALL nativeInTransaction( + JNIEnv *env, + jclass clazz, + jlong dbPointer) { + sqlite3 *db = reinterpret_cast(dbPointer); + if (sqlite3_get_autocommit(db) == 0) { + return JNI_TRUE; + } else { + return JNI_FALSE; + } +} + +static jlong JNICALL nativePrepare( + JNIEnv *env, + jclass clazz, + jlong dbPointer, + jstring sqlString) { + sqlite3 *db = reinterpret_cast(dbPointer); + sqlite3_stmt *stmt; + jsize sqlLength = env->GetStringLength(sqlString); + // Java / jstring represents a string in UTF-16 encoding. + const jchar *sql = env->GetStringCritical(sqlString, nullptr); + int rc = sqlite3_prepare16_v2(db, sql, sqlLength * sizeof(jchar), &stmt, nullptr); + env->ReleaseStringCritical(sqlString, sql); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(db)); + return 0; + } + return reinterpret_cast(stmt); +} + +static void JNICALL nativeLoadExtension( + JNIEnv *env, + jclass clazz, + jlong dbPointer, + jstring fileName, + jstring entryPoint) { + sqlite3 *db = reinterpret_cast(dbPointer); + const char *zFileName = env->GetStringUTFChars(fileName, nullptr); + const char *zEntryPoint = nullptr; + if (entryPoint) { + zEntryPoint = env->GetStringUTFChars(entryPoint, nullptr); + } + char *errorMsg = nullptr; + int rc = sqlite3_load_extension(db, zFileName, zEntryPoint, &errorMsg); + env->ReleaseStringUTFChars(fileName, zFileName); + if (entryPoint) { + env->ReleaseStringUTFChars(entryPoint, zEntryPoint); + } + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, errorMsg); + if (errorMsg) { + sqlite3_free(errorMsg); + } + } +} + +static void JNICALL nativeConnectionClose( + JNIEnv *env, + jclass clazz, + jlong dbPointer) { + sqlite3 *db = reinterpret_cast(dbPointer); + sqlite3_close_v2(db); +} + +static void JNICALL nativeBindBlob( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index, + jbyteArray value) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + jsize valueLength = env->GetArrayLength(value); + jbyte *blob = static_cast(env->GetPrimitiveArrayCritical(value, nullptr)); + int rc = sqlite3_bind_blob(stmt, index, blob, valueLength, SQLITE_TRANSIENT); + env->ReleasePrimitiveArrayCritical(value, blob, JNI_ABORT); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + } +} + +static void JNICALL nativeBindDouble( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index, + jdouble value) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + int rc = sqlite3_bind_double(stmt, index, value); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + } +} + +static void JNICALL nativeBindLong( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index, + jlong value) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + int rc = sqlite3_bind_int64(stmt, index, value); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + } +} + +static void JNICALL nativeBindText( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index, + jstring value) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + jsize valueLength = env->GetStringLength(value); + const jchar *text = env->GetStringCritical(value, NULL); + int rc = sqlite3_bind_text16(stmt, index, text, valueLength * sizeof(jchar), SQLITE_TRANSIENT); + env->ReleaseStringCritical(value, text); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + } +} + +static void JNICALL nativeBindNull( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + int rc = sqlite3_bind_null(stmt, index); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + } +} + +static jboolean JNICALL nativeStep( + JNIEnv *env, + jclass clazz, + jlong stmtPointer) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + int rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + return JNI_TRUE; + } + if (rc == SQLITE_DONE) { + return JNI_FALSE; + } + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + return JNI_FALSE; +} + +static jbyteArray JNICALL nativeGetBlob( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + if (throwIfNoRow(env, stmt)) return nullptr; + if (throwIfInvalidColumn(env, stmt, index)) return nullptr; + const void *blob = sqlite3_column_blob(stmt, index); + if (blob == nullptr && throwIfOutOfMemory(env, stmt)) return nullptr; + int size = sqlite3_column_bytes(stmt, index); + if (size == 0 && throwIfOutOfMemory(env, stmt)) return nullptr; + jbyteArray byteArray = env->NewByteArray(size); + if (size > 0) { + env->SetByteArrayRegion(byteArray, 0, size, static_cast(blob)); + } + return byteArray; +} + +static jdouble JNICALL nativeGetDouble( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + if (throwIfNoRow(env, stmt)) return 0.0; + if (throwIfInvalidColumn(env, stmt, index)) return 0.0; + return sqlite3_column_double(stmt, index); +} + +static jlong JNICALL nativeGetLong( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + if (throwIfNoRow(env, stmt)) return 0; + if (throwIfInvalidColumn(env, stmt, index)) return 0; + return sqlite3_column_int64(stmt, index); +} + +static jstring JNICALL nativeGetText( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + if (throwIfNoRow(env, stmt)) return nullptr; + if (throwIfInvalidColumn(env, stmt, index)) return nullptr; + // Java / jstring represents a string in UTF-16 encoding. + const jchar *text = static_cast(sqlite3_column_text16(stmt, index)); + if (text == nullptr && throwIfOutOfMemory(env, stmt)) return nullptr; + size_t length = sqlite3_column_bytes16(stmt, index) / sizeof(jchar); + if (length == 0 && throwIfOutOfMemory(env, stmt)) return nullptr; + return env->NewString(text, length); +} + +static jint JNICALL nativeGetColumnCount( + JNIEnv *env, + jclass clazz, + jlong stmtPointer) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + return sqlite3_column_count(stmt); +} + +static jstring JNICALL nativeGetColumnName( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + if (throwIfInvalidColumn(env, stmt, index)) return nullptr; + const char *name = sqlite3_column_name(stmt, index); + if (name == nullptr) { + throwOutOfMemoryError(env); + return nullptr; + } + return env->NewStringUTF(name); +} + +static jint JNICALL nativeGetColumnType( + JNIEnv *env, + jclass clazz, + jlong stmtPointer, + jint index) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + if (throwIfNoRow(env, stmt)) return 0; + if (throwIfInvalidColumn(env, stmt, index)) return 0; + return sqlite3_column_type(stmt, index); +} + +static void JNICALL nativeReset( + JNIEnv *env, + jclass clazz, + jlong stmtPointer) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + int rc = sqlite3_reset(stmt); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + } +} + +static void JNICALL nativeClearBindings( + JNIEnv *env, + jclass clazz, + jlong stmtPointer) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + int rc = sqlite3_clear_bindings(stmt); + if (rc != SQLITE_OK) { + throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt))); + } +} + +static void JNICALL nativeStatementClose( + JNIEnv *env, + jclass clazz, + jlong stmtPointer) { + sqlite3_stmt *stmt = reinterpret_cast(stmtPointer); + sqlite3_finalize(stmt); +} + +static const JNINativeMethod sDriverMethods[] = { + {"nativeOpen", "(Ljava/lang/String;I)J", (void *) nativeOpen} +}; + +static const JNINativeMethod sConnectionMethods[] = { + {"nativeInTransaction", "(J)Z", (void *) nativeInTransaction}, + {"nativePrepare", "(JLjava/lang/String;)J", (void *) nativePrepare}, + {"nativeLoadExtension", "(JLjava/lang/String;Ljava/lang/String;)V", (void *) nativeLoadExtension}, + {"nativeClose", "(J)V", (void *) nativeConnectionClose} +}; + +static const JNINativeMethod sStatementMethods[] = { + {"nativeBindBlob", "(JI[B)V", (void *) nativeBindBlob}, + {"nativeBindDouble", "(JID)V", (void *) nativeBindDouble}, + {"nativeBindLong", "(JIJ)V", (void *) nativeBindLong}, + {"nativeBindText", "(JILjava/lang/String;)V", (void *) nativeBindText}, + {"nativeBindNull", "(JI)V", (void *) nativeBindNull}, + {"nativeStep", "(J)Z", (void *) nativeStep}, + {"nativeGetBlob", "(JI)[B", (void *) nativeGetBlob}, + {"nativeGetDouble", "(JI)D", (void *) nativeGetDouble}, + {"nativeGetLong", "(JI)J", (void *) nativeGetLong}, + {"nativeGetText", "(JI)Ljava/lang/String;", (void *) nativeGetText}, + {"nativeGetColumnCount", "(J)I", (void *) nativeGetColumnCount}, + {"nativeGetColumnName", "(JI)Ljava/lang/String;", (void *) nativeGetColumnName}, + {"nativeGetColumnType", "(JI)I", (void *) nativeGetColumnType}, + {"nativeReset", "(J)V", (void *) nativeReset}, + {"nativeClearBindings", "(J)V", (void *) nativeClearBindings}, + {"nativeClose", "(J)V", (void *) nativeStatementClose}, +}; + +static int register_methods(JNIEnv *env, const char *className, + const JNINativeMethod *methods, + int methodCount) { + jclass clazz = env->FindClass(className); + if (clazz == nullptr) { + return JNI_ERR; + } + int result = env->RegisterNatives(clazz, methods, methodCount); + env->DeleteLocalRef(clazz); + if (result != 0) { + return JNI_ERR; + } + return JNI_OK; +} + +jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) { + JNIEnv *env = nullptr; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6)) { + return JNI_ERR; + } + + const int driverMethodCount = sizeof(sDriverMethods) / sizeof(sDriverMethods[0]); + if (register_methods(env, "com/powersync/encryption/BundledSQLiteDriverKt", + sDriverMethods, driverMethodCount) != JNI_OK) { + return JNI_ERR; + } + const int connectionMethodCount = sizeof(sConnectionMethods) / sizeof(sConnectionMethods[0]); + if (register_methods(env, "com/powersync/encryption/BundledSQLiteConnectionKt", + sConnectionMethods, connectionMethodCount) != JNI_OK) { + return JNI_ERR; + } + const int statementMethodCount = sizeof(sStatementMethods) / sizeof(sStatementMethods[0]); + if (register_methods(env, "com/powersync/encryption/BundledSQLiteStatementKt", + sStatementMethods, statementMethodCount) != JNI_OK) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt index 914e7aef..b10ffdb2 100644 --- a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt @@ -42,12 +42,7 @@ abstract class ClangCompile : DefaultTask() { @get:Input val xcodeInstallation: Provider - get() = - providers - .exec { - executable("xcode-select") - args("-p") - }.standardOutput.asText + get() = resolveXcode(providers) @TaskAction fun run() { @@ -95,15 +90,7 @@ abstract class ClangCompile : DefaultTask() { "--compile", "-I${include.get().asFile.absolutePath}", inputFile.get().asFile.absolutePath, - "-DHAVE_GETHOSTUUID=0", - "-DSQLITE_ENABLE_DBSTAT_VTAB", - "-DSQLITE_ENABLE_FTS5", - "-DSQLITE_ENABLE_RTREE", - // Used by GRDB - "-DSQLITE_ENABLE_SNAPSHOT", - // Used for GRDB update hook like functionality - "-DSQLITE_ENABLE_SESSION", - "-DSQLITE_ENABLE_PREUPDATE_HOOK", + *sqlite3ClangOptions, // "-O3", "-o", @@ -126,5 +113,26 @@ abstract class ClangCompile : DefaultTask() { const val TVOS_SIMULATOR_SDK = "Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk" const val MACOS_SDK = "Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/" + + val sqlite3ClangOptions = arrayOf( + // Note: Keep in sync with sqlite3multiplecipers/src/jni/CMakeLists.txt + "-DHAVE_GETHOSTUUID=0", + "-DSQLITE_ENABLE_DBSTAT_VTAB", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_ENABLE_RTREE", + // Used by GRDB + "-DSQLITE_ENABLE_SNAPSHOT", + // Used for GRDB update hook like functionality + "-DSQLITE_ENABLE_SESSION", + "-DSQLITE_ENABLE_PREUPDATE_HOOK", + ) + + fun resolveXcode(factory: ProviderFactory): Provider { + return factory + .exec { + executable("xcode-select") + args("-p") + }.standardOutput.asText.map { it.trim() } + } } } diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/JniLibraryCompile.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/JniLibraryCompile.kt new file mode 100644 index 00000000..0e0946b9 --- /dev/null +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/JniLibraryCompile.kt @@ -0,0 +1,154 @@ +package com.powersync.compile + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.ProjectLayout +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.process.ProcessExecutionException +import org.gradle.process.internal.ExecException +import java.io.File +import javax.inject.Inject + +enum class JniTarget { + LINUX_ARM, + LINUX_X64, + MACOS_ARM, + MACOS_X64, + WINDOWS_ARM, + WINDOWS_X64; + + fun filename(library: String): String = when (this) { + LINUX_ARM -> "lib${library}_aarch64.linux.so" + LINUX_X64 -> "lib${library}_x64.linux.so" + MACOS_ARM -> "lib${library}_aarch64.macos.dylib" + MACOS_X64 -> "lib${library}_x64.macos.dylib" + WINDOWS_ARM -> "${library}_aarch64.dll" + WINDOWS_X64 -> "${library}_x64.dll" + } +} + +/** + * A task compiling the native Androidx SQLite JNI library on macOS. + * + * We use this custom task instead of the regular library to be able to swap out sqlite3 for SQLiteMultipleCiphers. + */ +@CacheableTask +abstract class JniLibraryCompile: DefaultTask() { + @get:Input + abstract val target: Property + + @get:Input + abstract val clangPath: Property + + @get:Input + @get:Optional + abstract val toolchain: Property + + @get:InputFiles + @get:PathSensitive(PathSensitivity.NONE) + abstract val inputFiles: ConfigurableFileCollection + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.NONE) + abstract val include: DirectoryProperty + + @get:OutputFile + abstract val sharedLibrary: RegularFileProperty + + @get:Inject + protected abstract val providers: ProviderFactory + + @get:Inject + protected abstract val layout: ProjectLayout + + init { + clangPath.convention("clang") + } + + @TaskAction + fun run() { + val target = this.target.get() + val clang = clangPath.getOrElse("clang") + val workingDir = layout.projectDirectory.asFile + + fun filePath(file: File): String { + return file.relativeTo(workingDir).path + } + + val args = buildList { + if (target == JniTarget.LINUX_X64) { + add("-fuse-ld=lld") + add("--sysroot=build/sysroot/") + } + + if (target == JniTarget.LINUX_ARM) { + val gccParent = layout.buildDirectory.dir("sysroot/usr/lib/gcc/aarch64-linux-gnu") + val gccPath = filePath(gccParent.get().asFile.listFiles().single()) + + add("-fuse-ld=lld") + add("--sysroot=build/sysroot/usr/aarch64-linux-gnu/") + add("-Wl,-L") + add(gccPath) + add("-B") + add(gccPath) + } + + toolchain.orNull?.let { add("-B$toolchain") } + + add("-shared") + add("-fPIC") + add(when (target) { + JniTarget.LINUX_ARM -> "--target=aarch64-pc-linux" + JniTarget.LINUX_X64 -> "--target=x86_64-pc-linux" + JniTarget.MACOS_ARM -> "--target=aarch64-apple-macos" + JniTarget.MACOS_X64 -> "--target=x86_64-apple-macos" + JniTarget.WINDOWS_ARM -> "--target=aarch64-w64-mingw32uwp" + JniTarget.WINDOWS_X64 -> "--target=x86_64-w64-mingw32uwp" + }) + add("-o") + add(filePath(sharedLibrary.get().asFile)) + inputFiles.files.forEach { add(filePath(it)) } + add("-I") + add(filePath(include.get().asFile)) + add("-I") + add("jni/headers/common") + add("-I") + add(when (target) { + JniTarget.LINUX_X64, JniTarget.LINUX_ARM -> "jni/headers/inc_linux" + JniTarget.MACOS_X64, JniTarget.MACOS_ARM -> "jni/headers/inc_mac" + JniTarget.WINDOWS_X64, JniTarget.WINDOWS_ARM -> "jni/headers/inc_win" + }) + add("-O3") + addAll(ClangCompile.sqlite3ClangOptions) + } + + val execProvider = providers.exec { + this.workingDir = workingDir + executable = clang + args(args) + + isIgnoreExitValue = true // So that we can provide better error messages + } + + try { + execProvider.result.get().assertNormalExitValue() + } catch (_: ProcessExecutionException) { + val formattedArgs = args.joinToString(separator = " ") { "\"$it\"" } + val stderr = execProvider.standardError.asText.orNull + + throw ProcessExecutionException("Could not start $clang $formattedArgs. Stderr: $stderr") + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e793b1c7..02a22b4b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,6 +37,7 @@ include(":core") include(":core-tests-android") include(":integrations:room") include(":static-sqlite-driver") +include(":sqlite3multipleciphers") include(":integrations:sqldelight") include(":integrations:sqldelight-test-database") diff --git a/sqlite3multipleciphers/README.md b/sqlite3multipleciphers/README.md new file mode 100644 index 00000000..db63bce6 --- /dev/null +++ b/sqlite3multipleciphers/README.md @@ -0,0 +1,35 @@ +## PowerSync with SQLite3MultipleCiphers + +This is a cross-platform build of a SQLite driver using [SQLite3MultipleCiphers](https://utelle.github.io/SQLite3MultipleCiphers/) +for the PowerSync Kotlin SDK. + +> [!NOTE] +> Note that this package is currently in alpha. + +## Using + +This package is designed to be used with the main PowerSync SDK. On native platforms, it is incompatible with the +default `com.powersync:core` though. +To use this package, replace your dependency on `com.powersync:core` with `com.powersync:common` of the same version. +Also add a dependency on `com.powersync:sqlite3multipleciphers`. +Finally, note that `:core` has a dependency on a ktor client implementation for each platform. So if you're not using +ktor in your project already, you'd have to add: + +1. A dependency on `io.ktor:ktor-client-okhttp` on Android and the JVM. +2. A dependency on `io.ktor:ktor-client-darwin` for Apple targets on Kotlin/Native. + +Different client implementations are also available, see [the overview](https://ktor.io/docs/client-engines.html#platforms) +for details. + +You can continue to use `PowerSyncDatabase` to open databases. For the `factory` parameter, pass: + +- On Android: An instance of `AndroidEncryptedDatabaseFactory`. +- For JVM targets: An instance of `JavaEncryptedDatabaseFactory`. +- For Kotlin/Native: An instance of `NativeEncryptedDatabaseFactory`. + +Each of these factories takes a `Key` instance used to encrypt the database. + +## Credits + +The Android / Java sources of this package, as well as the JNI wrapper code, have been copied and adapted from +the `androidx.sqlite:bundled-sqlite-driver` package. diff --git a/sqlite3multipleciphers/build.gradle.kts b/sqlite3multipleciphers/build.gradle.kts new file mode 100644 index 00000000..c7ef1ecb --- /dev/null +++ b/sqlite3multipleciphers/build.gradle.kts @@ -0,0 +1,147 @@ +import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask +import com.powersync.compile.CreateSqliteCInterop +import com.powersync.plugins.utils.powersyncTargets +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.konan.target.HostManager + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.kotlinter) + id("com.powersync.plugins.sonatype") + id("com.powersync.plugins.sharedbuild") + id("dokka-convention") +} + +val nativeSqliteConfiguration: Configuration by configurations.creating { + isCanBeConsumed = false +} +val jniSqlite3McConfiguration: Configuration by configurations.creating { + isCanBeConsumed = false +} +val androidBuildSourceConfiguration by configurations.creating { + isCanBeConsumed = false +} + +dependencies { + nativeSqliteConfiguration(project(path=":internal:prebuild-binaries", configuration="nativeSqliteConfiguration")) + jniSqlite3McConfiguration(project(path=":internal:prebuild-binaries", configuration="jniSqlite3McConfiguration")) + androidBuildSourceConfiguration(project(path=":internal:prebuild-binaries", configuration="androidBuildSourceConfiguration")) +} + +val hostManager = HostManager() + +fun linkSqlite3McCInterop(target: KotlinNativeTarget): TaskProvider { + val buildCInteropDef = tasks.register("${target.name}CinteropSqlite3Mc", CreateSqliteCInterop::class) { + val precompiledSqlite: FileCollection = nativeSqliteConfiguration + inputs.files(precompiledSqlite) + dependsOn(precompiledSqlite) + + val staticLibrary = precompiledSqlite.singleFile.resolve("${target.konanTarget.name}sqlite3mc.a") + archiveFile.set(staticLibrary) + definitionFile.value(layout.buildDirectory.map { it.file("interopDefs/${target.name}/sqlite3mc.def") }) + } + + return buildCInteropDef +} + +kotlin { + powersyncTargets() + + applyDefaultHierarchyTemplate() + explicitApi() + + sourceSets { + all { + languageSettings.apply { + optIn("kotlin.experimental.ExperimentalNativeApi") + optIn("kotlinx.cinterop.ExperimentalForeignApi") + optIn("kotlinx.cinterop.BetaInteropApi") + optIn("com.powersync.PowerSyncInternal") + } + } + + val jvmAndroidMain by creating { + dependsOn(commonMain.get()) + } + + androidMain { + dependsOn(jvmAndroidMain) + } + + jvmMain { + dependsOn(jvmAndroidMain) + } + + commonMain.dependencies { + api(projects.common) + implementation(libs.androidx.sqlite.sqlite) + } + + commonTest.dependencies { + implementation(libs.kotlin.test) + api(libs.test.kotest.assertions) + } + } + + targets.withType { + if (hostManager.isEnabled(konanTarget)) { + val interopSource = linkSqlite3McCInterop(this) + + compilations.named("main") { + cinterops.create("sqlite3mc") { + definitionFile.set(interopSource.flatMap { it.definitionFile }) + } + } + } + } +} + +val generateCmake by tasks.registering(Copy::class) { + from(androidBuildSourceConfiguration) + into(layout.buildDirectory.dir("androidJniBuild")) +} + +android { + compileOptions { + targetCompatibility = JavaVersion.VERSION_17 + } + + namespace = "com.powersync.encryption" + compileSdk = + libs.versions.android.compileSdk + .get() + .toInt() + defaultConfig { + minSdk = + libs.versions.android.minSdk + .get() + .toInt() + consumerProguardFiles("src/androidMain/proguard-rules.pro") + } + + externalNativeBuild { + cmake { + path("build/androidJniBuild/CMakeLists.txt") + } + } + + ndkVersion = "29.0.14206865" +} + +tasks.named(kotlin.jvm().compilations["main"].processResourcesTaskName) { + from(jniSqlite3McConfiguration) +} + +tasks.withType(ExternalNativeBuildJsonTask::class.java).configureEach { + // Android runs these tasks to configure the CMake build. Since the CMake build in this case + // is generated by a Gradle task (which copies artifacts around), we need to make sure that task + // runs before CMake is invoked. + dependsOn(generateCmake) +} + +dokka { + moduleName.set("Encryption (SQLite3MultipleCiphers)") +} diff --git a/sqlite3multipleciphers/gradle.properties b/sqlite3multipleciphers/gradle.properties new file mode 100644 index 00000000..758e4237 --- /dev/null +++ b/sqlite3multipleciphers/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=sqlite3multipleciphers +POM_NAME=SQLite3MultipleCiphers driver for PowerSync +POM_DESCRIPTION=Encrypt SQLite databases with the PowerSync SDK and SQLite3MultipleCiphers. diff --git a/sqlite3multipleciphers/src/androidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.android.kt b/sqlite3multipleciphers/src/androidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.android.kt new file mode 100644 index 00000000..eddad1d5 --- /dev/null +++ b/sqlite3multipleciphers/src/androidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.android.kt @@ -0,0 +1,18 @@ +package com.powersync.encryption + +import android.content.Context + +public class AndroidEncryptedDatabaseFactory( + private val context: Context, + key: Key, +) : BundledSQLiteDriver(key) { + override fun resolveDefaultDatabasePath(dbFilename: String): String = context.getDatabasePath(dbFilename).path +} + +private val didLoadLibrary by lazy { + System.loadLibrary("sqlite3mc_bundled") +} + +internal actual fun ensureJniLibraryLoaded() { + didLoadLibrary +} diff --git a/sqlite3multipleciphers/src/androidMain/proguard-rules.pro b/sqlite3multipleciphers/src/androidMain/proguard-rules.pro new file mode 100644 index 00000000..55c53788 --- /dev/null +++ b/sqlite3multipleciphers/src/androidMain/proguard-rules.pro @@ -0,0 +1 @@ +-keepclasseswithmembers class com.powersync.encryption.** { native ; } diff --git a/sqlite3multipleciphers/src/commonMain/kotlin/com/powersync/encryption/BaseEncryptedDatabaseFactory.kt b/sqlite3multipleciphers/src/commonMain/kotlin/com/powersync/encryption/BaseEncryptedDatabaseFactory.kt new file mode 100644 index 00000000..47095576 --- /dev/null +++ b/sqlite3multipleciphers/src/commonMain/kotlin/com/powersync/encryption/BaseEncryptedDatabaseFactory.kt @@ -0,0 +1,25 @@ +package com.powersync.encryption + +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.execSQL + +public sealed interface Key { + public class Passphrase( + public val passphrase: String, + ) : Key + // TODO: Add raw key api +} + +internal fun SQLiteConnection.encryptOrClose(key: Key) { + try { + when (key) { + is Key.Passphrase -> { + val escaped = key.passphrase.replace("'", "''") + execSQL("pragma key='$escaped'") + } + } + } catch (e: Exception) { + close() + throw e + } +} diff --git a/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteConnection.kt b/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteConnection.kt new file mode 100644 index 00000000..a1f41d8c --- /dev/null +++ b/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteConnection.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("BundledSQLiteConnectionKt") + +package com.powersync.encryption + +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.SQLiteStatement +import androidx.sqlite.throwSQLiteException + +internal class BundledSQLiteConnection( + private val connectionPointer: Long, +) : SQLiteConnection { + @Volatile private var isClosed = false + + override fun inTransaction(): Boolean { + if (isClosed) { + throwSQLiteException(SQLITE_MISUSE, "connection is closed") + } + return nativeInTransaction(connectionPointer) + } + + override fun prepare(sql: String): SQLiteStatement { + if (isClosed) { + throwSQLiteException(SQLITE_MISUSE, "connection is closed") + } + val statementPointer = nativePrepare(connectionPointer, sql) + return BundledSQLiteStatement(connectionPointer, statementPointer) + } + + internal fun loadExtension( + fileName: String, + entryPoint: String?, + ) { + if (isClosed) { + throwSQLiteException(SQLITE_MISUSE, "connection is closed") + } + + nativeLoadExtension(connectionPointer, fileName, entryPoint) + } + + override fun close() { + if (!isClosed) { + isClosed = true + nativeClose(connectionPointer) + } + } + + private companion object { + private const val SQLITE_MISUSE = 21 + } +} + +private external fun nativeInTransaction(pointer: Long): Boolean + +private external fun nativePrepare( + pointer: Long, + sql: String, +): Long + +private external fun nativeLoadExtension( + pointer: Long, + fileName: String, + entryPoint: String?, +) + +private external fun nativeClose(pointer: Long) diff --git a/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.kt b/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.kt new file mode 100644 index 00000000..65526b16 --- /dev/null +++ b/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("BundledSQLiteDriverKt") + +package com.powersync.encryption + +import androidx.sqlite.SQLiteConnection +import com.powersync.PersistentConnectionFactory +import com.powersync.resolvePowerSyncLoadableExtensionPath + +public abstract class BundledSQLiteDriver internal constructor( + private val key: Key, +) : PersistentConnectionFactory { + private fun open( + fileName: String, + flags: Int, + ): SQLiteConnection { + ensureJniLibraryLoaded() + + val address = nativeOpen(fileName, flags) + val connection = BundledSQLiteConnection(address) + try { + connection.loadExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") + } catch (th: Throwable) { + connection.close() + throw th + } + + return connection + } + + override fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection = open(path, openFlags).also { it.encryptOrClose(key) } + + override fun openInMemoryConnection(): SQLiteConnection = open(":memory:", 2) +} + +internal expect fun ensureJniLibraryLoaded() + +private external fun nativeOpen( + name: String, + openFlags: Int, +): Long diff --git a/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteStatement.kt b/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteStatement.kt new file mode 100644 index 00000000..3ddd5562 --- /dev/null +++ b/sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteStatement.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("BundledSQLiteStatementKt") + +package com.powersync.encryption + +import androidx.sqlite.SQLiteStatement +import androidx.sqlite.throwSQLiteException + +internal class BundledSQLiteStatement( + private val connectionPointer: Long, + private val statementPointer: Long, +) : SQLiteStatement { + @Volatile private var isClosed = false + + override fun bindBlob( + index: Int, + value: ByteArray, + ) { + throwIfClosed() + nativeBindBlob(statementPointer, index, value) + } + + override fun bindDouble( + index: Int, + value: Double, + ) { + throwIfClosed() + nativeBindDouble(statementPointer, index, value) + } + + override fun bindLong( + index: Int, + value: Long, + ) { + throwIfClosed() + nativeBindLong(statementPointer, index, value) + } + + override fun bindText( + index: Int, + value: String, + ) { + throwIfClosed() + nativeBindText(statementPointer, index, value) + } + + override fun bindNull(index: Int) { + throwIfClosed() + nativeBindNull(statementPointer, index) + } + + override fun getBlob(index: Int): ByteArray { + throwIfClosed() + return nativeGetBlob(statementPointer, index) + } + + override fun getDouble(index: Int): Double { + throwIfClosed() + return nativeGetDouble(statementPointer, index) + } + + override fun getLong(index: Int): Long { + throwIfClosed() + return nativeGetLong(statementPointer, index) + } + + override fun getText(index: Int): String { + throwIfClosed() + return nativeGetText(statementPointer, index) + } + + override fun isNull(index: Int): Boolean { + throwIfClosed() + return nativeGetColumnType(statementPointer, index) == COLUMN_TYPE_NULL + } + + override fun getColumnCount(): Int { + throwIfClosed() + return nativeGetColumnCount(statementPointer) + } + + override fun getColumnName(index: Int): String { + throwIfClosed() + return nativeGetColumnName(statementPointer, index) + } + + override fun getColumnType(index: Int): Int { + throwIfClosed() + return nativeGetColumnType(statementPointer, index) + } + + override fun step(): Boolean { + throwIfClosed() + return nativeStep(statementPointer) + } + + override fun reset() { + throwIfClosed() + nativeReset(statementPointer) + } + + override fun clearBindings() { + throwIfClosed() + nativeClearBindings(statementPointer) + } + + override fun close() { + if (!isClosed) { + isClosed = true + nativeClose(statementPointer) + } + } + + private fun throwIfClosed() { + if (isClosed) { + throwSQLiteException(21, "statement is closed") + } + } + + private companion object { + private const val COLUMN_TYPE_NULL = 5 + } +} + +private external fun nativeBindBlob( + pointer: Long, + index: Int, + value: ByteArray, +) + +private external fun nativeBindDouble( + pointer: Long, + index: Int, + value: Double, +) + +private external fun nativeBindLong( + pointer: Long, + index: Int, + value: Long, +) + +private external fun nativeBindText( + pointer: Long, + index: Int, + value: String, +) + +private external fun nativeBindNull( + pointer: Long, + index: Int, +) + +private external fun nativeStep(pointer: Long): Boolean + +private external fun nativeGetBlob( + pointer: Long, + index: Int, +): ByteArray + +private external fun nativeGetDouble( + pointer: Long, + index: Int, +): Double + +private external fun nativeGetLong( + pointer: Long, + index: Int, +): Long + +private external fun nativeGetText( + pointer: Long, + index: Int, +): String + +private external fun nativeGetColumnCount(pointer: Long): Int + +private external fun nativeGetColumnName( + pointer: Long, + index: Int, +): String + +private external fun nativeGetColumnType( + pointer: Long, + index: Int, +): Int + +private external fun nativeReset(pointer: Long) + +private external fun nativeClearBindings(pointer: Long) + +private external fun nativeClose(pointer: Long) diff --git a/sqlite3multipleciphers/src/jvmMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.jvm.kt b/sqlite3multipleciphers/src/jvmMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.jvm.kt new file mode 100644 index 00000000..41e316d7 --- /dev/null +++ b/sqlite3multipleciphers/src/jvmMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.jvm.kt @@ -0,0 +1,18 @@ +package com.powersync.encryption + +import com.powersync.extractLib + +public class JavaEncryptedDatabaseFactory( + key: Key, +) : BundledSQLiteDriver(key) { + override fun resolveDefaultDatabasePath(dbFilename: String): String = dbFilename +} + +private val didLoadLibrary by lazy { + val path = extractLib(JavaEncryptedDatabaseFactory::class, "sqlite3mc_jni") + System.load(path) +} + +internal actual fun ensureJniLibraryLoaded() { + didLoadLibrary +} diff --git a/sqlite3multipleciphers/src/jvmTest/kotlin/com/powersync/JvmSmokeTest.kt b/sqlite3multipleciphers/src/jvmTest/kotlin/com/powersync/JvmSmokeTest.kt new file mode 100644 index 00000000..9846cc5f --- /dev/null +++ b/sqlite3multipleciphers/src/jvmTest/kotlin/com/powersync/JvmSmokeTest.kt @@ -0,0 +1,23 @@ +package com.powersync + +import com.powersync.encryption.JavaEncryptedDatabaseFactory +import com.powersync.encryption.Key +import io.kotest.matchers.shouldBe +import kotlin.test.Test +import kotlin.use + +class JvmSmokeTest { + @Test + fun linksSqlite3MultipleCiphers() { + JavaEncryptedDatabaseFactory(key).openInMemoryConnection().use { db -> + db.prepare("PRAGMA cipher").use { stmt -> + stmt.step() shouldBe true + stmt.getText(0) shouldBe "chacha20" + } + } + } + + private companion object Companion { + val key = Key.Passphrase("test") + } +} diff --git a/sqlite3multipleciphers/src/nativeMain/kotlin/com/powersync/encryption/NativeEncryptedDatabaseFactory.kt b/sqlite3multipleciphers/src/nativeMain/kotlin/com/powersync/encryption/NativeEncryptedDatabaseFactory.kt new file mode 100644 index 00000000..25084cda --- /dev/null +++ b/sqlite3multipleciphers/src/nativeMain/kotlin/com/powersync/encryption/NativeEncryptedDatabaseFactory.kt @@ -0,0 +1,25 @@ +package com.powersync.encryption + +import androidx.sqlite.SQLiteConnection +import com.powersync.appleDefaultDatabasePath +import com.powersync.db.NativeConnectionFactory + +/** + * A [NativeConnectionFactory] that links sqlite3multipleciphers and opens database with a [Key]. + */ +public class NativeEncryptedDatabaseFactory( + private val key: Key, +) : NativeConnectionFactory() { + override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) + + override fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection = + super.openConnection(path, openFlags).apply { + if (path != ":memory:") { + // Settings keys for in-memory or temporary databases is not supported. + encryptOrClose(key) + } + } +} diff --git a/sqlite3multipleciphers/src/nativeTest/kotlin/com/powersync/NativeSmokeTest.kt b/sqlite3multipleciphers/src/nativeTest/kotlin/com/powersync/NativeSmokeTest.kt new file mode 100644 index 00000000..ff662161 --- /dev/null +++ b/sqlite3multipleciphers/src/nativeTest/kotlin/com/powersync/NativeSmokeTest.kt @@ -0,0 +1,22 @@ +package com.powersync + +import com.powersync.encryption.Key +import com.powersync.encryption.NativeEncryptedDatabaseFactory +import io.kotest.matchers.shouldBe +import kotlin.test.Test + +class NativeSmokeTest { + @Test + fun linksSqlite3MultipleCiphers() { + NativeEncryptedDatabaseFactory(key).openInMemoryConnection().use { db -> + db.prepare("PRAGMA cipher").use { stmt -> + stmt.step() shouldBe true + stmt.getText(0) shouldBe "chacha20" + } + } + } + + private companion object Companion { + val key = Key.Passphrase("test") + } +} diff --git a/static-sqlite-driver/build.gradle.kts b/static-sqlite-driver/build.gradle.kts index e4fe686d..326411cc 100644 --- a/static-sqlite-driver/build.gradle.kts +++ b/static-sqlite-driver/build.gradle.kts @@ -22,6 +22,7 @@ fun linkSqliteCInterop(target: KotlinNativeTarget): TaskProvider