Modify YapDatabase to read converted database.

This commit is contained in:
Matthew Chen 2018-01-23 16:43:45 -05:00
parent 3cd1b2c96c
commit d0f1706a49
2 changed files with 550 additions and 26 deletions

View File

@ -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

View File

@ -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];