diff --git a/SignalServiceKit/src/Contacts/Contact.m b/SignalServiceKit/src/Contacts/Contact.m index 0d362cc22..8bc4004bf 100644 --- a/SignalServiceKit/src/Contacts/Contact.m +++ b/SignalServiceKit/src/Contacts/Contact.m @@ -223,7 +223,7 @@ NS_ASSUME_NONNULL_BEGIN [OWSPrimaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { for (PhoneNumber *number in self.parsedPhoneNumbers) { - if ([SignalRecipient isRegisteredSignalAccount:number.toE164 transaction:transaction]) { + if ([SignalRecipient isRegisteredRecipient:number.toE164 transaction:transaction]) { [identifiers addObject:number.toE164]; } } diff --git a/SignalServiceKit/src/Contacts/ContactsUpdater.m b/SignalServiceKit/src/Contacts/ContactsUpdater.m index 7adb95f20..cdd883651 100644 --- a/SignalServiceKit/src/Contacts/ContactsUpdater.m +++ b/SignalServiceKit/src/Contacts/ContactsUpdater.m @@ -165,10 +165,10 @@ NS_ASSUME_NONNULL_BEGIN for (NSString *recipientId in recipientIdsToLookup) { if ([registeredRecipientIds containsObject:recipientId]) { SignalRecipient *recipient = - [SignalRecipient markAccountAsRegistered:recipientId transaction:transaction]; + [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; [recipients addObject:recipient]; } else { - [SignalRecipient markAccountAsNotRegistered:recipientId transaction:transaction]; + [SignalRecipient removeUnregisteredRecipient:recipientId transaction:transaction]; } } }]; diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.h b/SignalServiceKit/src/Contacts/SignalRecipient.h index 8f5438d44..e1ef16f7b 100644 --- a/SignalServiceKit/src/Contacts/SignalRecipient.h +++ b/SignalServiceKit/src/Contacts/SignalRecipient.h @@ -36,13 +36,14 @@ NS_ASSUME_NONNULL_BEGIN - (NSComparisonResult)compare:(SignalRecipient *)other; -+ (BOOL)isRegisteredSignalAccount:(NSString *)recipientId transaction:(YapDatabaseReadTransaction *)transaction; - -+ (SignalRecipient *)markAccountAsRegistered:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction; -+ (void)markAccountAsRegistered:(NSString *)recipientId - deviceId:(UInt32)deviceId - transaction:(YapDatabaseReadWriteTransaction *)transaction; -+ (void)markAccountAsNotRegistered:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction; ++ (BOOL)isRegisteredRecipient:(NSString *)recipientId transaction:(YapDatabaseReadTransaction *)transaction; + ++ (SignalRecipient *)markRecipientAsRegisteredAndGet:(NSString *)recipientId + transaction:(YapDatabaseReadWriteTransaction *)transaction; ++ (void)markRecipientAsRegistered:(NSString *)recipientId + deviceId:(UInt32)deviceId + transaction:(YapDatabaseReadWriteTransaction *)transaction; ++ (void)removeUnregisteredRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction; @end diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.m b/SignalServiceKit/src/Contacts/SignalRecipient.m index ad7e39cc5..93d40bbf1 100644 --- a/SignalServiceKit/src/Contacts/SignalRecipient.m +++ b/SignalServiceKit/src/Contacts/SignalRecipient.m @@ -146,7 +146,14 @@ NS_ASSUME_NONNULL_BEGIN [self addDevices:devices]; - SignalRecipient *latest = [SignalRecipient markAccountAsRegistered:self.recipientId transaction:transaction]; + SignalRecipient *latest = + [SignalRecipient markRecipientAsRegisteredAndGet:self.recipientId transaction:transaction]; + + if ([devices isSubsetOfSet:latest.devices.set]) { + return; + } + DDLogDebug(@"%@ adding devices: %@, to recipient: %@", self.logTag, devices, latest.recipientId); + [latest addDevices:devices]; [latest saveWithTransaction_internal:transaction]; } @@ -158,7 +165,14 @@ NS_ASSUME_NONNULL_BEGIN [self removeDevices:devices]; - SignalRecipient *latest = [SignalRecipient markAccountAsRegistered:self.recipientId transaction:transaction]; + SignalRecipient *latest = + [SignalRecipient markRecipientAsRegisteredAndGet:self.recipientId transaction:transaction]; + + if (![devices isSubsetOfSet:latest.devices.set]) { + return; + } + DDLogDebug(@"%@ removing devices: %@, from recipient: %@", self.logTag, devices, latest.recipientId); + [latest removeDevices:devices]; [latest saveWithTransaction_internal:transaction]; } @@ -187,13 +201,14 @@ NS_ASSUME_NONNULL_BEGIN DDLogVerbose(@"%@ saved signal recipient: %@", self.logTag, self.recipientId); } -+ (BOOL)isRegisteredSignalAccount:(NSString *)recipientId transaction:(YapDatabaseReadTransaction *)transaction ++ (BOOL)isRegisteredRecipient:(NSString *)recipientId transaction:(YapDatabaseReadTransaction *)transaction { SignalRecipient *_Nullable instance = [self recipientForRecipientId:recipientId transaction:transaction]; return instance != nil; } -+ (SignalRecipient *)markAccountAsRegistered:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction ++ (SignalRecipient *)markRecipientAsRegisteredAndGet:(NSString *)recipientId + transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(transaction); OWSAssert(recipientId.length > 0); @@ -209,14 +224,14 @@ NS_ASSUME_NONNULL_BEGIN return instance; } -+ (void)markAccountAsRegistered:(NSString *)recipientId - deviceId:(UInt32)deviceId - transaction:(YapDatabaseReadWriteTransaction *)transaction ++ (void)markRecipientAsRegistered:(NSString *)recipientId + deviceId:(UInt32)deviceId + transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(transaction); OWSAssert(recipientId.length > 0); - SignalRecipient *recipient = [self markAccountAsRegistered:recipientId transaction:transaction]; + SignalRecipient *recipient = [self markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; if (![recipient.devices containsObject:@(deviceId)]) { DDLogDebug(@"%@ in %s adding device %u to existing recipient.", self.logTag, @@ -228,7 +243,7 @@ NS_ASSUME_NONNULL_BEGIN } } -+ (void)markAccountAsNotRegistered:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction ++ (void)removeUnregisteredRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(transaction); OWSAssert(recipientId.length > 0); @@ -237,6 +252,7 @@ NS_ASSUME_NONNULL_BEGIN if (!instance) { return; } + DDLogDebug(@"%@ removing recipient: %@", self.logTag, recipientId); [instance removeWithTransaction:transaction]; } diff --git a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m index ab4dafab6..78698d056 100644 --- a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m +++ b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m @@ -110,9 +110,9 @@ NS_ASSUME_NONNULL_BEGIN DecryptSuccessBlock successBlock = ^(NSData *_Nullable plaintextData, YapDatabaseReadWriteTransaction *transaction) { - [SignalRecipient markAccountAsRegistered:envelope.source - deviceId:envelope.sourceDevice - transaction:transaction]; + [SignalRecipient markRecipientAsRegistered:envelope.source + deviceId:envelope.sourceDevice + transaction:transaction]; successBlockParameter(plaintextData, transaction); }; diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index c8960db37..b161f376d 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -452,7 +452,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; OWSAssert(message); NSMutableArray *recipients = [NSMutableArray new]; - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { for (NSString *recipientId in recipientIds) { SignalRecipient *recipient = [SignalRecipient getOrBuildUnsavedRecipientForRecipientId:recipientId transaction:transaction]; @@ -705,21 +705,24 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; message:(TSOutgoingMessage *)message thread:(TSThread *)thread { - [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { if (thread.isGroupThread) { // Mark as "skipped" group members who no longer have signal accounts. [message updateWithSkippedRecipient:recipient.recipientId transaction:transaction]; } - if (![SignalRecipient isRegisteredSignalAccount:recipient.recipientId transaction:transaction]) { + if (![SignalRecipient isRegisteredRecipient:recipient.recipientId transaction:transaction]) { return; } - [SignalRecipient markAccountAsNotRegistered:recipient.recipientId - transaction:transaction]; + [SignalRecipient removeUnregisteredRecipient:recipient.recipientId transaction:transaction]; [[TSInfoMessage userNotRegisteredMessageInThread:thread recipientId:recipient.recipientId] saveWithTransaction:transaction]; + + // TODO: Should we deleteAllSessionsForContact here? + // If so, we'll need to avoid doing a prekey fetch every + // time we try to send a message to an unregistered user. }]; } @@ -731,6 +734,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; success:(void (^)(void))successHandler failure:(RetryableFailureHandler)failureHandler { + OWSAssert(message); + OWSAssert(recipient); OWSAssert(thread || [message isKindOfClass:[OWSOutgoingSyncMessage class]]); DDLogInfo(@"%@ attempting to send message: %@, timestamp: %llu, recipient: %@", @@ -773,7 +778,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; NSArray *deviceMessages; @try { - deviceMessages = [self deviceMessages:message forRecipient:recipient]; + deviceMessages = [self deviceMessages:message recipient:recipient]; } @catch (NSException *exception) { deviceMessages = @[]; if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { @@ -908,6 +913,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; } else if (!mayHaveLinkedDevices && hasDeviceMessages) { OWSFail(@"%@ sync message has device messages for unknown secondary devices.", self.logTag); } + } else { + OWSAssert(deviceMessages.count > 0); } if (deviceMessages.count == 0) { @@ -1016,7 +1023,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; // If we've just delivered a message to a user, we know they // have a valid Signal account. - [SignalRecipient markAccountAsRegistered:recipient.recipientId transaction:transaction]; + [SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction]; }]; [self handleMessageSentLocally:message]; @@ -1066,6 +1073,25 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; }); }; + void (^handle404)(void) = ^{ + DDLogWarn(@"%@ Unregistered recipient: %@", self.logTag, recipient.uniqueId); + + OWSAssert(thread); + + dispatch_async([OWSDispatch sendingQueue], ^{ + [self unregisteredRecipient:recipient message:message thread:thread]; + + NSError *error = OWSErrorMakeNoSuchSignalRecipientError(); + // No need to retry if the recipient is not registered. + [error setIsRetryable:NO]; + // If one member of a group deletes their account, + // the group should ignore errors when trying to send + // messages to this ex-member. + [error setShouldBeIgnoredForGroups:YES]; + failureHandler(error); + }); + }; + switch (statusCode) { case 401: { DDLogWarn(@"%@ Unable to send due to invalid credentials. Did the user's client get de-authed by " @@ -1079,23 +1105,15 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; return failureHandler(error); } case 404: { - DDLogWarn(@"%@ Unregistered recipient: %@", self.logTag, recipient.uniqueId); - - OWSAssert(thread); - - [self unregisteredRecipient:recipient message:message thread:thread]; - NSError *error = OWSErrorMakeNoSuchSignalRecipientError(); - // No need to retry if the recipient is not registered. - [error setIsRetryable:NO]; - // If one member of a group deletes their account, - // the group should ignore errors when trying to send - // messages to this ex-member. - [error setShouldBeIgnoredForGroups:YES]; - return failureHandler(error); + handle404(); + return; } case 409: { // Mismatched devices - DDLogWarn(@"%@ Mismatched devices for recipient: %@", self.logTag, recipient.uniqueId); + DDLogWarn(@"%@ Mismatched devices for recipient: %@ (%zd)", + self.logTag, + recipient.uniqueId, + deviceMessages.count); NSError *_Nullable error = nil; NSDictionary *_Nullable responseJson = nil; @@ -1108,6 +1126,13 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; return failureHandler(error); } + NSNumber *_Nullable errorCode = responseJson[@"code"]; + if ([@(404) isEqual:errorCode]) { + // Some 404s are returned as 409. + handle404(); + return; + } + [self handleMismatchedDevicesWithResponseJson:responseJson recipient:recipient completion:retrySend]; break; } @@ -1225,8 +1250,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; }]; } -- (NSArray *)deviceMessages:(TSOutgoingMessage *)message - forRecipient:(SignalRecipient *)recipient +- (NSArray *)deviceMessages:(TSOutgoingMessage *)message recipient:(SignalRecipient *)recipient { OWSAssert(message); OWSAssert(recipient); @@ -1247,7 +1271,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { @try { messageDict = [self encryptedMessageWithPlaintext:plainText - toRecipient:recipient.uniqueId + recipient:recipient deviceId:deviceNumber keyingStorage:self.primaryStorage isSilent:message.isSilent @@ -1284,18 +1308,21 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; } - (NSDictionary *)encryptedMessageWithPlaintext:(NSData *)plainText - toRecipient:(NSString *)identifier + recipient:(SignalRecipient *)recipient deviceId:(NSNumber *)deviceNumber keyingStorage:(OWSPrimaryStorage *)storage isSilent:(BOOL)isSilent transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(plainText); - OWSAssert(identifier.length > 0); + OWSAssert(recipient); OWSAssert(deviceNumber); OWSAssert(storage); OWSAssert(transaction); + NSString *identifier = recipient.recipientId; + OWSAssert(identifier.length > 0); + if (![storage containsSession:identifier deviceId:[deviceNumber intValue] protocolContext:transaction]) { __block dispatch_semaphore_t sema = dispatch_semaphore_create(0); __block PreKeyBundle *_Nullable bundle; @@ -1319,6 +1346,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; DDLogError(@"Server replied to PreKeyBundle request with error: %@", error); NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; if (response.statusCode == 404) { + [recipient removeDevicesFromRegisteredRecipient:[NSSet setWithObject:deviceNumber] + transaction:transaction]; + // Can't throw exception from within callback as it's probabably a different thread. exception = [NSException exceptionWithName:OWSMessageSenderInvalidDeviceException reason:@"Device not registered"