diff --git a/Signal/src/ViewControllers/OWSLinkDeviceViewController.m b/Signal/src/ViewControllers/OWSLinkDeviceViewController.m index 52da44807..edc941136 100644 --- a/Signal/src/ViewControllers/OWSLinkDeviceViewController.m +++ b/Signal/src/ViewControllers/OWSLinkDeviceViewController.m @@ -9,6 +9,7 @@ #import "OWSProfileManager.h" #import "Signal-Swift.h" #import +#import #import #import #import @@ -18,9 +19,10 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSLinkDeviceViewController () -@property (strong, nonatomic) IBOutlet UIView *qrScanningView; -@property (strong, nonatomic) IBOutlet UILabel *scanningInstructionsLabel; -@property (strong, nonatomic) OWSQRCodeScanningViewController *qrScanningController; +@property (nonatomic) YapDatabaseConnection *dbConnection; +@property (nonatomic) IBOutlet UIView *qrScanningView; +@property (nonatomic) IBOutlet UILabel *scanningInstructionsLabel; +@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController; @property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager; @end @@ -31,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN { [super viewDidLoad]; + self.dbConnection = [[TSStorageManager sharedManager] newDatabaseConnection]; + // HACK to get full width preview layer CGRect oldFrame = self.qrScanningView.frame; self.qrScanningView.frame = CGRectMake( @@ -142,6 +146,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)provisionWithParser:(OWSDeviceProvisioningURLParser *)parser { + // Optimistically set this flag. + [OWSDevice setMayHaveLinkedDevices:YES dbConnection:self.dbConnection]; + ECKeyPair *_Nullable identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair]; OWSAssert(identityKeyPair); NSData *myPublicKey = identityKeyPair.publicKey; diff --git a/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m b/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m index ebd4490e5..9b5efc150 100644 --- a/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m +++ b/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m @@ -20,10 +20,10 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSLinkedDevicesTableViewController () -@property YapDatabaseConnection *dbConnection; -@property YapDatabaseViewMappings *deviceMappings; -@property NSTimer *pollingRefreshTimer; -@property BOOL isExpectingMoreDevices; +@property (nonatomic) YapDatabaseConnection *dbConnection; +@property (nonatomic) YapDatabaseViewMappings *deviceMappings; +@property (nonatomic) NSTimer *pollingRefreshTimer; +@property (nonatomic) BOOL isExpectingMoreDevices; @end @@ -131,6 +131,14 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1; __weak typeof(self) wself = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[OWSDevicesService new] getDevicesWithSuccess:^(NSArray *devices) { + // If we have more than one device; we may have a linked device. + if (devices.count > 1) { + // Setting this flag here shouldn't be necessary, but we do so + // because the "cost" is low and it will improve robustness. + [OWSDevice setMayHaveLinkedDevices:YES + dbConnection:[[TSStorageManager sharedManager] newDatabaseConnection]]; + } + if (devices.count > [OWSDevice numberOfKeysInCollection]) { // Got our new device, we can stop refreshing. wself.isExpectingMoreDevices = NO; @@ -243,7 +251,6 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1; return (NSInteger)[self.deviceMappings numberOfItemsInSection:(NSUInteger)section]; case OWSLinkedDevicesTableViewControllerSectionAddDevice: return 1; - default: DDLogError(@"Unknown section: %ld", (long)section); return 0; diff --git a/SignalServiceKit/src/Devices/OWSDevice.h b/SignalServiceKit/src/Devices/OWSDevice.h index ce3230a57..095f04909 100644 --- a/SignalServiceKit/src/Devices/OWSDevice.h +++ b/SignalServiceKit/src/Devices/OWSDevice.h @@ -12,9 +12,9 @@ extern uint32_t const OWSDevicePrimaryDeviceId; @interface OWSDevice : TSYapDatabaseObject @property (nonatomic, readonly) NSInteger deviceId; -@property (nullable, readonly) NSString *name; -@property (readonly) NSDate *createdAt; -@property (readonly) NSDate *lastSeenAt; +@property (nonatomic, readonly, nullable) NSString *name; +@property (nonatomic, readonly) NSDate *createdAt; +@property (nonatomic, readonly) NSDate *lastSeenAt; + (instancetype)deviceFromJSONDictionary:(NSDictionary *)deviceAttributes error:(NSError **)error; @@ -52,6 +52,11 @@ extern uint32_t const OWSDevicePrimaryDeviceId; */ - (BOOL)updateAttributesWithDevice:(OWSDevice *)other; +#pragma mark - "May Have Linked Devices" Flag + ++ (BOOL)mayHaveLinkedDevices:(YapDatabaseConnection *)dbConnection; ++ (void)setMayHaveLinkedDevices:(BOOL)value dbConnection:(YapDatabaseConnection *)dbConnection; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSDevice.m b/SignalServiceKit/src/Devices/OWSDevice.m index b6a7be014..886cbc5ae 100644 --- a/SignalServiceKit/src/Devices/OWSDevice.m +++ b/SignalServiceKit/src/Devices/OWSDevice.m @@ -5,26 +5,29 @@ #import "OWSDevice.h" #import "NSDate+OWS.h" #import "OWSError.h" +#import "TSStorageManager.h" #import "YapDatabaseConnection.h" #import "YapDatabaseTransaction.h" #import NS_ASSUME_NONNULL_BEGIN -static MTLValueTransformer *_millisecondTimestampToDateTransformer; uint32_t const OWSDevicePrimaryDeviceId = 1; +NSString *const kTSStorageManager_OWSDeviceCollection = @"kTSStorageManager_OWSDeviceCollection"; +NSString *const kTSStorageManager_MayHaveLinkedDevices = @"kTSStorageManager_MayHaveLinkedDevices"; @interface OWSDevice () -@property NSString *name; -@property NSDate *createdAt; -@property NSDate *lastSeenAt; +@property (nonatomic) NSInteger deviceId; +@property (nonatomic, nullable) NSString *name; +@property (nonatomic) NSDate *createdAt; +@property (nonatomic) NSDate *lastSeenAt; @end -@implementation OWSDevice +#pragma mark - -@synthesize name = _name; +@implementation OWSDevice + (instancetype)deviceFromJSONDictionary:(NSDictionary *)deviceAttributes error:(NSError **)error { @@ -76,38 +79,39 @@ uint32_t const OWSDevicePrimaryDeviceId = 1; + (MTLValueTransformer *)millisecondTimestampToDateTransformer { - if (!_millisecondTimestampToDateTransformer) { - _millisecondTimestampToDateTransformer = - [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError **error) { - if ([value isKindOfClass:[NSNumber class]]) { - NSNumber *number = (NSNumber *)value; - NSDate *result = [NSDate ows_dateWithMillisecondsSince1970:[number longLongValue]]; + static MTLValueTransformer *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError **error) { + if ([value isKindOfClass:[NSNumber class]]) { + NSNumber *number = (NSNumber *)value; + NSDate *result = [NSDate ows_dateWithMillisecondsSince1970:[number longLongValue]]; + if (result) { + *success = YES; + return result; + } + } + *success = NO; + DDLogError(@"%@ unable to decode date from %@", self.logTag, value); + *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson, @"Unable to decode date from %@"); + return nil; + } + reverseBlock:^id(id value, BOOL *success, NSError **error) { + if ([value isKindOfClass:[NSDate class]]) { + NSDate *date = (NSDate *)value; + NSNumber *result = [NSNumber numberWithLongLong:[NSDate ows_millisecondsSince1970ForDate:date]]; if (result) { *success = YES; return result; } } + DDLogError(@"%@ unable to encode date from %@", self.logTag, value); + *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncodeJson, @"Unable to encode date"); *success = NO; - DDLogError(@"%@ unable to decode date from %@", self.logTag, value); - *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson, @"Unable to decode date from %@"); return nil; - } - reverseBlock:^id(id value, BOOL *success, NSError **error) { - if ([value isKindOfClass:[NSDate class]]) { - NSDate *date = (NSDate *)value; - NSNumber *result = [NSNumber numberWithLongLong:[NSDate ows_millisecondsSince1970ForDate:date]]; - if (result) { - *success = YES; - return result; - } - } - DDLogError(@"%@ unable to encode date from %@", self.logTag, value); - *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncodeJson, @"Unable to encode date"); - *success = NO; - return nil; - }]; - } - return _millisecondTimestampToDateTransformer; + }]; + }); + return instance; } + (uint32_t)currentDeviceId @@ -178,6 +182,26 @@ uint32_t const OWSDevicePrimaryDeviceId = 1; return self.deviceId == device.deviceId; } +#pragma mark - "May Have Linked Devices" Flag + ++ (BOOL)mayHaveLinkedDevices:(YapDatabaseConnection *)dbConnection +{ + OWSAssert(dbConnection); + + return [dbConnection boolForKey:kTSStorageManager_MayHaveLinkedDevices + inCollection:kTSStorageManager_OWSDeviceCollection + defaultValue:YES]; +} + ++ (void)setMayHaveLinkedDevices:(BOOL)value dbConnection:(YapDatabaseConnection *)dbConnection +{ + OWSAssert(dbConnection); + + [dbConnection setBool:value + forKey:kTSStorageManager_MayHaveLinkedDevices + inCollection:kTSStorageManager_OWSDeviceCollection]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 95051e372..ba7a27a28 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -971,15 +971,12 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; // devices, then can safely skip sending sync message. // 1. Check OWSDevice's state. - __block BOOL hasSecondaryDevices; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - hasSecondaryDevices = [OWSDevice hasSecondaryDevicesWithTransaction:transaction]; - }]; + BOOL mayHaveLinkedDevices = [OWSDevice mayHaveLinkedDevices:self.dbConnection]; // 2. Check SignalRecipient's state. BOOL hasDeviceMessages = deviceMessages.count > 0; - if (!hasSecondaryDevices && !hasDeviceMessages) { + if (!mayHaveLinkedDevices && !hasDeviceMessages) { DDLogInfo(@"%@ Ignoring sync message without secondary devices: %@", self.logTag, [message class]); OWSAssert([message isKindOfClass:[OWSOutgoingSyncMessage class]]); @@ -990,7 +987,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; }); return; - } else if (hasSecondaryDevices) { + } else if (mayHaveLinkedDevices) { // We may have just linked a new secondary device which is not yet reflected in // the SignalRecipient that corresponds to ourself. Proceed. Client should learn // of new secondary devices via 409 "Mismatched devices" response. @@ -1113,6 +1110,13 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; NSArray *extraDevices = [dictionary objectForKey:@"extraDevices"]; NSArray *missingDevices = [dictionary objectForKey:@"missingDevices"]; + if (missingDevices.count > 0) { + NSString *localNumber = [TSAccountManager localNumber]; + if ([localNumber isEqualToString:recipient.uniqueId]) { + [OWSDevice setMayHaveLinkedDevices:YES dbConnection:self.dbConnection]; + } + } + dispatch_async([OWSDispatch sessionStoreQueue], ^{ if (extraDevices.count < 1 && missingDevices.count < 1) { OWSProdFail([OWSAnalyticsEvents messageSenderErrorNoMissingOrExtraDevices]); diff --git a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h index d6aac59cc..79f8e6018 100644 --- a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h +++ b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)hasObjectForKey:(NSString *)key inCollection:(NSString *)collection; - (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection; +- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue; - (int)intForKey:(NSString *)key inCollection:(NSString *)collection; - (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection; - (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection; diff --git a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m index 957d0d4c8..9f2d2c024 100644 --- a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m +++ b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m @@ -51,6 +51,11 @@ NS_ASSUME_NONNULL_BEGIN } - (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection +{ + return [self boolForKey:key inCollection:collection defaultValue:NO]; +} + +- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue { NSNumber *_Nullable number = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; return [number boolValue];