From a29c4ce5d6bcdda0ab838e4e41675c7b0082bb98 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 18 Dec 2017 17:38:51 -0500 Subject: [PATCH] Pull out OWSStorage base class for TSStorageManager. --- Signal/src/environment/SignalApp.m | 2 +- Signal/src/util/FunctionalUtil.m | 16 +- Signal/src/util/NumberUtil.m | 10 +- SignalMessaging/categories/NSString+OWS.m | 2 +- SignalMessaging/contacts/OWSContactsSyncing.m | 10 +- .../environment/SignalKeyingStorage.m | 8 +- SignalMessaging/utils/OWSPreferences.m | 10 +- .../src/Contacts/PhoneNumberUtil.m | 6 +- SignalServiceKit/src/Contacts/TSThread.m | 6 +- .../TSInvalidIdentityKeyErrorMessage.m | 6 +- .../src/Network/OWSSignalService.m | 30 +- SignalServiceKit/src/Security/SecurityUtils.m | 8 +- .../AxolotlStore/TSStorageManager+Calling.m | 6 +- .../TSStorageManager+PreKeyStore.m | 26 +- .../TSStorageManager+SignedPreKeyStore.m | 62 ++- SignalServiceKit/src/Storage/OWSStorage.h | 51 ++ SignalServiceKit/src/Storage/OWSStorage.m | 521 ++++++++++++++++++ SignalServiceKit/src/Storage/TSRecipient.h | 13 + SignalServiceKit/src/Storage/TSRecipient.m | 13 + .../src/Storage/TSStorageManager.h | 36 +- .../src/Storage/TSStorageManager.m | 510 +---------------- SignalServiceKit/src/Util/Asserts.h | 2 + 22 files changed, 720 insertions(+), 634 deletions(-) create mode 100644 SignalServiceKit/src/Storage/OWSStorage.h create mode 100644 SignalServiceKit/src/Storage/OWSStorage.m create mode 100644 SignalServiceKit/src/Storage/TSRecipient.h create mode 100644 SignalServiceKit/src/Storage/TSRecipient.m diff --git a/Signal/src/environment/SignalApp.m b/Signal/src/environment/SignalApp.m index acd786008..76409b677 100644 --- a/Signal/src/environment/SignalApp.m +++ b/Signal/src/environment/SignalApp.m @@ -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]; diff --git a/Signal/src/util/FunctionalUtil.m b/Signal/src/util/FunctionalUtil.m index f919197da..ed74b9ead 100644 --- a/Signal/src/util/FunctionalUtil.m +++ b/Signal/src/util/FunctionalUtil.m @@ -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]; diff --git a/Signal/src/util/NumberUtil.m b/Signal/src/util/NumberUtil.m index db8424e1c..1bf02ce2d 100644 --- a/Signal/src/util/NumberUtil.m +++ b/Signal/src/util/NumberUtil.m @@ -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); } diff --git a/SignalMessaging/categories/NSString+OWS.m b/SignalMessaging/categories/NSString+OWS.m index 71324b443..afd59123e 100644 --- a/SignalMessaging/categories/NSString+OWS.m +++ b/SignalMessaging/categories/NSString+OWS.m @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)removeAllCharactersIn:(NSCharacterSet *)characterSet { - ows_require(characterSet != nil); + OWSAssert(characterSet != nil); return [[self componentsSeparatedByCharactersInSet:characterSet] componentsJoinedByString:@""]; } diff --git a/SignalMessaging/contacts/OWSContactsSyncing.m b/SignalMessaging/contacts/OWSContactsSyncing.m index 140fb4669..7323fd548 100644 --- a/SignalMessaging/contacts/OWSContactsSyncing.m +++ b/SignalMessaging/contacts/OWSContactsSyncing.m @@ -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; diff --git a/SignalMessaging/environment/SignalKeyingStorage.m b/SignalMessaging/environment/SignalKeyingStorage.m index 67963c1d0..1890120ee 100644 --- a/SignalMessaging/environment/SignalKeyingStorage.m +++ b/SignalMessaging/environment/SignalKeyingStorage.m @@ -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 diff --git a/SignalMessaging/utils/OWSPreferences.m b/SignalMessaging/utils/OWSPreferences.m index 9180a3fb3..31283ae15 100644 --- a/SignalMessaging/utils/OWSPreferences.m +++ b/SignalMessaging/utils/OWSPreferences.m @@ -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 diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m index 42189c711..ca1f715a2 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m @@ -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; diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index 4fa1b9b8f..f7ef5a92d 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -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 *)recipientIdentifiers { - NSAssert(FALSE, @"Should be implemented in subclasses"); + OWSFail(@"Should be implemented in subclasses"); return @[]; } diff --git a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m index 05606cfc2..efcc7b01d 100644 --- a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m +++ b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m @@ -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; } diff --git a/SignalServiceKit/src/Network/OWSSignalService.m b/SignalServiceKit/src/Network/OWSSignalService.m index 2e4176a6f..cc396cb80 100644 --- a/SignalServiceKit/src/Network/OWSSignalService.m +++ b/SignalServiceKit/src/Network/OWSSignalService.m @@ -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 diff --git a/SignalServiceKit/src/Security/SecurityUtils.m b/SignalServiceKit/src/Security/SecurityUtils.m index cce1f8986..39a2ff575 100644 --- a/SignalServiceKit/src/Security/SecurityUtils.m +++ b/SignalServiceKit/src/Security/SecurityUtils.m @@ -3,17 +3,13 @@ // #import "SecurityUtils.h" +#import @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 diff --git a/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+Calling.m b/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+Calling.m index ce772e109..8beec5964 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+Calling.m +++ b/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+Calling.m @@ -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 diff --git a/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+PreKeyStore.m b/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+PreKeyStore.m index 466310977..583d79dbb 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+PreKeyStore.m +++ b/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+PreKeyStore.m @@ -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. diff --git a/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+SignedPreKeyStore.m b/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+SignedPreKeyStore.m index 7ed3a28e9..05acadc48 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+SignedPreKeyStore.m +++ b/SignalServiceKit/src/Storage/AxolotlStore/TSStorageManager+SignedPreKeyStore.m @@ -6,10 +6,9 @@ #import "TSStorageManager+PreKeyStore.h" #import "TSStorageManager+SignedPreKeyStore.h" #import "TSStorageManager+keyFromIntLong.h" - -#import #import #import +#import 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 diff --git a/SignalServiceKit/src/Storage/OWSStorage.h b/SignalServiceKit/src/Storage/OWSStorage.h new file mode 100644 index 000000000..8a777d3f7 --- /dev/null +++ b/SignalServiceKit/src/Storage/OWSStorage.h @@ -0,0 +1,51 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import + +// TODO: Remove this import. +#import "YapDatabaseConnection+OWS.h" + +// TODO: Remove this import. +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol OWSDatabaseConnectionDelegate + +- (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 diff --git a/SignalServiceKit/src/Storage/OWSStorage.m b/SignalServiceKit/src/Storage/OWSStorage.m new file mode 100644 index 000000000..ac9cefb69 --- /dev/null +++ b/SignalServiceKit/src/Storage/OWSStorage.m @@ -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 +#import + +//#import + +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 delegate; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDatabase:(YapDatabase *)database + delegate:(id)delegate NS_DESIGNATED_INITIALIZER; + +@end + +#pragma mark - + +@implementation OWSDatabaseConnection + +- (id)initWithDatabase:(YapDatabase *)database delegate:(id)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 delegate = self.delegate; + OWSAssert(delegate); + OWSAssert(delegate.isDatabaseInitialized); + + [super readWriteWithBlock:block]; +} + +- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block +{ + id delegate = self.delegate; + OWSAssert(delegate); + OWSAssert(delegate.isDatabaseInitialized); + + [super asyncReadWriteWithBlock:block]; +} + +- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block + completionBlock:(nullable dispatch_block_t)completionBlock +{ + id 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 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 delegate; + +- (instancetype)init NS_UNAVAILABLE; +- (id)initWithPath:(NSString *)inPath + serializer:(YapDatabaseSerializer)inSerializer + deserializer:(YapDatabaseDeserializer)inDeserializer + options:(YapDatabaseOptions *)inOptions + delegate:(id)delegate NS_DESIGNATED_INITIALIZER; + +@end + +#pragma mark - + +@implementation OWSDatabase + +- (id)initWithPath:(NSString *)inPath + serializer:(YapDatabaseSerializer)inSerializer + deserializer:(YapDatabaseDeserializer)inDeserializer + options:(YapDatabaseOptions *)inOptions + delegate:(id)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 delegate = self.delegate; + OWSAssert(delegate); + + OWSDatabaseConnection *connection = [[OWSDatabaseConnection alloc] initWithDatabase:self delegate:delegate]; + [self addConnection:connection]; + return connection; +} + +@end + +#pragma mark - + +@interface OWSUnknownDBObject : NSObject + +@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 + +@end + +#pragma mark - + +@implementation OWSUnarchiverDelegate + +- (nullable Class)unarchiver:(NSKeyedUnarchiver *)unarchiver + cannotDecodeObjectOfClassName:(NSString *)name + originalClasses:(NSArray *)classNames +{ + DDLogError(@"%@ Could not decode object: %@", self.logTag, name); + OWSProdError([OWSAnalyticsEvents storageErrorCouldNotDecodeClass]); + return [OWSUnknownDBObject class]; +} + +@end + +#pragma mark - + +@interface OWSStorage () + +@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 diff --git a/SignalServiceKit/src/Storage/TSRecipient.h b/SignalServiceKit/src/Storage/TSRecipient.h new file mode 100644 index 000000000..5d2c3c74d --- /dev/null +++ b/SignalServiceKit/src/Storage/TSRecipient.h @@ -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 + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSRecipient.m b/SignalServiceKit/src/Storage/TSRecipient.m new file mode 100644 index 000000000..bf3ac0b56 --- /dev/null +++ b/SignalServiceKit/src/Storage/TSRecipient.m @@ -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 diff --git a/SignalServiceKit/src/Storage/TSStorageManager.h b/SignalServiceKit/src/Storage/TSStorageManager.h index 753408f8e..c3c3d11cc 100644 --- a/SignalServiceKit/src/Storage/TSStorageManager.h +++ b/SignalServiceKit/src/Storage/TSStorageManager.h @@ -2,6 +2,7 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +#import "OWSStorage.h" #import "TSStorageKeys.h" #import "YapDatabaseConnection+OWS.h" #import @@ -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; diff --git a/SignalServiceKit/src/Storage/TSStorageManager.m b/SignalServiceKit/src/Storage/TSStorageManager.m index 84f02f176..815f6f0d3 100644 --- a/SignalServiceKit/src/Storage/TSStorageManager.m +++ b/SignalServiceKit/src/Storage/TSStorageManager.m @@ -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 -#import #import 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 - -@end - -#pragma mark - - -@interface OWSUnknownObject : NSObject - -@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 - -@end - -#pragma mark - - -@implementation OWSUnarchiverDelegate - -- (nullable Class)unarchiver:(NSKeyedUnarchiver *)unarchiver cannotDecodeObjectOfClassName:(NSString *)name originalClasses:(NSArray *)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 diff --git a/SignalServiceKit/src/Util/Asserts.h b/SignalServiceKit/src/Util/Asserts.h index d52817043..2d9bad1a8 100755 --- a/SignalServiceKit/src/Util/Asserts.h +++ b/SignalServiceKit/src/Util/Asserts.h @@ -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