Pull out OWSStorage base class for TSStorageManager.

pull/1/head
Matthew Chen 8 years ago
parent 40ec869072
commit a29c4ce5d6

@ -242,7 +242,7 @@
DDLogError(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
[DDLog flushLog];
[[TSStorageManager sharedManager] resetSignalStorage];
[OWSStorage resetAllStorage];
[[OWSProfileManager sharedManager] resetProfileStorage];
[Environment.preferences clear];
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];

@ -6,7 +6,7 @@
@implementation NSArray (FunctionalUtil)
- (bool)any:(int (^)(id item))predicate {
ows_require(predicate != nil);
OWSAssert(predicate != nil);
for (id e in self) {
if (predicate(e)) {
return true;
@ -15,7 +15,7 @@
return false;
}
- (bool)all:(int (^)(id item))predicate {
ows_require(predicate != nil);
OWSAssert(predicate != nil);
for (id e in self) {
if (!predicate(e)) {
return false;
@ -24,7 +24,7 @@
return true;
}
- (id)firstMatchingElseNil:(int (^)(id item))predicate {
ows_require(predicate != nil);
OWSAssert(predicate != nil);
for (id e in self) {
if (predicate(e)) {
return e;
@ -33,7 +33,7 @@
return nil;
}
- (NSArray *)map:(id (^)(id item))projection {
ows_require(projection != nil);
OWSAssert(projection != nil);
NSMutableArray *r = [NSMutableArray arrayWithCapacity:self.count];
for (id e in self) {
@ -42,7 +42,7 @@
return r;
}
- (NSArray *)filter:(int (^)(id item))predicate {
ows_require(predicate != nil);
OWSAssert(predicate != nil);
NSMutableArray *r = [NSMutableArray array];
for (id e in self) {
@ -74,19 +74,19 @@
return s;
}
- (NSDictionary *)keyedBy:(id (^)(id value))keySelector {
ows_require(keySelector != nil);
OWSAssert(keySelector != nil);
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (id value in self) {
result[keySelector(value)] = value;
}
ows_require(result.count == self.count);
OWSAssert(result.count == self.count);
return result;
}
- (NSDictionary *)groupBy:(id (^)(id value))keySelector {
ows_require(keySelector != nil);
OWSAssert(keySelector != nil);
NSMutableDictionary *result = [NSMutableDictionary dictionary];

@ -30,7 +30,7 @@
}
+ (NSUInteger)largestIntegerThatIsAtMost:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple {
ows_require(multiple != 0);
OWSAssert(multiple != 0);
NSUInteger d = value / multiple;
d *= multiple;
if (d > value)
@ -39,7 +39,7 @@
}
+ (NSUInteger)smallestIntegerThatIsAtLeast:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple {
ows_require(multiple != 0);
OWSAssert(multiple != 0);
NSUInteger d = value / multiple;
d *= multiple;
if (d < value)
@ -48,7 +48,7 @@
}
+ (double)clamp:(double)value toMin:(double)min andMax:(double)max {
ows_require(min <= max);
OWSAssert(min <= max);
if (isnan(value)) {
return max;
}
@ -69,8 +69,8 @@
}
+ (uint8_t)uint8FromLowUInt4:(uint8_t)low4UInt4 andHighUInt4:(uint8_t)highUInt4 {
ows_require(low4UInt4 < 0x10);
ows_require(highUInt4 < 0x10);
OWSAssert(low4UInt4 < 0x10);
OWSAssert(highUInt4 < 0x10);
return low4UInt4 | (uint8_t)(highUInt4 << 4);
}

@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *)removeAllCharactersIn:(NSCharacterSet *)characterSet
{
ows_require(characterSet != nil);
OWSAssert(characterSet != nil);
return [[self componentsSeparatedByCharactersInSet:characterSet] componentsJoinedByString:@""];
}

@ -121,8 +121,8 @@ NSString *const kTSStorageManagerOWSContactsSyncingLastMessageKey
NSData *messageData = [syncContactsMessage buildPlainTextAttachmentData];
NSData *lastMessageData =
[[TSStorageManager sharedManager] objectForKey:kTSStorageManagerOWSContactsSyncingLastMessageKey
inCollection:kTSStorageManagerOWSContactsSyncingCollection];
[TSStorageManager.dbReadConnection objectForKey:kTSStorageManagerOWSContactsSyncingLastMessageKey
inCollection:kTSStorageManagerOWSContactsSyncingCollection];
if (lastMessageData && [lastMessageData isEqual:messageData]) {
// Ignore redundant contacts sync message.
@ -139,9 +139,9 @@ NSString *const kTSStorageManagerOWSContactsSyncingLastMessageKey
success:^{
DDLogInfo(@"%@ Successfully sent contacts sync message.", self.logTag);
[[TSStorageManager sharedManager] setObject:messageData
forKey:kTSStorageManagerOWSContactsSyncingLastMessageKey
inCollection:kTSStorageManagerOWSContactsSyncingCollection];
[TSStorageManager.dbReadWriteConnection setObject:messageData
forKey:kTSStorageManagerOWSContactsSyncingLastMessageKey
inCollection:kTSStorageManagerOWSContactsSyncingCollection];
dispatch_async(self.serialQueue, ^{
self.isRequestInFlight = NO;

@ -49,7 +49,7 @@
+ (void)storeData:(NSData *)data forKey:(NSString *)key
{
[TSStorageManager.sharedManager setObject:data forKey:key inCollection:SignalKeyingCollection];
[TSStorageManager.dbReadWriteConnection setObject:data forKey:key inCollection:SignalKeyingCollection];
}
+ (NSData *)dataForKey:(NSString *)key andVerifyLength:(uint)length
@ -65,17 +65,17 @@
+ (NSData *)dataForKey:(NSString *)key
{
return [TSStorageManager.sharedManager dataForKey:key inCollection:SignalKeyingCollection];
return [TSStorageManager.dbReadConnection dataForKey:key inCollection:SignalKeyingCollection];
}
+ (NSString *)stringForKey:(NSString *)key
{
return [TSStorageManager.sharedManager stringForKey:key inCollection:SignalKeyingCollection];
return [TSStorageManager.dbReadConnection stringForKey:key inCollection:SignalKeyingCollection];
}
+ (void)storeString:(NSString *)string forKey:(NSString *)key
{
[TSStorageManager.sharedManager setObject:string forKey:key inCollection:SignalKeyingCollection];
[TSStorageManager.dbReadWriteConnection setObject:string forKey:key inCollection:SignalKeyingCollection];
}
@end

@ -49,15 +49,17 @@ NSString *const OWSPreferencesKey_IsRegistered = @"OWSPreferencesKey_IsRegistere
- (nullable id)tryGetValueForKey:(NSString *)key
{
ows_require(key != nil);
return [TSStorageManager.sharedManager objectForKey:key inCollection:OWSPreferencesSignalDatabaseCollection];
OWSAssert(key != nil);
return [TSStorageManager.dbReadConnection objectForKey:key inCollection:OWSPreferencesSignalDatabaseCollection];
}
- (void)setValueForKey:(NSString *)key toValue:(nullable id)value
{
ows_require(key != nil);
OWSAssert(key != nil);
[TSStorageManager.sharedManager setObject:value forKey:key inCollection:OWSPreferencesSignalDatabaseCollection];
[TSStorageManager.dbReadWriteConnection setObject:value
forKey:key
inCollection:OWSPreferencesSignalDatabaseCollection];
}
#pragma mark - Specific Preferences

@ -516,9 +516,9 @@
from:(NSString *)source
to:(NSString *)target
stickingRightward:(bool)preferHigh {
ows_require(source != nil);
ows_require(target != nil);
ows_require(offset <= source.length);
OWSAssert(source != nil);
OWSAssert(target != nil);
OWSAssert(offset <= source.length);
NSUInteger n = source.length;
NSUInteger m = target.length;

@ -75,7 +75,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark To be subclassed.
- (BOOL)isGroupThread {
NSAssert(false, @"An abstract method on TSThread was called.");
OWSFail(@"An abstract method on TSThread was called.");
return FALSE;
}
@ -86,13 +86,13 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSString *)name {
NSAssert(FALSE, @"Should be implemented in subclasses");
OWSFail(@"Should be implemented in subclasses");
return nil;
}
- (NSArray<NSString *> *)recipientIdentifiers
{
NSAssert(FALSE, @"Should be implemented in subclasses");
OWSFail(@"Should be implemented in subclasses");
return @[];
}

@ -10,18 +10,18 @@ NS_ASSUME_NONNULL_BEGIN
- (void)acceptNewIdentityKey
{
NSAssert(NO, @"Method needs to be implemented in subclasses of TSInvalidIdentityKeyErrorMessage.");
OWSFail(@"Method needs to be implemented in subclasses of TSInvalidIdentityKeyErrorMessage.");
}
- (nullable NSData *)newIdentityKey
{
NSAssert(NO, @"Method needs to be implemented in subclasses of TSInvalidIdentityKeyErrorMessage.");
OWSFail(@"Method needs to be implemented in subclasses of TSInvalidIdentityKeyErrorMessage.");
return nil;
}
- (NSString *)theirSignalId
{
NSAssert(NO, @"Method needs to be implemented in subclasses of TSInvalidIdentityKeyErrorMessage.");
OWSFail(@"Method needs to be implemented in subclasses of TSInvalidIdentityKeyErrorMessage.");
return nil;
}

@ -103,15 +103,15 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange =
- (BOOL)isCensorshipCircumventionManuallyActivated
{
return [[TSStorageManager sharedManager] boolForKey:kTSStorageManager_isCensorshipCircumventionManuallyActivated
inCollection:kTSStorageManager_OWSSignalService];
return [[TSStorageManager dbReadConnection] boolForKey:kTSStorageManager_isCensorshipCircumventionManuallyActivated
inCollection:kTSStorageManager_OWSSignalService];
}
- (void)setIsCensorshipCircumventionManuallyActivated:(BOOL)value
{
[[TSStorageManager sharedManager] setObject:@(value)
forKey:kTSStorageManager_isCensorshipCircumventionManuallyActivated
inCollection:kTSStorageManager_OWSSignalService];
[[TSStorageManager dbReadWriteConnection] setObject:@(value)
forKey:kTSStorageManager_isCensorshipCircumventionManuallyActivated
inCollection:kTSStorageManager_OWSSignalService];
[self updateIsCensorshipCircumventionActive];
}
@ -344,28 +344,28 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange =
- (NSString *)manualCensorshipCircumventionDomain
{
return [[TSStorageManager sharedManager] objectForKey:kTSStorageManager_ManualCensorshipCircumventionDomain
inCollection:kTSStorageManager_OWSSignalService];
return [[TSStorageManager dbReadConnection] objectForKey:kTSStorageManager_ManualCensorshipCircumventionDomain
inCollection:kTSStorageManager_OWSSignalService];
}
- (void)setManualCensorshipCircumventionDomain:(NSString *)value
{
[[TSStorageManager sharedManager] setObject:value
forKey:kTSStorageManager_ManualCensorshipCircumventionDomain
inCollection:kTSStorageManager_OWSSignalService];
[[TSStorageManager dbReadWriteConnection] setObject:value
forKey:kTSStorageManager_ManualCensorshipCircumventionDomain
inCollection:kTSStorageManager_OWSSignalService];
}
- (NSString *)manualCensorshipCircumventionCountryCode
{
return [[TSStorageManager sharedManager] objectForKey:kTSStorageManager_ManualCensorshipCircumventionCountryCode
inCollection:kTSStorageManager_OWSSignalService];
return [[TSStorageManager dbReadConnection] objectForKey:kTSStorageManager_ManualCensorshipCircumventionCountryCode
inCollection:kTSStorageManager_OWSSignalService];
}
- (void)setManualCensorshipCircumventionCountryCode:(NSString *)value
{
[[TSStorageManager sharedManager] setObject:value
forKey:kTSStorageManager_ManualCensorshipCircumventionCountryCode
inCollection:kTSStorageManager_OWSSignalService];
[[TSStorageManager dbReadWriteConnection] setObject:value
forKey:kTSStorageManager_ManualCensorshipCircumventionCountryCode
inCollection:kTSStorageManager_OWSSignalService];
}
@end

@ -3,17 +3,13 @@
//
#import "SecurityUtils.h"
#import <Curve25519Kit/Randomness.h>
@implementation SecurityUtils
+ (NSData *)generateRandomBytes:(NSUInteger)length
{
NSMutableData *d = [NSMutableData dataWithLength:length];
OSStatus status = SecRandomCopyBytes(kSecRandomDefault, length, [d mutableBytes]);
if (status != noErr) {
[SecurityFailure raise:@"SecRandomCopyBytes failed"];
}
return [d copy];
return [Randomness generateRandomBytes:length];
}
@end

@ -15,14 +15,16 @@ NSString *const TSStorageManagerCallKitIdToPhoneNumberCollection = @"TSStorageMa
OWSAssert(phoneNumber.length > 0);
OWSAssert(callKitId.length > 0);
[self setObject:phoneNumber forKey:callKitId inCollection:TSStorageManagerCallKitIdToPhoneNumberCollection];
[self.dbReadWriteConnection setObject:phoneNumber
forKey:callKitId
inCollection:TSStorageManagerCallKitIdToPhoneNumberCollection];
}
- (NSString *)phoneNumberForCallKitId:(NSString *)callKitId
{
OWSAssert(callKitId.length > 0);
return [self objectForKey:callKitId inCollection:TSStorageManagerCallKitIdToPhoneNumberCollection];
return [self.dbReadConnection objectForKey:callKitId inCollection:TSStorageManagerCallKitIdToPhoneNumberCollection];
}
@end

@ -39,20 +39,24 @@
preKeyId++;
}
[self setInt:preKeyId forKey:TSNextPrekeyIdKey inCollection:TSStorageInternalSettingsCollection];
[self.dbReadWriteConnection setInt:preKeyId
forKey:TSNextPrekeyIdKey
inCollection:TSStorageInternalSettingsCollection];
}
return preKeyRecords;
}
- (void)storePreKeyRecords:(NSArray *)preKeyRecords {
for (PreKeyRecord *record in preKeyRecords) {
[self setObject:record forKey:[self keyFromInt:record.Id] inCollection:TSStorageManagerPreKeyStoreCollection];
[self.dbReadWriteConnection setObject:record
forKey:[self keyFromInt:record.Id]
inCollection:TSStorageManagerPreKeyStoreCollection];
}
}
- (PreKeyRecord *)loadPreKey:(int)preKeyId {
PreKeyRecord *preKeyRecord =
[self preKeyRecordForKey:[self keyFromInt:preKeyId] inCollection:TSStorageManagerPreKeyStoreCollection];
PreKeyRecord *preKeyRecord = [self.dbReadConnection preKeyRecordForKey:[self keyFromInt:preKeyId]
inCollection:TSStorageManagerPreKeyStoreCollection];
if (!preKeyRecord) {
@throw [NSException exceptionWithName:InvalidKeyIdException
@ -64,21 +68,25 @@
}
- (void)storePreKey:(int)preKeyId preKeyRecord:(PreKeyRecord *)record {
[self setObject:record forKey:[self keyFromInt:preKeyId] inCollection:TSStorageManagerPreKeyStoreCollection];
[self.dbReadWriteConnection setObject:record
forKey:[self keyFromInt:preKeyId]
inCollection:TSStorageManagerPreKeyStoreCollection];
}
- (BOOL)containsPreKey:(int)preKeyId {
PreKeyRecord *preKeyRecord =
[self preKeyRecordForKey:[self keyFromInt:preKeyId] inCollection:TSStorageManagerPreKeyStoreCollection];
PreKeyRecord *preKeyRecord = [self.dbReadConnection preKeyRecordForKey:[self keyFromInt:preKeyId]
inCollection:TSStorageManagerPreKeyStoreCollection];
return (preKeyRecord != nil);
}
- (void)removePreKey:(int)preKeyId {
[self removeObjectForKey:[self keyFromInt:preKeyId] inCollection:TSStorageManagerPreKeyStoreCollection];
[self.dbReadWriteConnection removeObjectForKey:[self keyFromInt:preKeyId]
inCollection:TSStorageManagerPreKeyStoreCollection];
}
- (int)nextPreKeyId {
int lastPreKeyId = [self intForKey:TSNextPrekeyIdKey inCollection:TSStorageInternalSettingsCollection];
int lastPreKeyId =
[self.dbReadConnection intForKey:TSNextPrekeyIdKey inCollection:TSStorageInternalSettingsCollection];
if (lastPreKeyId < 1) {
// One-time prekey ids must be > 0 and < kPreKeyOfLastResortId.

@ -6,10 +6,9 @@
#import "TSStorageManager+PreKeyStore.h"
#import "TSStorageManager+SignedPreKeyStore.h"
#import "TSStorageManager+keyFromIntLong.h"
#import <Curve25519Kit/Ed25519.h>
#import <AxolotlKit/AxolotlExceptions.h>
#import <AxolotlKit/NSData+keyVersionByte.h>
#import <Curve25519Kit/Ed25519.h>
NS_ASSUME_NONNULL_BEGIN
@ -35,8 +34,9 @@ NSString *const TSStorageManagerKeyPrekeyCurrentSignedPrekeyId = @"currentSigned
}
- (SignedPreKeyRecord *)loadSignedPrekey:(int)signedPreKeyId {
SignedPreKeyRecord *preKeyRecord = [self signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
SignedPreKeyRecord *preKeyRecord =
[self.dbReadConnection signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
if (!preKeyRecord) {
@throw [NSException exceptionWithName:InvalidKeyIdException
@ -49,8 +49,8 @@ NSString *const TSStorageManagerKeyPrekeyCurrentSignedPrekeyId = @"currentSigned
- (nullable SignedPreKeyRecord *)loadSignedPrekeyOrNil:(int)signedPreKeyId
{
return [self signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
return [self.dbReadConnection signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
}
- (NSArray *)loadSignedPreKeys {
@ -69,73 +69,75 @@ NSString *const TSStorageManagerKeyPrekeyCurrentSignedPrekeyId = @"currentSigned
}
- (void)storeSignedPreKey:(int)signedPreKeyId signedPreKeyRecord:(SignedPreKeyRecord *)signedPreKeyRecord {
[self setObject:signedPreKeyRecord
forKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
[self.dbReadWriteConnection setObject:signedPreKeyRecord
forKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
}
- (BOOL)containsSignedPreKey:(int)signedPreKeyId {
PreKeyRecord *preKeyRecord = [self signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
PreKeyRecord *preKeyRecord =
[self.dbReadConnection signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
return (preKeyRecord != nil);
}
- (void)removeSignedPreKey:(int)signedPrekeyId {
[self removeObjectForKey:[self keyFromInt:signedPrekeyId] inCollection:TSStorageManagerSignedPreKeyStoreCollection];
[self.dbReadWriteConnection removeObjectForKey:[self keyFromInt:signedPrekeyId]
inCollection:TSStorageManagerSignedPreKeyStoreCollection];
}
- (nullable NSNumber *)currentSignedPrekeyId
{
return [TSStorageManager.sharedManager objectForKey:TSStorageManagerKeyPrekeyCurrentSignedPrekeyId
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
return [self.dbReadConnection objectForKey:TSStorageManagerKeyPrekeyCurrentSignedPrekeyId
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
}
- (void)setCurrentSignedPrekeyId:(int)value
{
[TSStorageManager.sharedManager setObject:@(value)
forKey:TSStorageManagerKeyPrekeyCurrentSignedPrekeyId
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
[self.dbReadWriteConnection setObject:@(value)
forKey:TSStorageManagerKeyPrekeyCurrentSignedPrekeyId
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
}
#pragma mark - Prekey update failures
- (int)prekeyUpdateFailureCount;
{
NSNumber *value = [TSStorageManager.sharedManager objectForKey:TSStorageManagerKeyPrekeyUpdateFailureCount
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
NSNumber *value = [self.dbReadConnection objectForKey:TSStorageManagerKeyPrekeyUpdateFailureCount
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
// Will default to zero.
return [value intValue];
}
- (void)clearPrekeyUpdateFailureCount
{
[TSStorageManager.sharedManager removeObjectForKey:TSStorageManagerKeyPrekeyUpdateFailureCount
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
[self.dbReadWriteConnection removeObjectForKey:TSStorageManagerKeyPrekeyUpdateFailureCount
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
}
- (int)incrementPrekeyUpdateFailureCount
{
return [TSStorageManager.sharedManager incrementIntForKey:TSStorageManagerKeyPrekeyUpdateFailureCount
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
return [self.dbReadWriteConnection incrementIntForKey:TSStorageManagerKeyPrekeyUpdateFailureCount
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
}
- (nullable NSDate *)firstPrekeyUpdateFailureDate
{
return [TSStorageManager.sharedManager dateForKey:TSStorageManagerKeyFirstPrekeyUpdateFailureDate
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
return [self.dbReadConnection dateForKey:TSStorageManagerKeyFirstPrekeyUpdateFailureDate
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
}
- (void)setFirstPrekeyUpdateFailureDate:(nonnull NSDate *)value
{
[TSStorageManager.sharedManager setDate:value
forKey:TSStorageManagerKeyFirstPrekeyUpdateFailureDate
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
[self.dbReadWriteConnection setDate:value
forKey:TSStorageManagerKeyFirstPrekeyUpdateFailureDate
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
}
- (void)clearFirstPrekeyUpdateFailureDate
{
[TSStorageManager.sharedManager removeObjectForKey:TSStorageManagerKeyFirstPrekeyUpdateFailureDate
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
[self.dbReadWriteConnection removeObjectForKey:TSStorageManagerKeyFirstPrekeyUpdateFailureDate
inCollection:TSStorageManagerSignedPreKeyMetadataCollection];
}
#pragma mark - Debugging

@ -0,0 +1,51 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <YapDatabase/YapDatabaseConnection.h>
// TODO: Remove this import.
#import "YapDatabaseConnection+OWS.h"
// TODO: Remove this import.
#import <YapDatabase/YapDatabase.h>
NS_ASSUME_NONNULL_BEGIN
@protocol OWSDatabaseConnectionDelegate <NSObject>
- (BOOL)isDatabaseInitialized;
@end
#pragma mark -
@interface OWSStorage : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initStorage NS_DESIGNATED_INITIALIZER;
- (void)setDatabaseInitialized;
+ (void)resetAllStorage;
// TODO: Deprecate?
- (nullable YapDatabaseConnection *)newDatabaseConnection;
// TODO: Deprecate.
@property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadConnection;
@property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection;
#pragma mark - Password
/**
* Returns NO if:
*
* - Keychain is locked because device has just been restarted.
* - Password could not be retrieved because of a keychain error.
*/
+ (BOOL)isDatabasePasswordAccessible;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,521 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSStorage.h"
#import "AppContext.h"
#import "NSData+Base64.h"
//#import "OWSAnalytics.h"
//#import "OWSBatchMessageProcessor.h"
//#import "OWSDisappearingMessagesFinder.h"
//#import "OWSFailedAttachmentDownloadsJob.h"
//#import "OWSFailedMessagesJob.h"
//#import "OWSFileSystem.h"
//#import "OWSIncomingMessageFinder.h"
//#import "OWSMessageReceiver.h"
//#import "SignalRecipient.h"
#import "TSAttachmentStream.h"
//#import "TSDatabaseSecondaryIndexes.h"
//#import "TSDatabaseView.h"
//#import "TSInteraction.h"
//#import "TSThread.h"
#import "TSStorageManager.h"
#import <Curve25519Kit/Randomness.h>
#import <SAMKeychain/SAMKeychain.h>
//#import <YapDatabase/YapDatabaseRelationship.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSStorageExceptionName_DatabasePasswordInaccessibleWhileBackgrounded
= @"OWSStorageExceptionName_DatabasePasswordInaccessibleWhileBackgrounded";
NSString *const OWSStorageExceptionName_DatabasePasswordUnwritable
= @"OWSStorageExceptionName_DatabasePasswordUnwritable";
NSString *const OWSStorageExceptionName_NoDatabase = @"OWSStorageExceptionName_NoDatabase";
// NSString *const OWSStorageExceptionName_CouldNotMoveDatabaseFile
// = @"OWSStorageExceptionName_CouldNotMoveDatabaseFile";
// NSString *const OWSStorageExceptionName_CouldNotCreateDatabaseDirectory
// = @"OWSStorageExceptionName_CouldNotCreateDatabaseDirectory";
static NSString *keychainService = @"TSKeyChainService";
static NSString *keychainDBPassAccount = @"TSDatabasePass";
#pragma mark -
@interface YapDatabaseConnection ()
- (id)initWithDatabase:(YapDatabase *)database;
@end
#pragma mark -
@interface OWSDatabaseConnection : YapDatabaseConnection
@property (atomic, weak) id<OWSDatabaseConnectionDelegate> delegate;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithDatabase:(YapDatabase *)database
delegate:(id<OWSDatabaseConnectionDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@end
#pragma mark -
@implementation OWSDatabaseConnection
- (id)initWithDatabase:(YapDatabase *)database delegate:(id<OWSDatabaseConnectionDelegate>)delegate
{
self = [super initWithDatabase:database];
if (!self) {
return self;
}
OWSAssert(delegate);
_delegate = delegate;
return self;
}
// This clobbers the superclass implementation to include an assert which
// ensures that the database is in a ready state before creating write transactions.
//
// Creating write transactions before the _sync_ database views are registered
// causes YapDatabase to rebuild all of our database views, which is catastrophic.
// We're not sure why, but it causes YDB's "view version" checks to fail.
- (void)readWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.isDatabaseInitialized);
[super readWriteWithBlock:block];
}
- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.isDatabaseInitialized);
[super asyncReadWriteWithBlock:block];
}
- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
completionBlock:(nullable dispatch_block_t)completionBlock
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.isDatabaseInitialized);
[super asyncReadWriteWithBlock:block completionBlock:completionBlock];
}
- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
completionQueue:(nullable dispatch_queue_t)completionQueue
completionBlock:(nullable dispatch_block_t)completionBlock
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.isDatabaseInitialized);
[super asyncReadWriteWithBlock:block completionQueue:completionQueue completionBlock:completionBlock];
}
@end
#pragma mark -
// This class is only used in DEBUG builds.
@interface YapDatabase ()
- (void)addConnection:(YapDatabaseConnection *)connection;
@end
#pragma mark -
@interface OWSDatabase : YapDatabase
@property (atomic, weak) id<OWSDatabaseConnectionDelegate> delegate;
- (instancetype)init NS_UNAVAILABLE;
- (id)initWithPath:(NSString *)inPath
serializer:(YapDatabaseSerializer)inSerializer
deserializer:(YapDatabaseDeserializer)inDeserializer
options:(YapDatabaseOptions *)inOptions
delegate:(id<OWSDatabaseConnectionDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@end
#pragma mark -
@implementation OWSDatabase
- (id)initWithPath:(NSString *)inPath
serializer:(YapDatabaseSerializer)inSerializer
deserializer:(YapDatabaseDeserializer)inDeserializer
options:(YapDatabaseOptions *)inOptions
delegate:(id<OWSDatabaseConnectionDelegate>)delegate
{
self = [super initWithPath:inPath serializer:inSerializer deserializer:inDeserializer options:inOptions];
if (!self) {
return self;
}
OWSAssert(delegate);
_delegate = delegate;
return self;
}
// This clobbers the superclass implementation to include asserts which
// ensure that the database is in a ready state before creating write transactions.
//
// See comments in OWSDatabaseConnection.
- (YapDatabaseConnection *)newConnection
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSDatabaseConnection *connection = [[OWSDatabaseConnection alloc] initWithDatabase:self delegate:delegate];
[self addConnection:connection];
return connection;
}
@end
#pragma mark -
@interface OWSUnknownDBObject : NSObject <NSCoding>
@end
#pragma mark -
/**
* A default object to return when we can't deserialize an object from YapDB. This can prevent crashes when
* old objects linger after their definition file is removed. The danger is that, the objects can lay in wait
* until the next time a DB extension is added and we necessarily enumerate the entire DB.
*/
@implementation OWSUnknownDBObject
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
}
@end
#pragma mark -
@interface OWSUnarchiverDelegate : NSObject <NSKeyedUnarchiverDelegate>
@end
#pragma mark -
@implementation OWSUnarchiverDelegate
- (nullable Class)unarchiver:(NSKeyedUnarchiver *)unarchiver
cannotDecodeObjectOfClassName:(NSString *)name
originalClasses:(NSArray<NSString *> *)classNames
{
DDLogError(@"%@ Could not decode object: %@", self.logTag, name);
OWSProdError([OWSAnalyticsEvents storageErrorCouldNotDecodeClass]);
return [OWSUnknownDBObject class];
}
@end
#pragma mark -
@interface OWSStorage () <OWSDatabaseConnectionDelegate>
@property (atomic, nullable) YapDatabase *database;
@property (atomic) BOOL isDatabaseInitialized;
@end
#pragma mark -
@implementation OWSStorage
- (instancetype)initStorage
{
self = [super init];
if (![self tryToLoadDatabase]) {
// Failing to load the database is catastrophic.
//
// The best we can try to do is to discard the current database
// and behave like a clean install.
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabase]);
// Try to reset app by deleting database.
// Disabled resetting storage until we have better data on why this happens.
// [self resetAllStorage];
if (![self tryToLoadDatabase]) {
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]);
// Sleep to give analytics events time to be delivered.
[NSThread sleepForTimeInterval:15.0f];
[NSException raise:OWSStorageExceptionName_NoDatabase format:@"Failed to initialize database."];
}
OWSSingletonAssert();
}
return self;
}
- (void)setDatabaseInitialized
{
OWSAssert(!self.isDatabaseInitialized);
self.isDatabaseInitialized = YES;
}
- (BOOL)tryToLoadDatabase
{
// We determine the database password first, since a side effect of
// this can be deleting any existing database file (if we're recovering
// from a corrupt keychain).
NSData *databasePassword = [self databasePassword];
YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
options.corruptAction = YapDatabaseCorruptAction_Fail;
options.cipherKeyBlock = ^{
return databasePassword;
};
options.enableMultiProcessSupport = YES;
OWSDatabase *database = [[OWSDatabase alloc] initWithPath:[self dbPath]
serializer:NULL
deserializer:[[self class] logOnFailureDeserializer]
options:options
delegate:self];
if (!database) {
return NO;
}
_database = database;
_dbReadConnection = self.newDatabaseConnection;
_dbReadWriteConnection = self.newDatabaseConnection;
return YES;
}
/**
* NSCoding sometimes throws exceptions killing our app. We want to log that exception.
**/
+ (YapDatabaseDeserializer)logOnFailureDeserializer
{
OWSUnarchiverDelegate *unarchiverDelegate = [OWSUnarchiverDelegate new];
return ^id(NSString __unused *collection, NSString __unused *key, NSData *data) {
if (!data || data.length <= 0) {
return nil;
}
@try {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
unarchiver.delegate = unarchiverDelegate;
return [unarchiver decodeObjectForKey:@"root"];
} @catch (NSException *exception) {
// Sync log in case we bail.
OWSProdError([OWSAnalyticsEvents storageErrorDeserialization]);
@throw exception;
}
};
}
//+ (void)protectSignalFiles
//{
// // The old database location was in the Document directory,
// // so protect the database files individually.
// [OWSFileSystem protectFolderAtPath:self.legacyDatabaseFilePath];
// [OWSFileSystem protectFolderAtPath:self.legacyDatabaseFilePath_SHM];
// [OWSFileSystem protectFolderAtPath:self.legacyDatabaseFilePath_WAL];
//
// // Protect the entire new database directory.
// [OWSFileSystem protectFolderAtPath:self.sharedDataDatabaseDirPath];
//}
- (nullable YapDatabaseConnection *)newDatabaseConnection
{
return self.database.newConnection;
}
#pragma mark - Password
- (void)deleteDatabaseFile
{
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[self dbPath] error:&error];
if (error) {
DDLogError(@"Failed to delete database: %@", error.description);
}
}
- (void)resetStorage
{
self.database = nil;
_dbReadConnection = nil;
_dbReadWriteConnection = nil;
[self deleteDatabaseFile];
}
+ (void)resetAllStorage
{
[[TSStorageManager sharedManager] resetStorage];
[self deletePasswordFromKeychain];
if (CurrentAppContext().isMainApp) {
[TSAttachmentStream deleteAttachments];
}
// TODO: Delete Profiles on Disk?
}
#pragma mark - Password
- (NSString *)dbPath
{
OWS_ABSTRACT_METHOD();
return @"";
}
#pragma mark - Password
+ (BOOL)isDatabasePasswordAccessible
{
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
NSError *error;
NSString *dbPassword = [SAMKeychain passwordForService:keychainService account:keychainDBPassAccount error:&error];
if (dbPassword && !error) {
return YES;
}
if (error) {
DDLogWarn(@"Database password couldn't be accessed: %@", error.localizedDescription);
}
return NO;
}
- (NSData *)databasePassword
{
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
NSError *keyFetchError;
NSString *dbPassword =
[SAMKeychain passwordForService:keychainService account:keychainDBPassAccount error:&keyFetchError];
if (keyFetchError) {
NSString *errorDescription =
[NSString stringWithFormat:@"Database password inaccessible. No unlock since device restart? Error: %@",
keyFetchError];
if (CurrentAppContext().isMainApp) {
UIApplicationState applicationState = CurrentAppContext().mainApplicationState;
errorDescription =
[errorDescription stringByAppendingFormat:@", ApplicationState: %d", (int)applicationState];
}
DDLogError(@"%@ %@", self.logTag, errorDescription);
[DDLog flushLog];
if (CurrentAppContext().isMainApp) {
UIApplicationState applicationState = CurrentAppContext().mainApplicationState;
if (applicationState == UIApplicationStateBackground) {
// TODO: Rather than crash here, we should detect the situation earlier
// and exit gracefully - (in the app delegate?). See the `
// This is a last ditch effort to avoid blowing away the user's database.
[self backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:errorDescription];
}
} else {
[self backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:
@"Password inaccessible; not main app."];
}
// At this point, either this is a new install so there's no existing password to retrieve
// or the keychain has become corrupt. Either way, we want to get back to a
// "known good state" and behave like a new install.
BOOL shouldHavePassword = [NSFileManager.defaultManager fileExistsAtPath:[self dbPath]];
if (shouldHavePassword) {
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]);
}
// Try to reset app by deleting database.
[OWSStorage resetAllStorage];
dbPassword = [self createAndSetNewDatabasePassword];
}
return [dbPassword dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSString *)createAndSetNewDatabasePassword
{
NSString *newDBPassword = [[Randomness generateRandomBytes:30] base64EncodedString];
NSError *keySetError;
[SAMKeychain setPassword:newDBPassword forService:keychainService account:keychainDBPassAccount error:&keySetError];
if (keySetError) {
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotStoreDatabasePassword]);
[OWSStorage deletePasswordFromKeychain];
// Sleep to give analytics events time to be delivered.
[NSThread sleepForTimeInterval:15.0f];
[NSException raise:OWSStorageExceptionName_DatabasePasswordUnwritable
format:@"Setting DB password failed with error: %@", keySetError];
} else {
DDLogWarn(@"Succesfully set new DB password.");
}
return newDBPassword;
}
- (void)backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:(NSString *)errorDescription
{
OWSAssert(
CurrentAppContext().isMainApp && CurrentAppContext().mainApplicationState == UIApplicationStateBackground);
// Sleep to give analytics events time to be delivered.
[NSThread sleepForTimeInterval:5.0f];
// Presumably this happened in response to a push notification. It's possible that the keychain is corrupted
// but it could also just be that the user hasn't yet unlocked their device since our password is
// kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
[NSException raise:OWSStorageExceptionName_DatabasePasswordInaccessibleWhileBackgrounded
format:@"%@", errorDescription];
}
+ (void)deletePasswordFromKeychain
{
[SAMKeychain deletePasswordForService:keychainService account:keychainDBPassAccount];
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
// Some lingering TSRecipient records in the wild causing crashes.
// This is a stop gap until a proper cleanup happens.
@interface TSRecipient : NSObject <NSCoding>
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSRecipient.h"
NS_ASSUME_NONNULL_BEGIN
@implementation TSRecipient
@end
NS_ASSUME_NONNULL_END

@ -2,6 +2,7 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSStorage.h"
#import "TSStorageKeys.h"
#import "YapDatabaseConnection+OWS.h"
#import <YapDatabase/YapDatabase.h>
@ -12,20 +13,12 @@
NS_ASSUME_NONNULL_BEGIN
@interface TSStorageManager : NSObject
@interface TSStorageManager : OWSStorage
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedManager;
/**
* Returns NO if:
*
* - Keychain is locked because device has just been restarted.
* - Password could not be retrieved because of a keychain error.
*/
+ (BOOL)isDatabasePasswordAccessible;
/**
* The safeBlockingMigrationsBlock block will
* run any outstanding version migrations that are a) blocking and b) safe
@ -37,31 +30,12 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setupDatabaseWithSafeBlockingMigrations:(void (^_Nonnull)(void))safeBlockingMigrationsBlock;
- (void)deleteThreadsAndMessages;
- (void)resetSignalStorage;
- (nullable YapDatabase *)database;
- (nullable YapDatabaseConnection *)newDatabaseConnection;
- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection;
- (void)removeObjectForKey:(NSString *)string inCollection:(NSString *)collection;
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection;
- (int)intForKey:(NSString *)key inCollection:(NSString *)collection;
- (void)setInt:(int)integer forKey:(NSString *)key inCollection:(NSString *)collection;
- (id)objectForKey:(NSString *)key inCollection:(NSString *)collection;
- (int)incrementIntForKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection;
- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable NSDictionary *)dictionaryForKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable NSString *)stringForKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable ECKeyPair *)keyPairForKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable PreKeyRecord *)preKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection;
- (nullable SignedPreKeyRecord *)signedPreKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection;
- (void)purgeCollection:(NSString *)collection;
@property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadConnection;
@property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection;
// TODO: Deprecate.
+ (YapDatabaseConnection *)dbReadConnection;
+ (YapDatabaseConnection *)dbReadWriteConnection;
+ (void)migrateToSharedData;

@ -3,8 +3,6 @@
//
#import "TSStorageManager.h"
#import "AppContext.h"
#import "NSData+Base64.h"
#import "OWSAnalytics.h"
#import "OWSBatchMessageProcessor.h"
#import "OWSDisappearingMessagesFinder.h"
@ -19,187 +17,15 @@
#import "TSDatabaseView.h"
#import "TSInteraction.h"
#import "TSThread.h"
#import <Curve25519Kit/Randomness.h>
#import <SAMKeychain/SAMKeychain.h>
#import <YapDatabase/YapDatabaseRelationship.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const TSStorageManagerExceptionName_DatabasePasswordInaccessible
= @"TSStorageManagerExceptionName_DatabasePasswordInaccessible";
NSString *const TSStorageManagerExceptionName_DatabasePasswordInaccessibleWhileBackgrounded
= @"TSStorageManagerExceptionName_DatabasePasswordInaccessibleWhileBackgrounded";
NSString *const TSStorageManagerExceptionName_DatabasePasswordUnwritable
= @"TSStorageManagerExceptionName_DatabasePasswordUnwritable";
NSString *const TSStorageManagerExceptionName_NoDatabase = @"TSStorageManagerExceptionName_NoDatabase";
NSString *const TSStorageManagerExceptionName_CouldNotMoveDatabaseFile
= @"TSStorageManagerExceptionName_CouldNotMoveDatabaseFile";
NSString *const TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory
= @"TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory";
static NSString *keychainService = @"TSKeyChainService";
static NSString *keychainDBPassAccount = @"TSDatabasePass";
#pragma mark -
// This flag is only used in DEBUG builds.
static BOOL isDatabaseInitializedFlag = NO;
NSObject *isDatabaseInitializedFlagLock()
{
static NSObject *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [NSObject new];
});
return instance;
}
BOOL isDatabaseInitialized()
{
@synchronized(isDatabaseInitializedFlagLock())
{
return isDatabaseInitializedFlag;
}
}
void setDatabaseInitialized()
{
@synchronized(isDatabaseInitializedFlagLock())
{
isDatabaseInitializedFlag = YES;
}
}
#pragma mark -
@interface YapDatabaseConnection ()
- (id)initWithDatabase:(YapDatabase *)inDatabase;
@end
#pragma mark -
// This class is only used in DEBUG builds.
@interface OWSDatabaseConnection : YapDatabaseConnection
@end
#pragma mark -
@implementation OWSDatabaseConnection
// This clobbers the superclass implementation to include an assert which
// ensures that the database is in a ready state before creating write transactions.
//
// Creating write transactions before the _sync_ database views are registered
// causes YapDatabase to rebuild all of our database views, which is catastrophic.
// We're not sure why, but it causes YDB's "view version" checks to fail.
- (void)readWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
{
OWSAssert(isDatabaseInitialized());
[super readWriteWithBlock:block];
}
@end
#pragma mark -
// This class is only used in DEBUG builds.
@interface YapDatabase ()
- (void)addConnection:(YapDatabaseConnection *)connection;
@end
#pragma mark -
@interface OWSDatabase : YapDatabase
@end
#pragma mark -
@implementation OWSDatabase
// This clobbers the superclass implementation to include asserts which
// ensure that the database is in a ready state before creating write transactions.
//
// See comments in OWSDatabaseConnection.
- (YapDatabaseConnection *)newConnection
{
YapDatabaseConnection *connection = [[OWSDatabaseConnection alloc] initWithDatabase:self];
[self addConnection:connection];
return connection;
}
@end
#pragma mark -
@interface TSStorageManager ()
@property (nullable, atomic) YapDatabase *database;
@end
#pragma mark -
// Some lingering TSRecipient records in the wild causing crashes.
// This is a stop gap until a proper cleanup happens.
@interface TSRecipient : NSObject <NSCoding>
@end
#pragma mark -
@interface OWSUnknownObject : NSObject <NSCoding>
@end
#pragma mark -
/**
* A default object to return when we can't deserialize an object from YapDB. This can prevent crashes when
* old objects linger after their definition file is removed. The danger is that, the objects can lay in wait
* until the next time a DB extension is added and we necessarily enumerate the entire DB.
*/
@implementation OWSUnknownObject
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
}
@end
#pragma mark -
@interface OWSUnarchiverDelegate : NSObject <NSKeyedUnarchiverDelegate>
@end
#pragma mark -
@implementation OWSUnarchiverDelegate
- (nullable Class)unarchiver:(NSKeyedUnarchiver *)unarchiver cannotDecodeObjectOfClassName:(NSString *)name originalClasses:(NSArray<NSString *> *)classNames
{
DDLogError(@"%@ Could not decode object: %@", self.logTag, name);
OWSProdError([OWSAnalyticsEvents storageErrorCouldNotDecodeClass]);
return [OWSUnknownObject class];
}
@end
#pragma mark -
@implementation TSStorageManager
@ -212,101 +38,11 @@ void setDatabaseInitialized()
[TSStorageManager protectSignalFiles];
#endif
sharedManager = [[self alloc] initDefault];
sharedManager = [[self alloc] initStorage];
});
return sharedManager;
}
- (instancetype)initDefault
{
self = [super init];
if (![self tryToLoadDatabase]) {
// Failing to load the database is catastrophic.
//
// The best we can try to do is to discard the current database
// and behave like a clean install.
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabase]);
// Try to reset app by deleting database.
// Disabled resetting storage until we have better data on why this happens.
// [self resetSignalStorage];
if (![self tryToLoadDatabase]) {
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]);
// Sleep to give analytics events time to be delivered.
[NSThread sleepForTimeInterval:15.0f];
[NSException raise:TSStorageManagerExceptionName_NoDatabase format:@"Failed to initialize database."];
}
OWSSingletonAssert();
}
return self;
}
- (BOOL)tryToLoadDatabase
{
// We determine the database password first, since a side effect of
// this can be deleting any existing database file (if we're recovering
// from a corrupt keychain).
NSData *databasePassword = [self databasePassword];
YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
options.corruptAction = YapDatabaseCorruptAction_Fail;
options.cipherKeyBlock = ^{
return databasePassword;
};
options.enableMultiProcessSupport = YES;
#ifdef DEBUG
_database = [[OWSDatabase alloc] initWithPath:[self dbPath]
serializer:NULL
deserializer:[[self class] logOnFailureDeserializer]
options:options];
#else
_database = [[YapDatabase alloc] initWithPath:[self dbPath]
serializer:NULL
deserializer:[[self class] logOnFailureDeserializer]
options:options];
#endif
if (!_database) {
return NO;
}
_dbReadConnection = self.newDatabaseConnection;
_dbReadWriteConnection = self.newDatabaseConnection;
return YES;
}
/**
* NSCoding sometimes throws exceptions killing our app. We want to log that exception.
**/
+ (YapDatabaseDeserializer)logOnFailureDeserializer
{
OWSUnarchiverDelegate *unarchiverDelegate = [OWSUnarchiverDelegate new];
return ^id(NSString __unused *collection, NSString __unused *key, NSData *data) {
if (!data || data.length <= 0) {
return nil;
}
@try {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
unarchiver.delegate = unarchiverDelegate;
return [unarchiver decodeObjectForKey:@"root"];
} @catch (NSException *exception) {
// Sync log in case we bail.
OWSProdError([OWSAnalyticsEvents storageErrorDeserialization]);
@throw exception;
}
};
}
- (void)setupDatabaseWithSafeBlockingMigrations:(void (^_Nonnull)(void))safeBlockingMigrationsBlock
{
// Synchronously register extensions which are essential for views.
@ -324,7 +60,7 @@ void setDatabaseInitialized()
// seeing, this issue only seems to affect sync and not async registrations. We've always
// been opening write transactions before the async registrations complete without negative
// consequences.
setDatabaseInitialized();
[self setDatabaseInitialized];
// Run the blocking migrations.
//
@ -370,11 +106,6 @@ void setDatabaseInitialized()
[OWSFileSystem protectFolderAtPath:self.sharedDataDatabaseDirPath];
}
- (nullable YapDatabaseConnection *)newDatabaseConnection
{
return self.database.newConnection;
}
- (BOOL)userSetPassword {
return FALSE;
}
@ -460,225 +191,14 @@ void setDatabaseInitialized()
return TSStorageManager.sharedDataDatabaseFilePath;
}
+ (BOOL)isDatabasePasswordAccessible
{
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
NSError *error;
NSString *dbPassword = [SAMKeychain passwordForService:keychainService account:keychainDBPassAccount error:&error];
if (dbPassword && !error) {
return YES;
}
if (error) {
DDLogWarn(@"Database password couldn't be accessed: %@", error.localizedDescription);
}
return NO;
}
- (void)backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:(NSString *)errorDescription
{
OWSAssert(
CurrentAppContext().isMainApp && CurrentAppContext().mainApplicationState == UIApplicationStateBackground);
// Sleep to give analytics events time to be delivered.
[NSThread sleepForTimeInterval:5.0f];
// Presumably this happened in response to a push notification. It's possible that the keychain is corrupted
// but it could also just be that the user hasn't yet unlocked their device since our password is
// kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
[NSException raise:TSStorageManagerExceptionName_DatabasePasswordInaccessibleWhileBackgrounded
format:@"%@", errorDescription];
}
- (NSData *)databasePassword
{
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
NSError *keyFetchError;
NSString *dbPassword =
[SAMKeychain passwordForService:keychainService account:keychainDBPassAccount error:&keyFetchError];
if (keyFetchError) {
NSString *errorDescription =
[NSString stringWithFormat:@"Database password inaccessible. No unlock since device restart? Error: %@",
keyFetchError];
if (CurrentAppContext().isMainApp) {
UIApplicationState applicationState = CurrentAppContext().mainApplicationState;
errorDescription =
[errorDescription stringByAppendingFormat:@", ApplicationState: %d", (int)applicationState];
}
DDLogError(@"%@ %@", self.logTag, errorDescription);
[DDLog flushLog];
if (CurrentAppContext().isMainApp) {
UIApplicationState applicationState = CurrentAppContext().mainApplicationState;
if (applicationState == UIApplicationStateBackground) {
// TODO: Rather than crash here, we should detect the situation earlier
// and exit gracefully - (in the app delegate?). See the `
// This is a last ditch effort to avoid blowing away the user's database.
[self backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:errorDescription];
}
} else {
[self backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:
@"Password inaccessible; not main app."];
}
// At this point, either this is a new install so there's no existing password to retrieve
// or the keychain has become corrupt. Either way, we want to get back to a
// "known good state" and behave like a new install.
BOOL shouldHavePassword = [NSFileManager.defaultManager fileExistsAtPath:[self dbPath]];
if (shouldHavePassword) {
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]);
}
// Try to reset app by deleting database.
[self resetSignalStorage];
dbPassword = [self createAndSetNewDatabasePassword];
}
return [dbPassword dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSString *)createAndSetNewDatabasePassword
{
NSString *newDBPassword = [[Randomness generateRandomBytes:30] base64EncodedString];
NSError *keySetError;
[SAMKeychain setPassword:newDBPassword forService:keychainService account:keychainDBPassAccount error:&keySetError];
if (keySetError) {
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotStoreDatabasePassword]);
[self deletePasswordFromKeychain];
// Sleep to give analytics events time to be delivered.
[NSThread sleepForTimeInterval:15.0f];
[NSException raise:TSStorageManagerExceptionName_DatabasePasswordUnwritable
format:@"Setting DB password failed with error: %@", keySetError];
} else {
DDLogWarn(@"Succesfully set new DB password.");
}
return newDBPassword;
}
#pragma mark - convenience methods
- (void)purgeCollection:(NSString *)collection {
[self.dbReadWriteConnection purgeCollection:collection];
}
- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:object forKey:key inCollection:collection];
}];
}
- (void)removeObjectForKey:(NSString *)string inCollection:(NSString *)collection {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeObjectForKey:string inCollection:collection];
}];
}
- (id)objectForKey:(NSString *)key inCollection:(NSString *)collection {
__block NSString *object;
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
object = [transaction objectForKey:key inCollection:collection];
}];
return object;
}
- (nullable NSDictionary *)dictionaryForKey:(NSString *)key inCollection:(NSString *)collection
{
__block NSDictionary *object;
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
object = [transaction objectForKey:key inCollection:collection];
}];
return object;
}
- (nullable NSString *)stringForKey:(NSString *)key inCollection:(NSString *)collection
{
NSString *string = [self objectForKey:key inCollection:collection];
return string;
}
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection {
NSNumber *boolNum = [self objectForKey:key inCollection:collection];
return [boolNum boolValue];
}
- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection
{
NSData *data = [self objectForKey:key inCollection:collection];
return data;
}
- (nullable ECKeyPair *)keyPairForKey:(NSString *)key inCollection:(NSString *)collection
+ (YapDatabaseConnection *)dbReadConnection
{
ECKeyPair *keyPair = [self objectForKey:key inCollection:collection];
return keyPair;
return TSStorageManager.sharedManager.dbReadConnection;
}
- (nullable PreKeyRecord *)preKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection
+ (YapDatabaseConnection *)dbReadWriteConnection
{
PreKeyRecord *preKeyRecord = [self objectForKey:key inCollection:collection];
return preKeyRecord;
}
- (nullable SignedPreKeyRecord *)signedPreKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection
{
SignedPreKeyRecord *preKeyRecord = [self objectForKey:key inCollection:collection];
return preKeyRecord;
}
- (int)intForKey:(NSString *)key inCollection:(NSString *)collection {
int integer = [[self objectForKey:key inCollection:collection] intValue];
return integer;
}
- (void)setInt:(int)integer forKey:(NSString *)key inCollection:(NSString *)collection {
[self setObject:[NSNumber numberWithInt:integer] forKey:key inCollection:collection];
}
- (int)incrementIntForKey:(NSString *)key inCollection:(NSString *)collection
{
__block int value = 0;
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
value = [[transaction objectForKey:key inCollection:collection] intValue];
value++;
[transaction setObject:@(value) forKey:key inCollection:collection];
}];
return value;
}
- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection
{
NSNumber *value = [self objectForKey:key inCollection:collection];
if (value) {
return [NSDate dateWithTimeIntervalSince1970:value.doubleValue];
} else {
return nil;
}
}
- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection
{
[self setObject:@(value.timeIntervalSince1970) forKey:key inCollection:collection];
return TSStorageManager.sharedManager.dbReadWriteConnection;
}
- (void)deleteThreadsAndMessages {
@ -691,11 +211,6 @@ void setDatabaseInitialized()
[TSAttachmentStream deleteAttachments];
}
- (void)deletePasswordFromKeychain
{
[SAMKeychain deletePasswordForService:keychainService account:keychainDBPassAccount];
}
- (void)deleteDatabaseFile
{
NSError *error;
@ -705,19 +220,6 @@ void setDatabaseInitialized()
}
}
- (void)resetSignalStorage
{
self.database = nil;
_dbReadConnection = nil;
_dbReadWriteConnection = nil;
[self deletePasswordFromKeychain];
[self deleteDatabaseFile];
[TSAttachmentStream deleteAttachments];
}
@end
NS_ASSUME_NONNULL_END

@ -71,6 +71,8 @@
#endif
#define OWS_ABSTRACT_METHOD() OWSFail(@"Method needs to be implemented by subclasses.")
#pragma mark - Singleton Asserts
// The "singleton asserts" can be used to ensure

Loading…
Cancel
Save