diff --git a/sqlite3/assets/wasm/CMakeLists.txt b/sqlite3/assets/wasm/CMakeLists.txt index 71f5a747..3cdb4717 100644 --- a/sqlite3/assets/wasm/CMakeLists.txt +++ b/sqlite3/assets/wasm/CMakeLists.txt @@ -12,7 +12,7 @@ include(FetchContent) FetchContent_Declare( sqlite3 # NOTE: When changing this, also update `test/wasm/sqlite3_test.dart` - URL https://sqlite.org/2024/sqlite-autoconf-3470200.tar.gz + URL https://github.com/utelle/SQLite3MultipleCiphers/releases/download/v1.9.2/sqlite3mc-1.9.2-sqlite-3.47.2-amalgamation.zip DOWNLOAD_EXTRACT_TIMESTAMP NEW ) @@ -38,13 +38,13 @@ macro(base_sqlite3_target name debug) set(sources ${CMAKE_CURRENT_SOURCE_DIR}/os_web.c ${CMAKE_CURRENT_SOURCE_DIR}/helpers.c - ${sqlite3_SOURCE_DIR}/sqlite3.c + ${sqlite3_SOURCE_DIR}/sqlite3mc_amalgamation.c ) set(flags -Wall -Wextra -Wno-unused-parameter -Wno-unused-function) if(${debug}) list(APPEND sources "${CMAKE_BINARY_DIR}/vfstrace.c") - list(APPEND flags "-g" "-DDEBUG") + list(APPEND flags "-g" "-DDEBUG" "-O1") else() list(APPEND flags "-Oz" "-DNDEBUG" "-flto") endif() @@ -56,6 +56,8 @@ macro(base_sqlite3_target name debug) -o ${clang_output} -I ${PROJECT_SOURCE_DIR} -I ${sqlite3_SOURCE_DIR} -D_HAVE_SQLITE_CONFIG_H + -DSQLITE_OMIT_AUTOINIT + -D__WASM__ -mcpu=generic -mexec-model=reactor -fno-stack-protector -fno-stack-clash-protection diff --git a/sqlite3/assets/wasm/helpers.c b/sqlite3/assets/wasm/helpers.c index 380104ca..90b4e992 100644 --- a/sqlite3/assets/wasm/helpers.c +++ b/sqlite3/assets/wasm/helpers.c @@ -187,7 +187,11 @@ SQLITE_API sqlite3_vfs *dart_sqlite3_register_vfs(const char *name, int dartId, vfstrace_register(traceName, name, &dartvfs_trace_log1, NULL, makeDefault); #else // Just register the VFS as is. - sqlite3_vfs_register(vfs, makeDefault); + int rc = sqlite3_vfs_register(vfs, makeDefault); + if (rc) { + free(vfs); + return NULL; + } #endif return vfs; } diff --git a/sqlite3/assets/wasm/os_web.c b/sqlite3/assets/wasm/os_web.c index 98004432..35e6db42 100644 --- a/sqlite3/assets/wasm/os_web.c +++ b/sqlite3/assets/wasm/os_web.c @@ -5,6 +5,15 @@ #include "bridge.h" #include "sqlite3.h" -int sqlite3_os_init(void) { return SQLITE_OK; } +extern int sqlite3_powersync_init(sqlite3 *db, char **pzErrMsg, + const sqlite3_api_routines *pApi); + +int getentropy(void* buf, size_t n) { + return xRandomness(-1, (int) n, (char*) buf); +} + +int sqlite3_os_init(void) { + return SQLITE_OK; +} int sqlite3_os_end(void) { return SQLITE_OK; } diff --git a/sqlite3/example/web/main.dart b/sqlite3/example/web/main.dart index f7bf0bfc..9adb623c 100644 --- a/sqlite3/example/web/main.dart +++ b/sqlite3/example/web/main.dart @@ -15,11 +15,12 @@ Future main() async { print(sqlite3.version); sqlite3.registerVirtualFileSystem( - await IndexedDbFileSystem.open(dbName: 'sqlite3-example'), + InMemoryFileSystem(), makeDefault: true, ); - sqlite3.open('/database') + sqlite3.open('/database', vfs: 'multipleciphers-dart-memory') + ..execute("pragma key = 'test';") ..execute('pragma user_version = 1') ..execute('CREATE TABLE foo (bar INTEGER NOT NULL);') ..execute('INSERT INTO foo (bar) VALUES (?)', [3]) diff --git a/sqlite3/lib/src/vfs.dart b/sqlite3/lib/src/vfs.dart index 7cad5883..88395584 100644 --- a/sqlite3/lib/src/vfs.dart +++ b/sqlite3/lib/src/vfs.dart @@ -132,18 +132,30 @@ abstract base class BaseVirtualFileSystem extends VirtualFileSystem { final Random random; BaseVirtualFileSystem({Random? random, required String name}) - : random = random ?? Random.secure(), + : random = random ?? _fallbackRandom, super(name); @override void xRandomness(Uint8List target) { + generateRandomness(target, random); + } + + @override + DateTime xCurrentTime() => DateTime.now(); + + /// Fills [target] with random bytes. + /// + /// An optional [random] source can be provided, otherwise a default instance + /// of [Random.secure] will be used. + static void generateRandomness(Uint8List target, [Random? random]) { + random ??= _fallbackRandom; + for (var i = 0; i < target.length; i++) { target[i] = random.nextInt(1 << 8); } } - @override - DateTime xCurrentTime() => DateTime.now(); + static final Random _fallbackRandom = Random.secure(); } /// A [VirtualFileSystemFile] base class that implements [xRead] to zero-fill diff --git a/sqlite3/lib/src/wasm/bindings.dart b/sqlite3/lib/src/wasm/bindings.dart index f6245d2d..bf210817 100644 --- a/sqlite3/lib/src/wasm/bindings.dart +++ b/sqlite3/lib/src/wasm/bindings.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:sqlite3/src/vfs.dart'; import '../constants.dart'; +import '../exception.dart'; import '../functions.dart'; import '../implementation/bindings.dart'; import 'wasm_interop.dart' as wasm; @@ -55,6 +56,8 @@ final class WasmSqliteBindings extends RawSqliteBindings { @override SqliteResult sqlite3_open_v2( String name, int flags, String? zVfs) { + sqlite3_initialize(); + final namePtr = bindings.allocateZeroTerminated(name); final outDb = bindings.malloc(wasm.WasmBindings.pointerSize); final vfsPtr = zVfs == null ? 0 : bindings.allocateZeroTerminated(zVfs); @@ -76,12 +79,23 @@ final class WasmSqliteBindings extends RawSqliteBindings { return bindings.memory.readString(bindings.sqlite3_sourceid()); } + void sqlite3_initialize() { + final rc = bindings.sqlite3_initialize(); + if (rc != 0) { + throw SqliteException(rc, 'sqlite3_initialize call failed'); + } + } + @override void registerVirtualFileSystem(VirtualFileSystem vfs, int makeDefault) { final name = bindings.allocateZeroTerminated(vfs.name); final id = bindings.callbacks.registerVfs(vfs); final ptr = bindings.dart_sqlite3_register_vfs(name, id, makeDefault); + if (ptr == 0) { + throw StateError('could not register vfs'); + } + sqlite3_initialize(); DartCallbacks.sqliteVfsPointer[vfs] = ptr; } diff --git a/sqlite3/lib/src/wasm/wasm_interop.dart b/sqlite3/lib/src/wasm/wasm_interop.dart index 75a1ca94..1f9d1d68 100644 --- a/sqlite3/lib/src/wasm/wasm_interop.dart +++ b/sqlite3/lib/src/wasm/wasm_interop.dart @@ -83,7 +83,7 @@ class WasmBindings { _sqlite3_stmt_readonly, _sqlite3_stmt_isexplain; - final JSFunction? _sqlite3_db_config; + final JSFunction? _sqlite3_db_config, _sqlite3_initialize; final Global _sqlite3_temp_directory; @@ -160,7 +160,9 @@ class WasmBindings { _sqlite3_stmt_isexplain = instance.functions['sqlite3_stmt_isexplain']!, _sqlite3_stmt_readonly = instance.functions['sqlite3_stmt_readonly']!, _sqlite3_db_config = instance.functions['dart_sqlite3_db_config_int'], + _sqlite3_initialize = instance.functions['sqlite3_initialize'], _sqlite3_temp_directory = instance.globals['sqlite3_temp_directory']! + // Note when adding new fields: We remove functions from the wasm module that // aren't referenced in Dart. We consider a symbol used when it appears in a // string literal in an initializer of this constructor (`tool/wasm_dce.dart`). @@ -210,6 +212,13 @@ class WasmBindings { void sqlite3_free(Pointer ptr) => _sqlite3_free.callReturningVoid(ptr.toJS); + int sqlite3_initialize() { + return switch (_sqlite3_initialize) { + final fun? => fun.callReturningInt0(), + null => 0, + }; + } + int create_scalar_function( Pointer db, Pointer functionName, int nArg, int eTextRep, int id) { return _create_scalar.callReturningInt5( @@ -601,10 +610,18 @@ class _InjectedValues { }); }).toJS, 'xRandomness': ((int vfsId, int nByte, Pointer zOut) { - final vfs = callbacks.registeredVfs[vfsId]!; + final vfs = callbacks.registeredVfs[vfsId]; return _runVfs(() { - vfs.xRandomness(memory.buffer.toDart.asUint8List(zOut, nByte)); + final target = memory.buffer.toDart.asUint8List(zOut, nByte); + + if (vfs != null) { + vfs.xRandomness(target); + } else { + // Fall back to a default random source. We're using this to + // implement `getentropy` in C which is used by sqlite3mc. + return BaseVirtualFileSystem.generateRandomness(target); + } }); }).toJS, 'xSleep': ((int vfsId, int micros) {