Skip to content

Commit d2cd29d

Browse files
authored
Merge pull request #70 from OP-Engineering/sqlcipher
Roll SQLCipher into the package
2 parents 4a013dd + e5107aa commit d2cd29d

26 files changed

+266354
-130
lines changed

.github/workflows/ci.yml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,102 @@ jobs:
136136
- name: Build example for iOS
137137
run: |
138138
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"
139+
140+
build-ios-sqlcipher:
141+
runs-on: self-hosted
142+
env:
143+
TURBO_CACHE_DIR: .turbo/ios
144+
steps:
145+
- name: Checkout
146+
uses: actions/checkout@v4
147+
148+
- name: Setup
149+
uses: ./.github/actions/setup
150+
151+
- name: install bundler dependencies
152+
run: |
153+
cd example
154+
bundle install
155+
156+
- name: Cache turborepo for iOS
157+
uses: actions/cache@v3
158+
with:
159+
path: ${{ env.TURBO_CACHE_DIR }}
160+
key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }}
161+
restore-keys: |
162+
${{ runner.os }}-turborepo-ios-
163+
164+
- name: Check turborepo cache for iOS
165+
run: |
166+
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")
167+
168+
if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
169+
echo "turbo_cache_hit=1" >> $GITHUB_ENV
170+
fi
171+
172+
- name: Cache cocoapods
173+
if: env.turbo_cache_hit != 1
174+
id: cocoapods-cache
175+
uses: actions/cache@v3
176+
with:
177+
path: |
178+
**/ios/Pods
179+
key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
180+
restore-keys: |
181+
${{ runner.os }}-cocoapods-
182+
183+
- name: Install cocoapods
184+
# if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
185+
run: |
186+
cd example/ios
187+
OP_SQLITE_USE_SQLCIPHER=1 bundle exec pod install
188+
env:
189+
NO_FLIPPER: 1
190+
191+
- name: Build example for iOS
192+
run: |
193+
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"
194+
195+
build-android-sqlcipher:
196+
runs-on: self-hosted
197+
env:
198+
TURBO_CACHE_DIR: .turbo/android
199+
steps:
200+
- name: Checkout
201+
uses: actions/checkout@v4
202+
203+
- name: Setup
204+
uses: ./.github/actions/setup
205+
206+
- name: Cache turborepo for Android
207+
uses: actions/cache@v3
208+
with:
209+
path: ${{ env.TURBO_CACHE_DIR }}
210+
key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }}
211+
restore-keys: |
212+
${{ runner.os }}-turborepo-android-
213+
214+
- name: Check turborepo cache for Android
215+
run: |
216+
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")
217+
218+
if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
219+
echo "turbo_cache_hit=1" >> $GITHUB_ENV
220+
fi
221+
222+
- name: Cache Gradle
223+
if: env.turbo_cache_hit != 1
224+
uses: actions/cache@v3
225+
with:
226+
path: |
227+
~/.gradle/wrapper
228+
~/.gradle/caches
229+
key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
230+
restore-keys: |
231+
${{ runner.os }}-gradle-
232+
233+
- name: Build example for Android
234+
env:
235+
JAVA_OPTS: '-XX:MaxHeapSize=6g'
236+
run: |
237+
OP_SQLITE_USE_SQLCIPHER=1 yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"

.yarn/install-state.gz

-4.07 KB
Binary file not shown.

android/CMakeLists.txt

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ set (CMAKE_VERBOSE_MAKEFILE ON)
66
set (CMAKE_CXX_STANDARD 17)
77
set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
88

9-
include_directories(
10-
../cpp
11-
)
9+
include_directories(
10+
../cpp
11+
../cpp/sqlcipher
12+
)
1213

1314
add_definitions(
1415
${SQLITE_FLAGS}
@@ -21,8 +22,6 @@ add_library(
2122
../cpp/bridge.h
2223
../cpp/bindings.cpp
2324
../cpp/bindings.h
24-
../cpp/sqlite3.h
25-
../cpp/sqlite3.c
2625
../cpp/utils.h
2726
../cpp/utils.cpp
2827
../cpp/ThreadPool.h
@@ -40,6 +39,20 @@ add_library(
4039
cpp-adapter.cpp
4140
)
4241

42+
if (OP_SQLITE_USE_SQLCIPHER)
43+
target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlcipher/sqlite3.h ../cpp/sqlcipher/sqlite3.c)
44+
45+
add_definitions(
46+
-DOP_SQLITE_USE_SQLCIPHER
47+
-DSQLITE_HAS_CODEC
48+
-DSQLITE_TEMP_STORE=2
49+
)
50+
51+
find_package(openssl REQUIRED CONFIG)
52+
else()
53+
target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlite3.h ../cpp/sqlite3.c)
54+
endif()
55+
4356
set_target_properties(
4457
${PACKAGE_NAME} PROPERTIES
4558
CXX_STANDARD 17
@@ -49,12 +62,19 @@ set_target_properties(
4962

5063
find_package(ReactAndroid REQUIRED CONFIG)
5164
find_package(fbjni REQUIRED CONFIG)
65+
find_library(LOG_LIB log)
66+
5267

5368
target_link_libraries(
5469
${PACKAGE_NAME}
70+
${LOG_LIB}
5571
fbjni::fbjni
5672
ReactAndroid::jsi
5773
ReactAndroid::turbomodulejsijni
5874
ReactAndroid::react_nativemodule_core
5975
android
6076
)
77+
78+
if (OP_SQLITE_USE_SQLCIPHER)
79+
target_link_libraries(${PACKAGE_NAME} PRIVATE openssl::crypto)
80+
endif()

android/build.gradle

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,23 @@ android {
7878
cmake {
7979
if(System.getenv("OP_SQLITE_PERF") == '1') {
8080
println "OP-SQLITE performance mode enabled! 🚀"
81-
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"
81+
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"]
8282
}
8383
if(System.getenv("OP_SQLITE_PERF") == '2') {
8484
println "OP-SQLITE (thread safe) performance mode enabled! 🚀"
85-
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"
85+
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"]
8686
}
87+
88+
if(System.getenv("OP_SQLITE_USE_SQLCIPHER") == '1') {
89+
println "OP-SQLITE using SQLCipher! 🔒"
90+
cFlags += "-DOP_SQLITE_USE_SQLCIPHER=1"
91+
}
92+
8793
cppFlags "-O2", "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID"
8894
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
89-
arguments '-DANDROID_STL=c++_shared',
90-
"-DSQLITE_FLAGS='${SQLITE_FLAGS ? SQLITE_FLAGS : ''}'",
91-
"-DUSE_HERMES=${USE_HERMES}"
95+
arguments "-DANDROID_STL=c++_shared",
96+
"-DSQLITE_FLAGS='${SQLITE_FLAGS ? SQLITE_FLAGS : ''}'"
97+
"-DOP_SQLITE_USE_SQLCIPHER='${System.getenv("OP_SQLITE_USE_SQLCIPHER") == '1'? 1 : 0}'"
9298
abiFilters (*reactNativeArchitectures())
9399
}
94100
}
@@ -134,10 +140,12 @@ repositories {
134140
}
135141

136142
def kotlin_version = getExtOrDefault("kotlinVersion")
137-
138143
dependencies {
139144
implementation 'com.facebook.react:react-native'
140145
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
146+
if (System.getenv("OP_SQLITE_USE_SQLCIPHER") == '1') {
147+
implementation('com.android.ndk.thirdparty:openssl:1.1.1q-beta-1')
148+
}
141149
}
142150

143151
// Resolves "LOCAL_SRC_FILES points to a missing file, Check that libfb.so exists or that its path is correct".

cpp/bindings.cpp

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#include "sqlbatchexecutor.h"
99
#include "utils.h"
1010
#include <iostream>
11-
#include <sqlite3.h>
1211
#include <string>
1312
#include <unordered_map>
1413
#include <vector>
@@ -55,32 +54,50 @@ void install(jsi::Runtime &rt,
5554
throw std::runtime_error("[op-sqlite][open] database name is required");
5655
}
5756

58-
if (!args[0].isString()) {
59-
throw std::runtime_error(
60-
"[op-sqlite][open] database name must be a string");
61-
}
62-
63-
std::string dbName = args[0].asString(rt).utf8(rt);
57+
jsi::Object options = args[0].asObject(rt);
58+
std::string dbName = options.getProperty(rt, "name").asString(rt).utf8(rt);
6459
std::string path = std::string(basePath);
60+
std::string location;
61+
std::string encryptionKey;
6562

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

72-
std::string lastPath = args[1].asString(rt).utf8(rt);
67+
if (options.hasProperty(rt, "encryptionKey")) {
68+
encryptionKey =
69+
options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt);
70+
}
7371

74-
if (lastPath == ":memory:") {
72+
#ifdef OP_SQLITE_USE_SQLCIPHER
73+
if (encryptionKey.empty()) {
74+
throw std::runtime_error(
75+
"[OP SQLite] using SQLCipher encryption key is required");
76+
}
77+
// TODO(osp) find a way to display the yellow box from c++
78+
#else
79+
// if (!encryptionKey.empty()) {
80+
// // RCTLogWarn(@"Your message")
81+
// throw std::runtime_error("[OP SQLite] SQLCipher is not enabled, "
82+
// "encryption key is not allowed");
83+
// }
84+
#endif
85+
86+
if (!location.empty()) {
87+
if (location == ":memory:") {
7588
path = ":memory:";
76-
} else if (lastPath.rfind("/", 0) == 0) {
77-
path = lastPath;
89+
} else if (location.rfind("/", 0) == 0) {
90+
path = location;
7891
} else {
79-
path = path + "/" + lastPath;
92+
path = path + "/" + location;
8093
}
8194
}
8295

96+
#ifdef OP_SQLITE_USE_SQLCIPHER
97+
BridgeResult result = opsqlite_open(dbName, path, encryptionKey);
98+
#else
8399
BridgeResult result = opsqlite_open(dbName, path);
100+
#endif
84101

85102
if (result.type == SQLiteError) {
86103
throw std::runtime_error(result.message);

cpp/bridge.cpp

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,15 @@ std::string get_db_path(std::string const &db_name,
4141
return location + "/" + db_name;
4242
}
4343

44+
#ifdef OP_SQLITE_USE_SQLCIPHER
4445
BridgeResult opsqlite_open(std::string const &dbName,
45-
std::string const &lastPath) {
46-
std::string dbPath = get_db_path(dbName, lastPath);
46+
std::string const &last_path,
47+
std::string const &encryptionKey) {
48+
#else
49+
BridgeResult opsqlite_open(std::string const &dbName,
50+
std::string const &last_path) {
51+
#endif
52+
std::string dbPath = get_db_path(dbName, last_path);
4753

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

5965
dbMap[dbName] = db;
60-
66+
#ifdef OP_SQLITE_USE_SQLCIPHER
67+
auto encryptionResult =
68+
opsqlite_execute(dbName, "PRAGMA key = '" + encryptionKey + "'", nullptr,
69+
nullptr, nullptr);
70+
LOGD("Encrypting database");
71+
#endif
6172
return BridgeResult{.type = SQLiteOk, .affectedRows = 0};
6273
}
6374

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

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

@@ -359,15 +370,15 @@ opsqlite_execute(std::string const &dbName, std::string const &query,
359370
const char *message = sqlite3_errmsg(db);
360371
return {
361372
.type = SQLiteError,
362-
.message = "[op-sqlite] SQL statement error:" +
363-
std::to_string(statementStatus) +
364-
" description:" + std::string(message) +
373+
.message = "[op-sqlite] SQL statement error on opsqlite_execute:\n" +
374+
std::to_string(statementStatus) + " description:\n" +
375+
std::string(message) +
365376
". See error codes: https://www.sqlite.org/rescode.html",
366377
};
367378
}
368379

369-
// The statement did not fail to parse but there is nothing to do, just skip
370-
// to the end
380+
// The statement did not fail to parse but there is nothing to do, just
381+
// skip to the end
371382
if (statement == NULL) {
372383
continue;
373384
}
@@ -502,8 +513,8 @@ opsqlite_execute(std::string const &dbName, std::string const &query,
502513
.insertId = static_cast<double>(latestInsertRowId)};
503514
}
504515

505-
/// Executes returning data in raw arrays, a small performance optimization for
506-
/// certain use cases
516+
/// Executes returning data in raw arrays, a small performance optimization
517+
/// for certain use cases
507518
BridgeResult
508519
opsqlite_execute_raw(std::string const &dbName, std::string const &query,
509520
const std::vector<JSVariant> *params,
@@ -540,8 +551,8 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
540551
};
541552
}
542553

543-
// The statement did not fail to parse but there is nothing to do, just skip
544-
// to the end
554+
// The statement did not fail to parse but there is nothing to do, just
555+
// skip to the end
545556
if (statement == NULL) {
546557
continue;
547558
}
@@ -661,8 +672,8 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
661672

662673
void opsqlite_close_all() {
663674
for (auto const &x : dbMap) {
664-
// Interrupt will make all pending operations to fail with SQLITE_INTERRUPT
665-
// The ongoing work from threads will then fail ASAP
675+
// Interrupt will make all pending operations to fail with
676+
// SQLITE_INTERRUPT The ongoing work from threads will then fail ASAP
666677
sqlite3_interrupt(x.second);
667678
// Each DB connection can then be safely interrupted
668679
sqlite3_close_v2(x.second);

cpp/bridge.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ typedef std::function<void(std::string dbName, std::string tableName,
1919
typedef std::function<void(std::string dbName)> CommitCallback;
2020
typedef std::function<void(std::string dbName)> RollbackCallback;
2121

22+
#ifdef OP_SQLITE_USE_SQLCIPHER
23+
BridgeResult opsqlite_open(std::string const &dbName, std::string const &dbPath,
24+
std::string const &encryptionKey);
25+
#else
2226
BridgeResult opsqlite_open(std::string const &dbName,
2327
std::string const &dbPath);
28+
#endif
2429

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

0 commit comments

Comments
 (0)