Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roll SQLCipher into the package #70

Merged
merged 7 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,102 @@ jobs:
- name: Build example for iOS
run: |
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"

build-ios-sqlcipher:
runs-on: self-hosted
env:
TURBO_CACHE_DIR: .turbo/ios
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: install bundler dependencies
run: |
cd example
bundle install

- name: Cache turborepo for iOS
uses: actions/cache@v3
with:
path: ${{ env.TURBO_CACHE_DIR }}
key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-turborepo-ios-

- name: Check turborepo cache for iOS
run: |
TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status")

if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
echo "turbo_cache_hit=1" >> $GITHUB_ENV
fi

- name: Cache cocoapods
if: env.turbo_cache_hit != 1
id: cocoapods-cache
uses: actions/cache@v3
with:
path: |
**/ios/Pods
key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-cocoapods-

- name: Install cocoapods
# if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
run: |
cd example/ios
OP_SQLITE_USE_SQLCIPHER=1 bundle exec pod install
env:
NO_FLIPPER: 1

- name: Build example for iOS
run: |
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"

build-android-sqlcipher:
runs-on: self-hosted
env:
TURBO_CACHE_DIR: .turbo/android
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: Cache turborepo for Android
uses: actions/cache@v3
with:
path: ${{ env.TURBO_CACHE_DIR }}
key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-turborepo-android-

- name: Check turborepo cache for Android
run: |
TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status")

if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
echo "turbo_cache_hit=1" >> $GITHUB_ENV
fi

- name: Cache Gradle
if: env.turbo_cache_hit != 1
uses: actions/cache@v3
with:
path: |
~/.gradle/wrapper
~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Build example for Android
env:
JAVA_OPTS: '-XX:MaxHeapSize=6g'
run: |
OP_SQLITE_USE_SQLCIPHER=1 yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"
Binary file modified .yarn/install-state.gz
Binary file not shown.
30 changes: 25 additions & 5 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 17)
set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build)

include_directories(
../cpp
)
include_directories(
../cpp
../cpp/sqlcipher
)

add_definitions(
${SQLITE_FLAGS}
Expand All @@ -21,8 +22,6 @@ add_library(
../cpp/bridge.h
../cpp/bindings.cpp
../cpp/bindings.h
../cpp/sqlite3.h
../cpp/sqlite3.c
../cpp/utils.h
../cpp/utils.cpp
../cpp/ThreadPool.h
Expand All @@ -40,6 +39,20 @@ add_library(
cpp-adapter.cpp
)

if (OP_SQLITE_USE_SQLCIPHER)
target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlcipher/sqlite3.h ../cpp/sqlcipher/sqlite3.c)

add_definitions(
-DOP_SQLITE_USE_SQLCIPHER
-DSQLITE_HAS_CODEC
-DSQLITE_TEMP_STORE=2
)

find_package(openssl REQUIRED CONFIG)
else()
target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlite3.h ../cpp/sqlite3.c)
endif()

set_target_properties(
${PACKAGE_NAME} PROPERTIES
CXX_STANDARD 17
Expand All @@ -49,12 +62,19 @@ set_target_properties(

find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG)
find_library(LOG_LIB log)


target_link_libraries(
${PACKAGE_NAME}
${LOG_LIB}
fbjni::fbjni
ReactAndroid::jsi
ReactAndroid::turbomodulejsijni
ReactAndroid::react_nativemodule_core
android
)

if (OP_SQLITE_USE_SQLCIPHER)
target_link_libraries(${PACKAGE_NAME} PRIVATE openssl::crypto)
endif()
20 changes: 14 additions & 6 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,23 @@ android {
cmake {
if(System.getenv("OP_SQLITE_PERF") == '1') {
println "OP-SQLITE performance mode enabled! 🚀"
cFlags "-DSQLITE_DQS=0", "-DSQLITE_THREADSAFE=0", "-DSQLITE_DEFAULT_MEMSTATUS=0", "-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1", "-DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1", "-DSQLITE_MAX_EXPR_DEPTH=0", "-DSQLITE_OMIT_DEPRECATED=1", "-DSQLITE_OMIT_PROGRESS_CALLBACK=1", "-DSQLITE_OMIT_SHARED_CACHE=1", "-DSQLITE_USE_ALLOCA=1"
cFlags += ["-DSQLITE_DQS=0", "-DSQLITE_THREADSAFE=0", "-DSQLITE_DEFAULT_MEMSTATUS=0", "-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1", "-DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1", "-DSQLITE_MAX_EXPR_DEPTH=0", "-DSQLITE_OMIT_DEPRECATED=1", "-DSQLITE_OMIT_PROGRESS_CALLBACK=1", "-DSQLITE_OMIT_SHARED_CACHE=1", "-DSQLITE_USE_ALLOCA=1"]
}
if(System.getenv("OP_SQLITE_PERF") == '2') {
println "OP-SQLITE (thread safe) performance mode enabled! 🚀"
cFlags "-DSQLITE_DQS=0", "-DSQLITE_THREADSAFE=1", "-DSQLITE_DEFAULT_MEMSTATUS=0", "-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1", "-DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1", "-DSQLITE_MAX_EXPR_DEPTH=0", "-DSQLITE_OMIT_DEPRECATED=1", "-DSQLITE_OMIT_PROGRESS_CALLBACK=1", "-DSQLITE_OMIT_SHARED_CACHE=1", "-DSQLITE_USE_ALLOCA=1"
cFlags += ["-DSQLITE_DQS=0", "-DSQLITE_THREADSAFE=1", "-DSQLITE_DEFAULT_MEMSTATUS=0", "-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1", "-DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1", "-DSQLITE_MAX_EXPR_DEPTH=0", "-DSQLITE_OMIT_DEPRECATED=1", "-DSQLITE_OMIT_PROGRESS_CALLBACK=1", "-DSQLITE_OMIT_SHARED_CACHE=1", "-DSQLITE_USE_ALLOCA=1"]
}

if(System.getenv("OP_SQLITE_USE_SQLCIPHER") == '1') {
println "OP-SQLITE using SQLCipher! 🔒"
cFlags += "-DOP_SQLITE_USE_SQLCIPHER=1"
}

cppFlags "-O2", "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID"
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
arguments '-DANDROID_STL=c++_shared',
"-DSQLITE_FLAGS='${SQLITE_FLAGS ? SQLITE_FLAGS : ''}'",
"-DUSE_HERMES=${USE_HERMES}"
arguments "-DANDROID_STL=c++_shared",
"-DSQLITE_FLAGS='${SQLITE_FLAGS ? SQLITE_FLAGS : ''}'"
"-DOP_SQLITE_USE_SQLCIPHER='${System.getenv("OP_SQLITE_USE_SQLCIPHER") == '1'? 1 : 0}'"
abiFilters (*reactNativeArchitectures())
}
}
Expand Down Expand Up @@ -134,10 +140,12 @@ repositories {
}

def kotlin_version = getExtOrDefault("kotlinVersion")

dependencies {
implementation 'com.facebook.react:react-native'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
if (System.getenv("OP_SQLITE_USE_SQLCIPHER") == '1') {
implementation('com.android.ndk.thirdparty:openssl:1.1.1q-beta-1')
}
}

// Resolves "LOCAL_SRC_FILES points to a missing file, Check that libfb.so exists or that its path is correct".
Expand Down
51 changes: 34 additions & 17 deletions cpp/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "sqlbatchexecutor.h"
#include "utils.h"
#include <iostream>
#include <sqlite3.h>
#include <string>
#include <unordered_map>
#include <vector>
Expand Down Expand Up @@ -55,32 +54,50 @@ void install(jsi::Runtime &rt,
throw std::runtime_error("[op-sqlite][open] database name is required");
}

if (!args[0].isString()) {
throw std::runtime_error(
"[op-sqlite][open] database name must be a string");
}

std::string dbName = args[0].asString(rt).utf8(rt);
jsi::Object options = args[0].asObject(rt);
std::string dbName = options.getProperty(rt, "name").asString(rt).utf8(rt);
std::string path = std::string(basePath);
std::string location;
std::string encryptionKey;

if (count > 1 && !args[1].isUndefined() && !args[1].isNull()) {
if (!args[1].isString()) {
throw std::runtime_error(
"[op-sqlite][open] database location must be a string");
}
if (options.hasProperty(rt, "location")) {
location = options.getProperty(rt, "location").asString(rt).utf8(rt);
}

std::string lastPath = args[1].asString(rt).utf8(rt);
if (options.hasProperty(rt, "encryptionKey")) {
encryptionKey =
options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt);
}

if (lastPath == ":memory:") {
#ifdef OP_SQLITE_USE_SQLCIPHER
if (encryptionKey.empty()) {
throw std::runtime_error(
"[OP SQLite] using SQLCipher encryption key is required");
}
// TODO(osp) find a way to display the yellow box from c++
#else
// if (!encryptionKey.empty()) {
// // RCTLogWarn(@"Your message")
// throw std::runtime_error("[OP SQLite] SQLCipher is not enabled, "
// "encryption key is not allowed");
// }
#endif

if (!location.empty()) {
if (location == ":memory:") {
path = ":memory:";
} else if (lastPath.rfind("/", 0) == 0) {
path = lastPath;
} else if (location.rfind("/", 0) == 0) {
path = location;
} else {
path = path + "/" + lastPath;
path = path + "/" + location;
}
}

#ifdef OP_SQLITE_USE_SQLCIPHER
BridgeResult result = opsqlite_open(dbName, path, encryptionKey);
#else
BridgeResult result = opsqlite_open(dbName, path);
#endif

if (result.type == SQLiteError) {
throw std::runtime_error(result.message);
Expand Down
41 changes: 26 additions & 15 deletions cpp/bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ std::string get_db_path(std::string const &db_name,
return location + "/" + db_name;
}

#ifdef OP_SQLITE_USE_SQLCIPHER
BridgeResult opsqlite_open(std::string const &dbName,
std::string const &lastPath) {
std::string dbPath = get_db_path(dbName, lastPath);
std::string const &last_path,
std::string const &encryptionKey) {
#else
BridgeResult opsqlite_open(std::string const &dbName,
std::string const &last_path) {
#endif
std::string dbPath = get_db_path(dbName, last_path);

int sqlOpenFlags =
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
Expand All @@ -57,7 +63,12 @@ BridgeResult opsqlite_open(std::string const &dbName,
}

dbMap[dbName] = db;

#ifdef OP_SQLITE_USE_SQLCIPHER
auto encryptionResult =
opsqlite_execute(dbName, "PRAGMA key = '" + encryptionKey + "'", nullptr,
nullptr, nullptr);
LOGD("Encrypting database");
#endif
return BridgeResult{.type = SQLiteOk, .affectedRows = 0};
}

Expand Down Expand Up @@ -321,7 +332,7 @@ sqlite3_stmt *opsqlite_prepare_statement(std::string const &dbName,

if (statementStatus == SQLITE_ERROR) {
const char *message = sqlite3_errmsg(db);
throw std::runtime_error("[op-sqlite] SQL statement error: " +
throw std::runtime_error("[op-sqlite] SQL prepare statement error: " +
std::string(message));
}

Expand Down Expand Up @@ -359,15 +370,15 @@ opsqlite_execute(std::string const &dbName, std::string const &query,
const char *message = sqlite3_errmsg(db);
return {
.type = SQLiteError,
.message = "[op-sqlite] SQL statement error:" +
std::to_string(statementStatus) +
" description:" + std::string(message) +
.message = "[op-sqlite] SQL statement error on opsqlite_execute:\n" +
std::to_string(statementStatus) + " description:\n" +
std::string(message) +
". See error codes: https://www.sqlite.org/rescode.html",
};
}

// The statement did not fail to parse but there is nothing to do, just skip
// to the end
// The statement did not fail to parse but there is nothing to do, just
// skip to the end
if (statement == NULL) {
continue;
}
Expand Down Expand Up @@ -502,8 +513,8 @@ opsqlite_execute(std::string const &dbName, std::string const &query,
.insertId = static_cast<double>(latestInsertRowId)};
}

/// Executes returning data in raw arrays, a small performance optimization for
/// certain use cases
/// Executes returning data in raw arrays, a small performance optimization
/// for certain use cases
BridgeResult
opsqlite_execute_raw(std::string const &dbName, std::string const &query,
const std::vector<JSVariant> *params,
Expand Down Expand Up @@ -540,8 +551,8 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
};
}

// The statement did not fail to parse but there is nothing to do, just skip
// to the end
// The statement did not fail to parse but there is nothing to do, just
// skip to the end
if (statement == NULL) {
continue;
}
Expand Down Expand Up @@ -661,8 +672,8 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,

void opsqlite_close_all() {
for (auto const &x : dbMap) {
// Interrupt will make all pending operations to fail with SQLITE_INTERRUPT
// The ongoing work from threads will then fail ASAP
// Interrupt will make all pending operations to fail with
// SQLITE_INTERRUPT The ongoing work from threads will then fail ASAP
sqlite3_interrupt(x.second);
// Each DB connection can then be safely interrupted
sqlite3_close_v2(x.second);
Expand Down
5 changes: 5 additions & 0 deletions cpp/bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ typedef std::function<void(std::string dbName, std::string tableName,
typedef std::function<void(std::string dbName)> CommitCallback;
typedef std::function<void(std::string dbName)> RollbackCallback;

#ifdef OP_SQLITE_USE_SQLCIPHER
BridgeResult opsqlite_open(std::string const &dbName, std::string const &dbPath,
std::string const &encryptionKey);
#else
BridgeResult opsqlite_open(std::string const &dbName,
std::string const &dbPath);
#endif

BridgeResult opsqlite_close(std::string const &dbName);

Expand Down
Loading
Loading