Skip to content

Commit

Permalink
Merge pull request #70 from OP-Engineering/sqlcipher
Browse files Browse the repository at this point in the history
Roll SQLCipher into the package
  • Loading branch information
ospfranco authored Mar 12, 2024
2 parents 4a013dd + e5107aa commit d2cd29d
Show file tree
Hide file tree
Showing 26 changed files with 266,354 additions and 130 deletions.
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

0 comments on commit d2cd29d

Please sign in to comment.