Modify YapDatabase to read converted database.
This commit is contained in:
parent
3cd1b2c96c
commit
d0f1706a49
|
@ -9,6 +9,7 @@
|
|||
#import <SignalServiceKit/YapDatabaseConnection+OWS.h>
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
#import <YapDatabase/YapDatabasePrivate.h>
|
||||
#import <SignalServiceKit/NSData+hexString.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -24,6 +25,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
+ (BOOL)doesDatabaseNeedToBeConverted:(NSString *)databaseFilePath;
|
||||
|
||||
+ (NSData *)readFirstNBytesOfDatabaseFile:(NSString *)filePath byteCount:(NSUInteger)byteCount;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
@ -133,6 +136,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
|
||||
- (void)createTestDatabase:(NSString *)databaseFilePath databasePassword:(NSData *)databasePassword
|
||||
databaseSalt:(NSData *_Nullable)databaseSalt
|
||||
{
|
||||
OWSAssert(databaseFilePath.length > 0);
|
||||
OWSAssert(databasePassword.length > 0);
|
||||
|
@ -141,8 +145,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
[self openYapDatabase:databaseFilePath
|
||||
databasePassword:databasePassword
|
||||
databaseSalt:nil
|
||||
databaseSalt:databaseSalt
|
||||
databaseBlock:^(YapDatabase *database) {
|
||||
[self logHeaderOfDatabaseFile:databaseFilePath
|
||||
label:@"mid-creation"];
|
||||
|
||||
YapDatabaseConnection *dbConnection = database.newConnection;
|
||||
[dbConnection setObject:@(YES) forKey:@"test_key_name" inCollection:@"test_collection_name"];
|
||||
[dbConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:nil];
|
||||
|
@ -187,18 +194,31 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
databaseSalt:nil];
|
||||
}
|
||||
|
||||
- (NSString *)createTempDatabaseFilePath
|
||||
{
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
NSString *filename = [[NSUUID UUID].UUIDString stringByAppendingString:@".sqlite"];
|
||||
NSString *databaseFilePath = [temporaryDirectory stringByAppendingPathComponent:filename];
|
||||
|
||||
DDLogInfo(@"%@ databaseFilePath: %@", self.logTag, databaseFilePath);
|
||||
[DDLog flushLog];
|
||||
|
||||
return databaseFilePath;
|
||||
}
|
||||
|
||||
// If databaseSalt is nil, creates a non-converted database.
|
||||
// Otherwise creates a pre-converted database.
|
||||
- (nullable NSString *)createDatabase:(NSData *)databasePassword
|
||||
databaseSalt:(NSData *_Nullable)databaseSalt
|
||||
{
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
NSString *filename = [[NSUUID UUID].UUIDString stringByAppendingString:@".sqlite"];
|
||||
NSString *databaseFilePath = [temporaryDirectory stringByAppendingPathComponent:filename];
|
||||
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
||||
|
||||
DDLogInfo(@"%@ databaseFilePath: %@", self.logTag, databaseFilePath);
|
||||
[self createTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
|
||||
|
||||
[self createTestDatabase:databaseFilePath databasePassword:databasePassword];
|
||||
[self logHeaderOfDatabaseFile:databaseFilePath
|
||||
label:@"created"];
|
||||
|
||||
[DDLog flushLog];
|
||||
|
||||
BOOL isValid = [self verifyTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
|
||||
OWSAssert(isValid);
|
||||
|
@ -217,7 +237,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)testDoesDatabaseNeedToBeConverted_Converted
|
||||
{
|
||||
// TODO: When we can create converted databases.
|
||||
NSData *databasePassword = [self randomDatabasePassword];
|
||||
NSData *databaseSalt = [self randomDatabaseSalt];
|
||||
NSString *_Nullable databaseFilePath = [self createDatabase:databasePassword
|
||||
databaseSalt:databaseSalt];
|
||||
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
|
||||
}
|
||||
|
||||
// Verifies that legacy users with non-converted databases can convert.
|
||||
|
@ -276,6 +300,507 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
XCTAssertTrue(isValid);
|
||||
}
|
||||
|
||||
- (void)testConversionWithoutYapDatabase
|
||||
{
|
||||
sqlite3 *db;
|
||||
sqlite3_stmt *stmt;
|
||||
const int ROWSTOINSERT = 3;
|
||||
|
||||
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
||||
|
||||
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
||||
|
||||
NSData *keyData = [self randomDatabasePassword];
|
||||
|
||||
/* Step 1. Create a new encrypted database. */
|
||||
|
||||
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
||||
|
||||
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
for(int row = 0; row < ROWSTOINSERT; row++) {
|
||||
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_step(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_DONE);
|
||||
rc = sqlite3_reset(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
rc = sqlite3_finalize(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
NSString *salt = [self executeSingleStringQuery:@"PRAGMA cipher_salt;"
|
||||
db:db];
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
[self logHeaderOfDatabaseFile:databaseFilePath
|
||||
label:@"Unconverted header"];
|
||||
|
||||
/* Step 2. Rewrite header */
|
||||
|
||||
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA user_version = 2;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
int log, ckpt;
|
||||
rc = sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_FULL, &log, &ckpt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
DDLogInfo(@"log = %d, ckpt = %d", log, ckpt);
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
[self logHeaderOfDatabaseFile:databaseFilePath
|
||||
label:@"Converted header"];
|
||||
|
||||
/* Step 3. Open the database and query it */
|
||||
|
||||
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
||||
DDLogInfo(@"salt pragma = %@", saltPragma);
|
||||
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
|
||||
|
||||
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
|
||||
- (int)executeSingleIntQuery:(NSString *)sql
|
||||
db:(sqlite3 *)db
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
XCTAssertTrue(rc = SQLITE_ROW);
|
||||
|
||||
int result = sqlite3_column_int(stmt, 0);
|
||||
|
||||
rc = sqlite3_finalize(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)executeSingleStringQuery:(NSString *)sql
|
||||
db:(sqlite3 *)db
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_step(stmt);
|
||||
XCTAssertTrue(rc = SQLITE_ROW);
|
||||
NSString *result = [NSString stringWithFormat:@"%s", sqlite3_column_text(stmt, 0)];
|
||||
|
||||
rc = sqlite3_finalize(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)testNewUserWithoutYapDatabase
|
||||
{
|
||||
sqlite3 *db;
|
||||
sqlite3_stmt *stmt;
|
||||
const int ROWSTOINSERT = 3;
|
||||
|
||||
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
||||
|
||||
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
||||
|
||||
NSData *keyData = [self randomDatabasePassword];
|
||||
NSData *databaseSalt = [self randomDatabaseSalt];
|
||||
NSString *salt = databaseSalt.hexadecimalString;
|
||||
|
||||
/* Step 1. Create a new encrypted database. */
|
||||
|
||||
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
||||
|
||||
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
||||
DDLogInfo(@"salt pragma = %@", saltPragma);
|
||||
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
for(int row = 0; row < ROWSTOINSERT; row++) {
|
||||
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_step(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_DONE);
|
||||
rc = sqlite3_reset(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
rc = sqlite3_finalize(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
/* Step 2. Open the database and query it */
|
||||
|
||||
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
// NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
||||
// DDLogInfo(@"salt pragma = %@", saltPragma);
|
||||
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
|
||||
|
||||
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
|
||||
- (void)testCreatePreconvertedDatabaseWithoutYapDatabase
|
||||
{
|
||||
sqlite3 *db;
|
||||
sqlite3_stmt *stmt;
|
||||
const int ROWSTOINSERT = 3;
|
||||
|
||||
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
||||
|
||||
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
||||
|
||||
NSData *keyData = [self randomDatabasePassword];
|
||||
NSData *databaseSalt = [self randomDatabaseSalt];
|
||||
NSString *salt = databaseSalt.hexadecimalString;
|
||||
|
||||
/* Step 1. Create a new encrypted database. */
|
||||
|
||||
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
||||
|
||||
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
||||
DDLogInfo(@"salt pragma = %@", saltPragma);
|
||||
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
for(int row = 0; row < ROWSTOINSERT; row++) {
|
||||
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_step(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_DONE);
|
||||
rc = sqlite3_reset(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
rc = sqlite3_finalize(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
|
||||
- (void)testNewUserLikeYapDatabase
|
||||
{
|
||||
sqlite3 *db;
|
||||
sqlite3_stmt *stmt;
|
||||
const int ROWSTOINSERT = 3;
|
||||
|
||||
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
||||
|
||||
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
||||
|
||||
NSData *keyData = [self randomDatabasePassword];
|
||||
NSData *databaseSalt = [self randomDatabaseSalt];
|
||||
NSString *salt = databaseSalt.hexadecimalString;
|
||||
|
||||
/* Step 1. Create a new encrypted database. */
|
||||
|
||||
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
||||
|
||||
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
||||
DDLogInfo(@"salt pragma = %@", saltPragma);
|
||||
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
{
|
||||
int status = sqlite3_exec(db, "PRAGMA auto_vacuum = FULL; VACUUM;", NULL, NULL, NULL);
|
||||
XCTAssertEqual(status, SQLITE_OK);
|
||||
|
||||
// Set synchronous to normal for THIS sqlite instance.
|
||||
//
|
||||
// This does NOT affect normal connections.
|
||||
// That is, this does NOT affect YapDatabaseConnection instances.
|
||||
// The sqlite connections of normal YapDatabaseConnection instances will follow the set pragmaSynchronous value.
|
||||
//
|
||||
// The reason we hardcode normal for this sqlite instance is because
|
||||
// it's only used to write the initial snapshot value.
|
||||
// And this doesn't need to be durable, as it is initialized to zero everytime.
|
||||
//
|
||||
// (This sqlite db is also used to perform checkpoints.
|
||||
// But a normal value won't affect these operations,
|
||||
// as they will perform sync operations whether the connection is normal or full.)
|
||||
|
||||
status = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
||||
XCTAssertEqual(status, SQLITE_OK);
|
||||
|
||||
// Set journal_size_imit.
|
||||
//
|
||||
// We only need to do set this pragma for THIS connection,
|
||||
// because it is the only connection that performs checkpoints.
|
||||
|
||||
NSString *pragma_journal_size_limit =
|
||||
[NSString stringWithFormat:@"PRAGMA journal_size_limit = %d;", 0];
|
||||
|
||||
status = sqlite3_exec(db, [pragma_journal_size_limit UTF8String], NULL, NULL, NULL);
|
||||
XCTAssertEqual(status, SQLITE_OK);
|
||||
|
||||
|
||||
// // Set mmap_size (if needed).
|
||||
// //
|
||||
// // This configures memory mapped I/O.
|
||||
//
|
||||
// if (options.pragmaMMapSize > 0)
|
||||
// {
|
||||
// NSString *pragma_mmap_size =
|
||||
// [NSString stringWithFormat:@"PRAGMA mmap_size = %ld;", (long)options.pragmaMMapSize];
|
||||
//
|
||||
// status = sqlite3_exec(db, [pragma_mmap_size UTF8String], NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK)
|
||||
// {
|
||||
// YDBLogError(@"Error setting PRAGMA mmap_size: %d %s", status, sqlite3_errmsg(db));
|
||||
// // This isn't critical, so we can continue.
|
||||
// }
|
||||
// }
|
||||
|
||||
// Disable autocheckpointing.
|
||||
//
|
||||
// YapDatabase has its own optimized checkpointing algorithm built-in.
|
||||
// It knows the state of every active connection for the database,
|
||||
// so it can invoke the checkpoint methods at the precise time in which a checkpoint can be most effective.
|
||||
|
||||
status = sqlite3_wal_autocheckpoint(db, 0);
|
||||
XCTAssertEqual(status, SQLITE_OK);
|
||||
}
|
||||
|
||||
// PRAGMA auto_vacuum = FULL; VACUUM;
|
||||
// PRAGMA synchronous = NORMAL;
|
||||
// PRAGMA journal_size_limit = 0;
|
||||
// sqlite3_wal_autocheckpoint(db, 0);
|
||||
// yap_vfs_shim_name = [NSString stringWithFormat:@"yap_vfs_shim_%@", [[NSUUID UUID] UUIDString]];
|
||||
// yap_vfs_shim_register([yap_vfs_shim_name UTF8String], NULL, &yap_vfs_shim);
|
||||
//
|
||||
//
|
||||
|
||||
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
for(int row = 0; row < ROWSTOINSERT; row++) {
|
||||
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_step(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_DONE);
|
||||
rc = sqlite3_reset(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
rc = sqlite3_finalize(stmt);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
/* Step 2. Open the database and query it */
|
||||
|
||||
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
// NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
||||
// DDLogInfo(@"salt pragma = %@", saltPragma);
|
||||
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
|
||||
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
|
||||
|
||||
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
|
||||
|
||||
rc = sqlite3_close(db);
|
||||
XCTAssertTrue(rc == SQLITE_OK);
|
||||
}
|
||||
|
||||
- (void)logHeaderOfDatabaseFile:(NSString *)databaseFilePath
|
||||
label:(NSString *)label
|
||||
{
|
||||
OWSAssert(databaseFilePath.length > 0);
|
||||
OWSAssert(label.length > 0);
|
||||
|
||||
NSData *headerData = [OWSDatabaseConverter readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
|
||||
OWSAssert(headerData);
|
||||
NSMutableString *output = [NSMutableString new];
|
||||
[output appendFormat:@"Hex: %@, ", headerData.hexadecimalString];
|
||||
[output appendString:@"Ascii: "];
|
||||
NSMutableCharacterSet *characterSet = [NSMutableCharacterSet new];
|
||||
[characterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
[characterSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
|
||||
[characterSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
|
||||
[characterSet formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]];
|
||||
|
||||
const unsigned char *bytes = (const unsigned char *) headerData.bytes;
|
||||
for (NSUInteger i=0; i < headerData.length; i++) {
|
||||
unsigned char byte = bytes[i];
|
||||
if ([characterSet characterIsMember:(unichar)byte]) {
|
||||
[output appendFormat:@"%C", (unichar)byte];
|
||||
} else {
|
||||
[output appendString:@"_"];
|
||||
}
|
||||
}
|
||||
DDLogInfo(@"%@: %@", label, output);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
|
|
@ -279,25 +279,24 @@ const NSUInteger kSQLCipherSaltLength = 16;
|
|||
return error;
|
||||
}
|
||||
|
||||
|
||||
// MJK TODO: If possible, I think we want to avoid setting the default plain text header size.
|
||||
//
|
||||
// Ultimately it sets a static variable in the SQLCipher framework which will affect subsequent
|
||||
// setups of SQLCipher DB's. In particular, unless we're careful to clean up after ourselves, this
|
||||
// piece of global state will cause some tests to fail depending on the order in which they're called.
|
||||
// Similarly, this global state could be a liability when trying to reason about our migration process.
|
||||
// if the code that sets this global default ends up getting called earlier than we intend.
|
||||
//
|
||||
// IMO it's better to explicitly set the plaintext_header_size each time we open the db (which entails
|
||||
// setting up the YapDatabase instance and for each newConnection.)
|
||||
NSString *setDefaultPlainTextHeaderPragma =
|
||||
[NSString stringWithFormat:@"PRAGMA cipher_default_plaintext_header_size = %zd;", kSqliteHeaderLength];
|
||||
error = [self executeSql:setDefaultPlainTextHeaderPragma
|
||||
db:db
|
||||
label:setDefaultPlainTextHeaderPragma];
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
// // MJK TODO: If possible, I think we want to avoid setting the default plain text header size.
|
||||
// //
|
||||
// // Ultimately it sets a static variable in the SQLCipher framework which will affect subsequent
|
||||
// // setups of SQLCipher DB's. In particular, unless we're careful to clean up after ourselves, this
|
||||
// // piece of global state will cause some tests to fail depending on the order in which they're called.
|
||||
// // Similarly, this global state could be a liability when trying to reason about our migration process.
|
||||
// // if the code that sets this global default ends up getting called earlier than we intend.
|
||||
// //
|
||||
// // IMO it's better to explicitly set the plaintext_header_size each time we open the db (which entails
|
||||
// // setting up the YapDatabase instance and for each newConnection.)
|
||||
// NSString *setDefaultPlainTextHeaderPragma =
|
||||
// [NSString stringWithFormat:@"PRAGMA cipher_default_plaintext_header_size = %zd;", kSqliteHeaderLength];
|
||||
// error = [self executeSql:setDefaultPlainTextHeaderPragma
|
||||
// db:db
|
||||
// label:setDefaultPlainTextHeaderPragma];
|
||||
// if (error) {
|
||||
// return error;
|
||||
// }
|
||||
|
||||
// Modify the first page, so that SQLCipher will overwrite, respecting our new cipher_plaintext_header_size
|
||||
NSString *tableName = [NSString stringWithFormat:@"signal-migration-%@", [NSUUID new].UUIDString];
|
||||
|
|
Loading…
Reference in New Issue