From 2e83a2bb853ce7dcb2752d8cd809803281cbda5a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 09:18:40 +1100 Subject: [PATCH 01/17] Set encrypted profile picture. Rotate profileKey every-time we encrypt a new profile picture. --- SignalMessaging/profiles/OWSProfileManager.m | 164 +++---------------- 1 file changed, 25 insertions(+), 139 deletions(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 907f97370..e85eae446 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -405,150 +405,36 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); OWSAssertDebug(successBlock); OWSAssertDebug(failureBlock); OWSAssertDebug(avatarData == nil || avatarData.length > 0); - - [[LKStorageAPI setProfilePicture:avatarData] - .thenOn(dispatch_get_main_queue(), ^(NSString *url) { - successBlock(url); - }) - .catchOn(dispatch_get_main_queue(), ^(id result) { - // There appears to be a bug in PromiseKit that sometimes causes catchOn - // to be invoked with the fulfilled promise's value as the error. The below - // is a quick and dirty workaround. - if ([result isKindOfClass:NSString.class]) { - successBlock(result); - } else { - failureBlock(result); - } - }) retainUntilComplete]; - /* - // We want to clear the local user's profile avatar as soon as - // we request the upload form, since that request clears our - // avatar on the service. - // - // TODO: Revisit this so that failed profile updates don't leave - // the profile avatar blank, etc. - void (^clearLocalAvatar)(void) = ^{ - OWSUserProfile *userProfile = self.localUserProfile; - [userProfile updateWithAvatarUrlPath:nil avatarFileName:nil dbConnection:self.dbConnection completion:nil]; - }; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // We always want to encrypt a profile with a new profile key + // This ensures that other users know that our profile picture was updated + OWSAES256Key newProfileKey = [OWSAES256Key generateRandomKey]; + NSData *_Nullable encryptedAvatarData; + if (avatarData) { + encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey]; + OWSAssertDebug(encryptedAvatarData.length > 0); + } - // See: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html - TSRequest *formRequest = [OWSRequestFactory profileAvatarUploadFormRequest]; - - [self.networkManager makeRequest:formRequest - success:^(NSURLSessionDataTask *task, id formResponseObject) { - if (avatarData == nil) { - OWSLogDebug(@"successfully cleared avatar"); - clearLocalAvatar(); - successBlock(nil); - return; - } - - if (![formResponseObject isKindOfClass:[NSDictionary class]]) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidResponse]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - NSDictionary *responseMap = formResponseObject; - OWSLogError(@"responseObject: %@", formResponseObject); - - NSString *formAcl = responseMap[@"acl"]; - if (![formAcl isKindOfClass:[NSString class]] || formAcl.length < 1) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidAcl]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - NSString *formKey = responseMap[@"key"]; - if (![formKey isKindOfClass:[NSString class]] || formKey.length < 1) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidKey]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - NSString *formPolicy = responseMap[@"policy"]; - if (![formPolicy isKindOfClass:[NSString class]] || formPolicy.length < 1) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidPolicy]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - NSString *formAlgorithm = responseMap[@"algorithm"]; - if (![formAlgorithm isKindOfClass:[NSString class]] || formAlgorithm.length < 1) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidAlgorithm]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - NSString *formCredential = responseMap[@"credential"]; - if (![formCredential isKindOfClass:[NSString class]] || formCredential.length < 1) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidCredential]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - NSString *formDate = responseMap[@"date"]; - if (![formDate isKindOfClass:[NSString class]] || formDate.length < 1) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidDate]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - NSString *formSignature = responseMap[@"signature"]; - if (![formSignature isKindOfClass:[NSString class]] || formSignature.length < 1) { - OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidSignature]); - return failureBlock( - OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed.")); - } - - [self.avatarHTTPManager POST:@"" - parameters:nil - constructingBodyWithBlock:^(id _Nonnull formData) { - NSData * (^formDataForString)(NSString *formString) = ^(NSString *formString) { - return [formString dataUsingEncoding:NSUTF8StringEncoding]; - }; - - // We have to build up the form manually vs. simply passing in a paramaters dict - // because AWS is sensitive to the order of the form params (at least the "key" - // field must occur early on). - // For consistency, all fields are ordered here in a known working order. - [formData appendPartWithFormData:formDataForString(formKey) name:@"key"]; - [formData appendPartWithFormData:formDataForString(formAcl) name:@"acl"]; - [formData appendPartWithFormData:formDataForString(formAlgorithm) name:@"x-amz-algorithm"]; - [formData appendPartWithFormData:formDataForString(formCredential) name:@"x-amz-credential"]; - [formData appendPartWithFormData:formDataForString(formDate) name:@"x-amz-date"]; - [formData appendPartWithFormData:formDataForString(formPolicy) name:@"policy"]; - [formData appendPartWithFormData:formDataForString(formSignature) name:@"x-amz-signature"]; - [formData appendPartWithFormData:formDataForString(OWSMimeTypeApplicationOctetStream) - name:@"Content-Type"]; - NSData *encryptedAvatarData = [self encryptProfileData:avatarData]; - OWSAssertDebug(encryptedAvatarData.length > 0); - [formData appendPartWithFormData:encryptedAvatarData name:@"file"]; - - OWSLogVerbose(@"constructed body"); - } - progress:^(NSProgress *_Nonnull uploadProgress) { - OWSLogVerbose(@"avatar upload progress: %.2f%%", uploadProgress.fractionCompleted * 100); - } - success:^(NSURLSessionDataTask *_Nonnull uploadTask, id _Nullable responseObject) { - OWSLogInfo(@"successfully uploaded avatar with key: %@", formKey); - successBlock(formKey); - } - failure:^(NSURLSessionDataTask *_Nullable uploadTask, NSError *error) { - OWSLogError(@"uploading avatar failed with error: %@", error); - clearLocalAvatar(); - return failureBlock(error); - }]; - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - // Only clear the local avatar if we have a response. Otherwise, we - // had a network failure and probably didn't reach the service. - if (task.response != nil) { - clearLocalAvatar(); - } - - OWSLogError(@"Failed to get profile avatar upload form: %@", error); - return failureBlock(error); + [[LKStorageAPI setProfilePicture:encryptedAvatarData] + .thenOn(dispatch_get_main_queue(), ^(NSString *url) { + [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ + successBlock(url); }]; + }) + .catchOn(dispatch_get_main_queue(), ^(id result) { + // There appears to be a bug in PromiseKit that sometimes causes catchOn + // to be invoked with the fulfilled promise's value as the error. The below + // is a quick and dirty workaround. + if ([result isKindOfClass:NSString.class]) { + [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ + successBlock(result); + }]; + } else { + failureBlock(result); + } + }) retainUntilComplete]; }); - */ } - (void)updateServiceWithProfileName:(nullable NSString *)localProfileName From 5f7ceeed6a004304edbbac4e36584c320e010d9b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 09:44:04 +1100 Subject: [PATCH 02/17] Decrypt profile picture after downloading. --- SignalMessaging/profiles/OWSProfileManager.m | 78 ++++++++++++++------ 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index e85eae446..d395058d8 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -1056,9 +1056,11 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } NSString *_Nullable avatarUrlPathAtStart = userProfile.avatarUrlPath; - if (userProfile.avatarUrlPath.length < 1) { + if (userProfile.profileKey.keyData.length < 1 || userProfile.avatarUrlPath.length < 1) { return; } + + OWSAES256Key *profileKeyAtStart = userProfile.profileKey; NSString *fileName = [self generateAvatarFilename]; NSString *filePath = [OWSUserProfile profileAvatarFilepathWithFilename:fileName]; @@ -1077,7 +1079,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); NSString *tempDirectory = OWSTemporaryDirectory(); NSString *tempFilePath = [tempDirectory stringByAppendingPathComponent:fileName]; - NSString *profilePictureURL = userProfile.avatarUrlPath; NSError *serializationError; NSMutableURLRequest *request = @@ -1091,9 +1092,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } NSURLSession* session = [NSURLSession sharedSession]; - NSURLSessionTask* downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { - + @synchronized(self.currentAvatarDownloads) { [self.currentAvatarDownloads removeObject:userProfile.recipientId]; @@ -1105,32 +1105,68 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *fileURL = [NSURL fileURLWithPath:filePath]; + NSURL *tempFileUrl = [NSURL fileURLWithPath:tempFilePath]; NSError *moveError; - if (![fileManager moveItemAtURL:location toURL:fileURL error:&moveError]) { + if (![fileManager moveItemAtURL:location toURL:tempFileUrl error:&moveError]) { OWSLogError(@"MoveItemAtURL for avatar failed: %@", moveError); return; } + + NSData *_Nullable encryptedData = (error ? nil : [NSData dataWithContentsOfFile:tempFilePath]); + NSData *_Nullable decryptedData = [self decryptProfileData:encryptedData profileKey:profileKeyAtStart]; + UIImage *_Nullable image = nil; + if (decryptedData) { + BOOL success = [decryptedData writeToFile:filePath atomically:YES]; + if (success) { + image = [UIImage imageWithContentsOfFile:filePath]; + } + } + + OWSUserProfile *latestUserProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:userProfile.recipientId dbConnection:self.dbConnection]; + + if (latestUserProfile.profileKey.keyData.length < 1 + || ![latestUserProfile.profileKey isEqual:userProfile.profileKey]) { + OWSLogWarn(@"Ignoring avatar download for obsolete user profile."); + } else if (![avatarUrlPathAtStart isEqualToString:latestUserProfile.avatarUrlPath]) { + OWSLogInfo(@"avatar url has changed during download"); + if (latestUserProfile.avatarUrlPath.length > 0) { + [self downloadAvatarForUserProfile:latestUserProfile]; + } + } else if (error) { + if ([response isKindOfClass:NSHTTPURLResponse.class] + && ((NSHTTPURLResponse *)response).statusCode == 403) { + OWSLogInfo(@"no avatar for: %@", userProfile.recipientId); + } else { + OWSLogError(@"avatar download for %@ failed with error: %@", userProfile.recipientId, error); + } + } else if (!encryptedData) { + OWSLogError(@"avatar encrypted data for %@ could not be read.", userProfile.recipientId); + } else if (!decryptedData) { + OWSLogError(@"avatar data for %@ could not be decrypted.", userProfile.recipientId); + } else if (!image) { + OWSLogError(@"avatar image for %@ could not be loaded with error: %@", userProfile.recipientId, error); + } else { + [self updateProfileAvatarCache:image filename:fileName]; - UIImage *image = [UIImage imageWithContentsOfFile:[fileURL path]]; - if (image) { - dispatch_async(dispatch_get_main_queue(), ^{ + [latestUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:^{ + [[NSNotificationCenter defaultCenter] + postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification + object:nil]; + }]; + } - [self updateProfileAvatarCache:image filename:fileName]; - - OWSUserProfile *latestUserProfile = - [OWSUserProfile getOrBuildUserProfileForRecipientId:userProfile.recipientId - dbConnection:self.dbConnection]; + // If we're updating the profile that corresponds to our local number, + // update the local profile as well. + if (userProfile.address.isLocalAddress) { + OWSUserProfile *localUserProfile = self.localUserProfile; + OWSAssertDebug(localUserProfile); - [latestUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:^{ - [[NSNotificationCenter defaultCenter] - postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification - object:nil]; - }]; - }); + [localUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:nil]; + [self updateProfileAvatarCache:image filename:fileName]; } - + OWSAssertDebug(backgroundTask); + backgroundTask = nil; }]; [downloadTask resume]; From 9cde3262547fb66f33e61d0dc33481d88de0d212 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 10:18:04 +1100 Subject: [PATCH 03/17] Correctly handle profile key update from incoming messages. --- SignalMessaging/profiles/OWSProfileManager.h | 2 +- SignalMessaging/profiles/OWSProfileManager.m | 59 +++++++++++-------- SignalMessaging/profiles/OWSUserProfile.h | 7 +++ SignalMessaging/profiles/OWSUserProfile.m | 14 +++++ .../src/Messages/OWSMessageManager.m | 53 ++++++++++------- .../src/Protocols/ProfileManagerProtocol.h | 7 ++- .../src/TestUtils/OWSFakeProfileManager.m | 2 +- 7 files changed, 93 insertions(+), 51 deletions(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.h b/SignalMessaging/profiles/OWSProfileManager.h index ff04242fd..ee3b64f51 100644 --- a/SignalMessaging/profiles/OWSProfileManager.h +++ b/SignalMessaging/profiles/OWSProfileManager.h @@ -83,7 +83,7 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; profileNameEncrypted:(nullable NSData *)profileNameEncrypted avatarUrlPath:(nullable NSString *)avatarUrlPath; -- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName profilePictureURL:(NSString *)profilePictureURL with:(YapDatabaseReadWriteTransaction *)transaction; +- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction; #pragma mark - User Interface diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index d395058d8..6d2cbc937 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -340,9 +340,18 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } } -- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction +- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName transaction:(YapDatabaseReadWriteTransaction *)transaction { - [self updateLocalProfileName:displayName avatarImage:nil success:^{ } failure:^{ }]; + [self.localUserProfile updateWithProfileName:displayName transaction:transaction]; +} + +- (void)updateUserPofileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction { + OWSAES256Key *profileKey = [OWSAES256Key keyWithData:profileKeyData]; + if (profileKey != nil) { + [self.localUserProfile updateWithProfileKey:profileKey transaction:transaction completion:^{ + [self.localUserProfile updateWithAvatarUrlPath:avatarURL transaction:transaction]; + }]; + } } - (nullable NSString *)profilePictureURL @@ -409,7 +418,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // We always want to encrypt a profile with a new profile key // This ensures that other users know that our profile picture was updated - OWSAES256Key newProfileKey = [OWSAES256Key generateRandomKey]; + OWSAES256Key *newProfileKey = [OWSAES256Key generateRandomKey]; NSData *_Nullable encryptedAvatarData; if (avatarData) { encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey]; @@ -942,7 +951,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); }]; } -- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId +- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OWSAES256Key *_Nullable profileKey = [OWSAES256Key keyWithData:profileKeyData]; @@ -950,28 +959,35 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); OWSFailDebug(@"Failed to make profile key for key data"); return; } - - OWSUserProfile *userProfile = - [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection]; - + + OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection]; + OWSAssertDebug(userProfile); if (userProfile.profileKey && [userProfile.profileKey.keyData isEqual:profileKey.keyData]) { // Ignore redundant update. return; } - + [userProfile clearWithProfileKey:profileKey dbConnection:self.dbConnection completion:^{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeUnknown - recipientId:recipientId]; - [self fetchProfileForRecipientId:recipientId]; - }); - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeUnknown + recipientId:recipientId]; + [userProfile updateWithProfileName:userProfile.profileName avatarUrlPath:avatarURL dbConnection:self.dbConnection completion:^{ + [self downloadAvatarForUserProfile:userProfile]; + }]; + // [self fetchProfileForRecipientId:recipientId]; + }); + }]; }); } +- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId +{ + [self setProfileKeyData:profileKeyData forRecipientId:recipientId avatarURL:nil]; +} + - (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId { return [self profileKeyForRecipientId:recipientId].keyData; @@ -1157,7 +1173,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); // If we're updating the profile that corresponds to our local number, // update the local profile as well. - if (userProfile.address.isLocalAddress) { + if ([self.tsAccountManager.localNumber isEqualToString:userProfile.recipientId]) { OWSUserProfile *localUserProfile = self.localUserProfile; OWSAssertDebug(localUserProfile); @@ -1229,17 +1245,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); }); } -- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName profilePictureURL:(NSString *)profilePictureURL with:(YapDatabaseReadWriteTransaction *)transaction +- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction { OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:contactID transaction:transaction]; - NSString *oldProfilePictureURL = userProfile.avatarUrlPath; - // Note: we keep using the old file name until we have the new one - // (otherwise the profile picuture would disspear for a short time) - NSString *oldAvatarFileName = userProfile.avatarFileName; - [userProfile updateWithProfileName:displayName avatarUrlPath:profilePictureURL avatarFileName:oldAvatarFileName transaction:transaction completion:nil]; - if (profilePictureURL && ![oldProfilePictureURL isEqual:profilePictureURL]) { - [self downloadAvatarForUserProfile:userProfile]; - } + [userProfile updateWithProfileName:displayName avatarUrlPath:userProfile.avatarUrlPath avatarFileName:userProfile.avatarFileName transaction:transaction completion:nil]; } - (BOOL)isNullableDataEqual:(NSData *_Nullable)left toData:(NSData *_Nullable)right diff --git a/SignalMessaging/profiles/OWSUserProfile.h b/SignalMessaging/profiles/OWSUserProfile.h index b26638f09..80ef6d71e 100644 --- a/SignalMessaging/profiles/OWSUserProfile.h +++ b/SignalMessaging/profiles/OWSUserProfile.h @@ -73,10 +73,17 @@ extern NSString *const kLocalProfileUniqueId; dbConnection:(YapDatabaseConnection *)dbConnection completion:(nullable OWSUserProfileCompletion)completion; +- (void)updateWithProfileKey:(OWSAES256Key *)profileKey + transaction:(YapDatabaseReadWriteTransaction *)transaction + completion:(nullable OWSUserProfileCompletion)completion; + - (void)clearWithProfileKey:(OWSAES256Key *)profileKey dbConnection:(YapDatabaseConnection *)dbConnection completion:(nullable OWSUserProfileCompletion)completion; +- (void)updateWithProfileName:(nullable NSString *)profileName transaction:(YapDatabaseReadWriteTransaction*)transaction; +- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction; + #pragma mark - Profile Avatars Directory + (NSString *)profileAvatarFilepathWithFilename:(NSString *)filename; diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index 91f2ea85c..cb73a9765 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -316,6 +316,20 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId"; completion:completion]; } +- (void)updateWithProfileName:(nullable NSString *)profileName transaction:(YapDatabaseReadWriteTransaction*)transaction +{ + [self updateWithProfileName:profileName avatarUrlPath:self.avatarUrlPath avatarFileName:self.avatarFileName transaction:transaction completion:nil]; +} + +- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction { + [self applyChanges:^(OWSUserProfile *userProfile) { + [userProfile setAvatarUrlPath:avatarUrlPath]; + } + functionName:__PRETTY_FUNCTION__ + transaction:transaction + completion:nil]; +} + - (void)updateWithAvatarUrlPath:(nullable NSString *)avatarUrlPath avatarFileName:(nullable NSString *)avatarFileName dbConnection:(YapDatabaseConnection *)dbConnection diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 4a8eade9b..4fe4b6241 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -554,17 +554,9 @@ NS_ASSUME_NONNULL_BEGIN return; } } + + [self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:envelope.source]; - if ([dataMessage hasProfileKey]) { - NSData *profileKey = [dataMessage profileKey]; - NSString *recipientId = envelope.source; - if (profileKey.length == kAES256_KeyByteLength) { - [self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId]; - } else { - OWSFailDebug( - @"Unexpected profile key length:%lu on message from:%@", (unsigned long)profileKey.length, recipientId); - } - } if (dataMessage.group) { TSGroupThread *_Nullable groupThread = @@ -932,10 +924,14 @@ NS_ASSUME_NONNULL_BEGIN // Loki: Try to update using the provided profile if (wasSentByMasterDevice) { dispatch_async(dispatch_get_main_queue(), ^{ - SSKProtoDataMessageLokiProfile *profile = syncMessage.sent.message.profile; + SSKProtoDataMessage *dataMessage = syncMessage.sent.message; + SSKProtoDataMessageLokiProfile *profile = dataMessage.profile; NSString *displayName = profile.displayName; NSString *profilePictureURL = profile.profilePicture; - [self.profileManager updateUserProfileWithDisplayName:displayName profilePictureURL:profilePictureURL transaction:transaction]; + [self.profileManager updateUserProfileWithDisplayName:displayName transaction:transaction]; + if ([dataMessage hasProfileKey]) { + [self.profileManager updateUserPofileKeyData:dataMessage.profileKey avatarURL:profilePictureURL transaction:transaction]; + } }); } @@ -1203,9 +1199,14 @@ NS_ASSUME_NONNULL_BEGIN envelopeAddress(envelope)); return; } + + if (dataMessage.profile == nil) { + OWSFailDebug(@"received profile key message without loki profile attached from: %@", envelopeAddress(envelope)); + return; + } id profileManager = SSKEnvironment.shared.profileManager; - [profileManager setProfileKeyData:profileKey forRecipientId:recipientId]; + [profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:dataMessage.profile.profilePicture]; } - (void)handleUnlinkDeviceMessageWithEnvelope:(SSKProtoEnvelope *)envelope dataMessage:(SSKProtoDataMessage *)dataMessage transaction:(YapDatabaseReadWriteTransaction *)transaction @@ -1388,9 +1389,9 @@ NS_ASSUME_NONNULL_BEGIN TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; - NSString *profilePictureURL = dataMessage.profile.profilePicture; + // Only set the display name here, the logic for updating profile pictures is handled when we're setting profile key NSString *displayName = dataMessage.profile.displayName; - [self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName profilePictureURL:profilePictureURL with:transaction]; + [self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName with:transaction]; switch (dataMessage.group.type) { case SSKProtoGroupContextTypeUpdate: { @@ -1588,13 +1589,8 @@ NS_ASSUME_NONNULL_BEGIN if (rawDisplayName != nil && rawDisplayName.length > 0) { displayName = [NSString stringWithFormat:@"%@ (...%@)", rawDisplayName, [incomingMessage.authorId substringFromIndex:incomingMessage.authorId.length - 8]]; } - NSString *rawProfilePictureURL = dataMessage.profile.profilePicture; - NSString *profilePictureURL = nil; - if (rawProfilePictureURL != nil && rawProfilePictureURL.length > 0) { - profilePictureURL = rawProfilePictureURL; - } - - [self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName profilePictureURL:profilePictureURL with:transaction]; + [self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName with:transaction]; + [self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:thread.contactIdentifier]; // Loki: Parse Loki specific properties if needed if (envelope.isPtpMessage) { incomingMessage.isP2P = YES; } @@ -1635,6 +1631,19 @@ NS_ASSUME_NONNULL_BEGIN } } +- (void)handleProfileKeyUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId { + if ([dataMessage hasProfileKey]) { + NSData *profileKey = [dataMessage profileKey]; + NSString *url = dataMessage.profile != nil ? dataMessage.profile.profilePicture : nil; + if (profileKey.length == kAES256_KeyByteLength) { + [self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:url]; + } else { + OWSFailDebug( + @"Unexpected profile key length:%lu on message from:%@", (unsigned long)profileKey.length, recipientId); + } + } +} + - (BOOL)canFriendRequestBeAutoAcceptedForThread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction { NSString *senderHexEncodedPublicKey = thread.contactIdentifier; diff --git a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h index 1e8820789..3afb804ea 100644 --- a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h @@ -5,6 +5,7 @@ @class OWSAES256Key; @class TSThread; @class YapDatabaseReadWriteTransaction; +@class OWSUserProfile; NS_ASSUME_NONNULL_BEGIN @@ -17,8 +18,8 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString *)profilePictureURL; - (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId; -- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId; +- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL; - (BOOL)isUserInProfileWhitelist:(NSString *)recipientId; @@ -31,7 +32,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)fetchProfileForRecipientId:(NSString *)recipientId; -- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName profilePictureURL:(NSString *)profilePictureURL with:(YapDatabaseReadWriteTransaction *)transaction; +- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName transaction:(YapDatabaseReadWriteTransaction *)transaction; +- (void)updateUserPofileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction; +- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction; @end diff --git a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m index 17cccf777..de7cf2044 100644 --- a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m +++ b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m @@ -48,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN return _localProfileKey; } -- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction +- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName transaction:(YapDatabaseReadWriteTransaction *)transaction { // Do nothing } From eafc2afe4ae28fb469feecdf370c281e5158d799 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 13:21:09 +1100 Subject: [PATCH 04/17] Attach a profile key if we're friends with a contact. --- SignalMessaging/profiles/OWSProfileManager.m | 4 +++- .../src/Messages/DeviceSyncing/OWSSyncContactsMessage.m | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 6d2cbc937..460709d59 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -916,6 +916,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); TSGroupThread *groupThread = (TSGroupThread *)thread; NSData *groupId = groupThread.groupModel.groupId; return [self isGroupIdInProfileWhitelist:groupId]; + } else if (thread.friendRequestStatus == LKThreadFriendRequestStatusFriends) { + return true; } else { NSString *recipientId = thread.contactIdentifier; return [self isUserInProfileWhitelist:recipientId]; @@ -1067,7 +1069,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (userProfile.avatarUrlPath.length < 1) { - OWSFailDebug(@"Malformed avatar URL: %@", userProfile.avatarUrlPath); + OWSLogDebug(@"Skipping downloading avatar for %@ because url is not set", userProfile.recipientId); return; } NSString *_Nullable avatarUrlPathAtStart = userProfile.avatarUrlPath; diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m index b2aef164d..acecb880a 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m @@ -8,6 +8,7 @@ #import "OWSContactsOutputStream.h" #import "OWSIdentityManager.h" #import "ProfileManagerProtocol.h" +#import "ProtoUtils.h" #import "SSKEnvironment.h" #import "SignalAccount.h" #import "TSAccountManager.h" @@ -111,6 +112,8 @@ NS_ASSUME_NONNULL_BEGIN [profileBuilder setProfilePicture:profilePictureURL ?: @""]; SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; [messageBuilder setProfile:[profileBuilder buildAndReturnError:nil]]; + [ProtoUtils addLocalProfileKeyToDataMessageBuilder:messageBuilder]; + SSKProtoSyncMessageSentBuilder *transcriptBuilder = [SSKProtoSyncMessageSent builder]; [transcriptBuilder setMessage:[messageBuilder buildAndReturnError:nil]]; [syncMessageBuilder setSent:[transcriptBuilder buildAndReturnError:nil]]; From 74943934738a478571142bc332786937ae1bd03b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 13:47:41 +1100 Subject: [PATCH 05/17] Set avatar on public chats. --- SignalMessaging/profiles/OWSProfileManager.m | 4 ++++ .../API/Public Chat/LokiPublicChatAPI.swift | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 460709d59..13472e66e 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -270,6 +270,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); void (^tryToUpdateService)(NSString *_Nullable, NSString *_Nullable) = ^( NSString *_Nullable avatarUrlPath, NSString *_Nullable avatarFileName) { [self updateServiceWithProfileName:profileName + avatarUrl:avatarUrlPath success:^{ OWSUserProfile *userProfile = self.localUserProfile; OWSAssertDebug(userProfile); @@ -447,6 +448,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } - (void)updateServiceWithProfileName:(nullable NSString *)localProfileName + avatarUrl:(nullable NSString *)avatarURL success:(void (^)(void))successBlock failure:(ProfileManagerFailureBlock)failureBlock { OWSAssertDebug(successBlock); @@ -461,6 +463,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); for (NSString *server in servers) { [[LKPublicChatAPI setDisplayName:localProfileName on:server] retainUntilComplete]; + [[LKPublicChatAPI setAvatar:avatarURL profileKey:self.localProfileKey.keyData on:server] retainUntilComplete]; } successBlock(); @@ -636,6 +639,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { [self updateServiceWithProfileName:oldProfileName + avatarUrl:localUserProfile.avatarUrlPath success:^{ OWSLogInfo(@"Update to profile name succeeded."); diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift index 44d985c14..d09a95a28 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift @@ -12,6 +12,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { // MARK: Public Chat private static let channelInfoType = "net.patter-app.settings" private static let attachmentType = "net.app.core.oembed" + public static let avatarType = "network.loki.messenger.avatar" @objc public static let publicChatMessageType = "network.loki.messenger.publicChat" @objc public static let defaultChats: [LokiPublicChat] = { @@ -287,6 +288,24 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { }.retryingIfNeeded(maxRetryCount: 3) } + public static func setAvatar(with url: String?, profileKey: Data, on server: String) -> Promise { + print("[Loki] Updating avatar on server: \(server).") + return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in + var annotation: JSON = ["type" : avatarType] + if let url = url { + annotation["value"] = ["profileKey": profileKey.base64EncodedString(), "url" : url] + } + let parameters: JSON = [ "annotations" : [annotation] ] + let url = URL(string: "\(server)/users/me")! + let request = TSRequest(url: url, method: "PATCH", parameters: parameters) + request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] + return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in }.recover(on: DispatchQueue.global()) { error in + print("Couldn't update avatar due to error: \(error).") + throw error + } + }.retryingIfNeeded(maxRetryCount: 3) + } + public static func getInfo(for channel: UInt64, on server: String) -> Promise { let url = URL(string: "\(server)/channels/\(channel)?include_annotations=1")! let request = TSRequest(url: url) @@ -329,4 +348,9 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { public static func objc_setDisplayName(to newDisplayName: String?, on server: String) -> AnyPromise { return AnyPromise.from(setDisplayName(to: newDisplayName, on: server)) } + + @objc(setAvatar:profileKey:on:) + public static func objc_setAvatar(with url: String?, profileKey: Data, on server: String) -> AnyPromise { + return AnyPromise.from(setAvatar(with: url, profileKey: profileKey, on: server)) + } } From 1a9ad0062bc0c2da8bf052c2c6e7e29d049d842b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 14:14:02 +1100 Subject: [PATCH 06/17] Public chat incoming message avatar parsing. --- .../Loki/API/Public Chat/LokiPublicChatAPI.swift | 8 ++++++-- .../API/Public Chat/LokiPublicChatMessage.swift | 13 +++++++++---- .../Loki/API/Public Chat/LokiPublicChatPoller.swift | 9 +++++---- SignalServiceKit/src/Messages/OWSMessageSender.m | 3 +-- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift index d09a95a28..c1bff0e2b 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift @@ -99,8 +99,12 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(message).") return nil } - let profilePictureURL = value["avatar"] as? String ?? nil + var avatar: LokiPublicChatMessage.Avatar? = nil let displayName = user["name"] as? String ?? NSLocalizedString("Anonymous", comment: "") + if let userAnnotations = user["annotations"] as? [JSON], let avatarAnnoration = userAnnotations.first(where: { $0["type"] as? String == avatarType }), + let avatarValue = avatarAnnoration["value"] as? JSON, let profileKeyString = avatarValue["profileKey"] as? String, let profileKey = Data(base64Encoded: profileKeyString), let url = avatarValue["url"] as? String { + avatar = LokiPublicChatMessage.Avatar(profileKey: profileKey, url: url) + } let lastMessageServerID = getLastMessageServerID(for: channel, on: server) if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: channel, on: server, to: serverID) } let quote: LokiPublicChatMessage.Quote? @@ -129,7 +133,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } return LokiPublicChatMessage.Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle) } - let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: profilePictureURL, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature) + let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature) guard result.hasValidSignature() else { print("[Loki] Ignoring public chat message with invalid signature.") return nil diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift index 4c1e616eb..dd8f59d1c 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift @@ -5,7 +5,7 @@ public final class LokiPublicChatMessage : NSObject { public let serverID: UInt64? public let hexEncodedPublicKey: String public let displayName: String - public let avatar: String? + public let avatar: Avatar? public let body: String /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. public let timestamp: UInt64 @@ -22,6 +22,11 @@ public final class LokiPublicChatMessage : NSObject { private let attachmentType = "net.app.core.oembed" // MARK: Types + public struct Avatar { + public let profileKey: Data + public let url: String + } + public struct Quote { public let quotedMessageTimestamp: UInt64 public let quoteeHexEncodedPublicKey: String @@ -67,7 +72,7 @@ public final class LokiPublicChatMessage : NSObject { } // MARK: Initialization - public init(serverID: UInt64?, hexEncodedPublicKey: String, displayName: String, avatar: String?, body: String, type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?) { + public init(serverID: UInt64?, hexEncodedPublicKey: String, displayName: String, avatar: Avatar?, body: String, type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?) { self.serverID = serverID self.hexEncodedPublicKey = hexEncodedPublicKey self.displayName = displayName @@ -81,7 +86,7 @@ public final class LokiPublicChatMessage : NSObject { super.init() } - @objc public convenience init(hexEncodedPublicKey: String, displayName: String, avatar: String?, body: String, type: String, timestamp: UInt64, quotedMessageTimestamp: UInt64, quoteeHexEncodedPublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64, signatureData: Data?, signatureVersion: UInt64) { + @objc public convenience init(hexEncodedPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64, quotedMessageTimestamp: UInt64, quoteeHexEncodedPublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64, signatureData: Data?, signatureVersion: UInt64) { let quote: Quote? if quotedMessageTimestamp != 0, let quoteeHexEncodedPublicKey = quoteeHexEncodedPublicKey, let quotedMessageBody = quotedMessageBody { let quotedMessageServerID = (quotedMessageServerID != 0) ? quotedMessageServerID : nil @@ -95,7 +100,7 @@ public final class LokiPublicChatMessage : NSObject { } else { signature = nil } - self.init(serverID: nil, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature) + self.init(serverID: nil, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: nil, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature) } // MARK: Crypto diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift index e16258301..17dd3cacd 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift @@ -122,11 +122,12 @@ public final class LokiPublicChatPoller : NSObject { dataMessage.setPreview([ try! signalLinkPreview.build() ]) } let profile = SSKProtoDataMessageLokiProfile.builder() - if let profilePicture = message.avatar { - profile.setProfilePicture(profilePicture) - profile.setDisplayName(message.displayName) - dataMessage.setProfile(try! profile.build()) + profile.setDisplayName(message.displayName) + if let avatar = message.avatar { + profile.setProfilePicture(avatar.url) + dataMessage.setProfileKey(avatar.profileKey) } + dataMessage.setProfile(try! profile.build()) dataMessage.setTimestamp(message.timestamp) dataMessage.setGroup(try! groupContext.build()) if let quote = message.quote { diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index e543736d3..f483239ef 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -1207,7 +1207,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName; if (displayName == nil) { displayName = @"Anonymous"; } - NSString *avatarUrl = SSKEnvironment.shared.profileManager.profilePictureURL; TSQuotedMessage *quote = message.quotedMessage; uint64_t quoteID = quote.timestamp; NSString *quoteeHexEncodedPublicKey = quote.authorId; @@ -1218,7 +1217,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; }]; } NSString *body = (message.body != nil && message.body.length > 0) ? message.body : [NSString stringWithFormat:@"%@", @(message.timestamp)]; // Workaround for the fact that the back-end doesn't accept messages without a body - LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName avatar:avatarUrl body:body type:LKPublicChatAPI.publicChatMessageType + LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:body type:LKPublicChatAPI.publicChatMessageType timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteeHexEncodedPublicKey:quoteeHexEncodedPublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0]; OWSLinkPreview *linkPreview = message.linkPreview; if (linkPreview != nil) { From b2a2cd8d2083912ae8ac274ab26d9d56960eec15 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 14:37:46 +1100 Subject: [PATCH 07/17] Update our profile key if our primary device sends a message on the public chat. --- SignalMessaging/profiles/OWSProfileManager.m | 10 ++++++++-- SignalMessaging/profiles/OWSUserProfile.h | 2 +- SignalMessaging/profiles/OWSUserProfile.m | 4 ++-- .../Loki/API/Public Chat/LokiPublicChatPoller.swift | 7 +++++++ SignalServiceKit/src/Messages/OWSMessageManager.m | 2 +- .../src/Protocols/ProfileManagerProtocol.h | 3 ++- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 13472e66e..5e0fa88e5 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -346,11 +346,13 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); [self.localUserProfile updateWithProfileName:displayName transaction:transaction]; } -- (void)updateUserPofileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction { +- (void)updateUserProfileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAES256Key *profileKey = [OWSAES256Key keyWithData:profileKeyData]; if (profileKey != nil) { [self.localUserProfile updateWithProfileKey:profileKey transaction:transaction completion:^{ - [self.localUserProfile updateWithAvatarUrlPath:avatarURL transaction:transaction]; + [self.localUserProfile updateWithAvatarUrlPath:avatarURL transaction:transaction completion:^{ + [self downloadAvatarForUserProfile:self.localUserProfile]; + }]; }]; } } @@ -469,6 +471,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); successBlock(); } +- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarUrl:(nullable NSString *)avatarURL { + [self updateServiceWithProfileName:localProfileName avatarUrl:avatarURL success:^{} failure:^(NSError * _Nonnull error) {}]; +} + - (void)fetchLocalUsersProfile { OWSAssertIsOnMainThread(); diff --git a/SignalMessaging/profiles/OWSUserProfile.h b/SignalMessaging/profiles/OWSUserProfile.h index 80ef6d71e..8cd728dc7 100644 --- a/SignalMessaging/profiles/OWSUserProfile.h +++ b/SignalMessaging/profiles/OWSUserProfile.h @@ -82,7 +82,7 @@ extern NSString *const kLocalProfileUniqueId; completion:(nullable OWSUserProfileCompletion)completion; - (void)updateWithProfileName:(nullable NSString *)profileName transaction:(YapDatabaseReadWriteTransaction*)transaction; -- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction; +- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction completion:(nullable OWSUserProfileCompletion)completion; #pragma mark - Profile Avatars Directory diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index cb73a9765..79ae3d9dc 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -321,13 +321,13 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId"; [self updateWithProfileName:profileName avatarUrlPath:self.avatarUrlPath avatarFileName:self.avatarFileName transaction:transaction completion:nil]; } -- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction { +- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction completion:(nullable OWSUserProfileCompletion)completion { [self applyChanges:^(OWSUserProfile *userProfile) { [userProfile setAvatarUrlPath:avatarUrlPath]; } functionName:__PRETTY_FUNCTION__ transaction:transaction - completion:nil]; + completion:completion]; } - (void)updateWithAvatarUrlPath:(nullable NSString *)avatarUrlPath diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift index 17dd3cacd..a883e15c7 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift @@ -163,6 +163,13 @@ public final class LokiPublicChatPoller : NSObject { transaction.setObject(senderDisplayName, forKey: senderHexEncodedPublicKey, inCollection: publicChat.id) let messageServerID = message.serverID SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: messageServerID ?? 0) + + // If we got a message from our primary device then we should use its avatar + if let avatar = message.avatar, masterHexEncodedPublicKey == message.hexEncodedPublicKey { + SSKEnvironment.shared.profileManager.updateUserProfile(withDisplayName: message.displayName, transaction: transaction) + SSKEnvironment.shared.profileManager.updateUserProfileKeyData(avatar.profileKey, avatarURL: avatar.url, transaction: transaction) + SSKEnvironment.shared.profileManager.updateService(withProfileName: message.displayName, avatarUrl: avatar.url) + } } } } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 4fe4b6241..1862676f9 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -930,7 +930,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *profilePictureURL = profile.profilePicture; [self.profileManager updateUserProfileWithDisplayName:displayName transaction:transaction]; if ([dataMessage hasProfileKey]) { - [self.profileManager updateUserPofileKeyData:dataMessage.profileKey avatarURL:profilePictureURL transaction:transaction]; + [self.profileManager updateUserProfileKeyData:dataMessage.profileKey avatarURL:profilePictureURL transaction:transaction]; } }); } diff --git a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h index 3afb804ea..5f2cebc44 100644 --- a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h @@ -33,8 +33,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)fetchProfileForRecipientId:(NSString *)recipientId; - (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName transaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)updateUserPofileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction; +- (void)updateUserProfileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction; +- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarUrl:(nullable NSString *)avatarURL; @end From 3c3d185bec4a8efe2891b3697abf16054f24157d Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 Nov 2019 15:23:18 +1100 Subject: [PATCH 08/17] Don't send profile in a contact sync message. This is because both android and desktop first check to see if a sent sync message exists before the check for contact sync. This causes contact sync messages to be ignored. --- SignalMessaging/profiles/OWSUserProfile.m | 7 +- .../src/Loki/Messaging/LKDeviceLinkMessage.m | 15 ++ .../DeviceSyncing/OWSSyncContactsMessage.m | 15 -- .../src/Messages/OWSMessageManager.m | 226 +++++++++--------- 4 files changed, 135 insertions(+), 128 deletions(-) diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index 79ae3d9dc..ca97889fd 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -318,7 +318,12 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId"; - (void)updateWithProfileName:(nullable NSString *)profileName transaction:(YapDatabaseReadWriteTransaction*)transaction { - [self updateWithProfileName:profileName avatarUrlPath:self.avatarUrlPath avatarFileName:self.avatarFileName transaction:transaction completion:nil]; + [self applyChanges:^(OWSUserProfile *userProfile) { + [userProfile setProfileName:profileName]; + } + functionName:__PRETTY_FUNCTION__ + transaction:transaction + completion:nil]; } - (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction completion:(nullable OWSUserProfileCompletion)completion { diff --git a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m index eb6380c61..968aff11b 100644 --- a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m +++ b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m @@ -1,6 +1,9 @@ #import "LKDeviceLinkMessage.h" #import "OWSIdentityManager.h" #import "OWSPrimaryStorage+Loki.h" +#import "ProfileManagerProtocol.h" +#import "ProtoUtils.h" +#import "SSKEnvironment.h" #import "SignalRecipient.h" #import #import @@ -45,6 +48,18 @@ } else { [contentBuilder setPrekeyBundleMessage:preKeyBundleMessage]; } + } else { + // Loki: Set display name & profile picture + id profileManager = SSKEnvironment.shared.profileManager; + NSString *displayName = profileManager.localProfileName; + NSString *profilePictureURL = profileManager.profilePictureURL; + SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder]; + [profileBuilder setDisplayName:displayName]; + [profileBuilder setProfilePicture:profilePictureURL ?: @""]; + SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; + [messageBuilder setProfile:[profileBuilder buildAndReturnError:nil]]; + [ProtoUtils addLocalProfileKeyToDataMessageBuilder:messageBuilder]; + [contentBuilder setDataMessage:messageBuilder]; } // Build the device link message SSKProtoLokiDeviceLinkMessageBuilder *deviceLinkMessageBuilder = [SSKProtoLokiDeviceLinkMessage builder]; diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m index acecb880a..19bd4263c 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m @@ -103,21 +103,6 @@ NS_ASSUME_NONNULL_BEGIN SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; [syncMessageBuilder setContacts:contactsProto]; - // Loki: Set display name & profile picture - id profileManager = SSKEnvironment.shared.profileManager; - NSString *displayName = profileManager.localProfileName; - NSString *profilePictureURL = profileManager.profilePictureURL; - SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder]; - [profileBuilder setDisplayName:displayName]; - [profileBuilder setProfilePicture:profilePictureURL ?: @""]; - SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; - [messageBuilder setProfile:[profileBuilder buildAndReturnError:nil]]; - [ProtoUtils addLocalProfileKeyToDataMessageBuilder:messageBuilder]; - - SSKProtoSyncMessageSentBuilder *transcriptBuilder = [SSKProtoSyncMessageSent builder]; - [transcriptBuilder setMessage:[messageBuilder buildAndReturnError:nil]]; - [syncMessageBuilder setSent:[transcriptBuilder buildAndReturnError:nil]]; - return syncMessageBuilder; } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 1862676f9..6f1ed547d 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -438,22 +438,7 @@ NS_ASSUME_NONNULL_BEGIN BOOL isDuplicate = [LKAPI isDuplicateSyncMessage:contentProto.syncMessage.sent from:envelope.source]; if (isDuplicate) { return; } } - - // Loki: Handle device linking message if needed - if (contentProto.lokiDeviceLinkMessage != nil) { - NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.masterHexEncodedPublicKey; - NSString *slaveHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.slaveHexEncodedPublicKey; - NSData *masterSignature = contentProto.lokiDeviceLinkMessage.masterSignature; - NSData *slaveSignature = contentProto.lokiDeviceLinkMessage.slaveSignature; - if (masterSignature != nil) { // Authorization - OWSLogInfo(@"[Loki] Received a device linking authorization from: %@", envelope.source); // Not masterHexEncodedPublicKey - [LKDeviceLinkingSession.current processLinkingAuthorizationFrom:masterHexEncodedPublicKey for:slaveHexEncodedPublicKey masterSignature:masterSignature slaveSignature:slaveSignature]; - } else if (slaveSignature != nil) { // Request - OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); // Not slaveHexEncodedPublicKey - [LKDeviceLinkingSession.current processLinkingRequestFrom:slaveHexEncodedPublicKey to:masterHexEncodedPublicKey with:slaveSignature]; - } - } - + // Loki: Handle pre key bundle message if needed if (contentProto.prekeyBundleMessage != nil) { OWSLogInfo(@"[Loki] Received a pre key bundle message from: %@.", envelope.source); @@ -471,7 +456,25 @@ NS_ASSUME_NONNULL_BEGIN [LKP2PAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage]; } - if (contentProto.syncMessage) { + // Loki: Handle device linking message if needed + if (contentProto.lokiDeviceLinkMessage != nil) { + NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.masterHexEncodedPublicKey; + NSString *slaveHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.slaveHexEncodedPublicKey; + NSData *masterSignature = contentProto.lokiDeviceLinkMessage.masterSignature; + NSData *slaveSignature = contentProto.lokiDeviceLinkMessage.slaveSignature; + if (masterSignature != nil) { // Authorization + OWSLogInfo(@"[Loki] Received a device linking authorization from: %@", envelope.source); // Not masterHexEncodedPublicKey + [LKDeviceLinkingSession.current processLinkingAuthorizationFrom:masterHexEncodedPublicKey for:slaveHexEncodedPublicKey masterSignature:masterSignature slaveSignature:slaveSignature]; + + // Set any profile information + if (contentProto.dataMessage) { + [self handleLokiProfileForUserIfNeeded:contentProto.dataMessage transaction:transaction]; + } + } else if (slaveSignature != nil) { // Request + OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); // Not slaveHexEncodedPublicKey + [LKDeviceLinkingSession.current processLinkingRequestFrom:slaveHexEncodedPublicKey to:masterHexEncodedPublicKey with:slaveSignature]; + } + } else if (contentProto.syncMessage) { [self throws_handleIncomingEnvelope:envelope withSyncMessage:contentProto.syncMessage transaction:transaction @@ -915,109 +918,66 @@ NS_ASSUME_NONNULL_BEGIN OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorSyncMessageFromUnknownSource], envelope); return; } - + + NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction]; + BOOL wasSentByMasterDevice = [masterHexEncodedPublicKey isEqual:envelope.source]; if (syncMessage.sent) { - NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction]; - BOOL wasSentByMasterDevice = [masterHexEncodedPublicKey isEqual:envelope.source]; - + // Loki: Try to update using the provided profile if (wasSentByMasterDevice) { - dispatch_async(dispatch_get_main_queue(), ^{ - SSKProtoDataMessage *dataMessage = syncMessage.sent.message; - SSKProtoDataMessageLokiProfile *profile = dataMessage.profile; - NSString *displayName = profile.displayName; - NSString *profilePictureURL = profile.profilePicture; - [self.profileManager updateUserProfileWithDisplayName:displayName transaction:transaction]; - if ([dataMessage hasProfileKey]) { - [self.profileManager updateUserProfileKeyData:dataMessage.profileKey avatarURL:profilePictureURL transaction:transaction]; - } - }); + [self handleLokiProfileForUserIfNeeded:syncMessage.sent.message transaction:transaction]; } - - // Loki: Handle contact sync if needed - if (syncMessage.contacts != nil) { - if (wasSentByMasterDevice) { - NSLog(@"[Loki] Received contact sync message."); - NSData *data = syncMessage.contacts.data; - ContactParser *parser = [[ContactParser alloc] initWithData:data]; - NSArray *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys]; - // Try to establish sessions - for (NSString *hexEncodedPublicKey in hexEncodedPublicKeys) { - TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; - LKThreadFriendRequestStatus friendRequestStatus = thread.friendRequestStatus; - switch (friendRequestStatus) { - case LKThreadFriendRequestStatusNone: { - OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender; - OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey]; - dispatch_async(OWSDispatch.sendingQueue, ^{ - [messageSender sendMessage:automatedFriendRequestMessage]; - }); - break; - } - case LKThreadFriendRequestStatusRequestReceived: { - [thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction]; - // The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread] - LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread]; - [self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction]; - break; - } - default: break; // Do nothing + + OWSIncomingSentMessageTranscript *transcript = + [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction]; + + SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message; + if (!dataMessage) { + OWSFailDebug(@"Missing dataMessage."); + return; + } + NSString *destination = syncMessage.sent.destination; + if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) { + // If we observe a linked device sending our profile key to another + // user, we can infer that that user belongs in our profile whitelist. + if (dataMessage.group) { + [self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id]; + } else { + [self.profileManager addUserToProfileWhitelist:destination]; + } + } + + if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) { + [OWSRecordTranscriptJob + processIncomingSentMessageTranscript:transcript + serverID:0 + attachmentHandler:^(NSArray *attachmentStreams) { + OWSAssertDebug(attachmentStreams.count == 1); + TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; + [self.dbConnection readWriteWithBlock:^( + YapDatabaseReadWriteTransaction *transaction) { + TSGroupThread *_Nullable groupThread = + [TSGroupThread threadWithGroupId:dataMessage.group.id + transaction:transaction]; + if (!groupThread) { + OWSFailDebug(@"ignoring sync group avatar update for unknown group."); + return; } - } + + [groupThread updateAvatarWithAttachmentStream:attachmentStream + transaction:transaction]; + }]; } + transaction:transaction]; } else { - OWSIncomingSentMessageTranscript *transcript = - [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction]; - - SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message; - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - NSString *destination = syncMessage.sent.destination; - if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) { - // If we observe a linked device sending our profile key to another - // user, we can infer that that user belongs in our profile whitelist. - if (dataMessage.group) { - [self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id]; - } else { - [self.profileManager addUserToProfileWhitelist:destination]; - } - } - - if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:0 - attachmentHandler:^(NSArray *attachmentStreams) { - OWSAssertDebug(attachmentStreams.count == 1); - TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; - [self.dbConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id - transaction:transaction]; - if (!groupThread) { - OWSFailDebug(@"ignoring sync group avatar update for unknown group."); - return; - } - - [groupThread updateAvatarWithAttachmentStream:attachmentStream - transaction:transaction]; - }]; - } - transaction:transaction]; - } else { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:serverID - attachmentHandler:^(NSArray *attachmentStreams) { - OWSLogDebug(@"successfully fetched transcript attachments: %lu", - (unsigned long)attachmentStreams.count); - } - transaction:transaction]; + [OWSRecordTranscriptJob + processIncomingSentMessageTranscript:transcript + serverID:serverID + attachmentHandler:^(NSArray *attachmentStreams) { + OWSLogDebug(@"successfully fetched transcript attachments: %lu", + (unsigned long)attachmentStreams.count); } + transaction:transaction]; } } else if (syncMessage.request) { if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) { @@ -1066,11 +1026,53 @@ NS_ASSUME_NONNULL_BEGIN } else if (syncMessage.verified) { OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destination); [self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction]; + } else if (syncMessage.contacts != nil) { + if (false && wasSentByMasterDevice && syncMessage.contacts.data.length > 0) { + NSLog(@"[Loki] Received contact sync message."); + NSData *data = syncMessage.contacts.data; + ContactParser *parser = [[ContactParser alloc] initWithData:data]; + NSArray *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys]; + // Try to establish sessions + for (NSString *hexEncodedPublicKey in hexEncodedPublicKeys) { + TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; + LKThreadFriendRequestStatus friendRequestStatus = thread.friendRequestStatus; + switch (friendRequestStatus) { + case LKThreadFriendRequestStatusNone: { + OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender; + OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey]; + dispatch_async(OWSDispatch.sendingQueue, ^{ + [messageSender sendMessage:automatedFriendRequestMessage]; + }); + break; + } + case LKThreadFriendRequestStatusRequestReceived: { + [thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction]; + // The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread] + LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread]; + [self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction]; + break; + } + default: break; // Do nothing + } + } + } } else { OWSLogWarn(@"Ignoring unsupported sync message."); } } +- (void)handleLokiProfileForUserIfNeeded:(SSKProtoDataMessage *)dataMessage transaction:(YapDatabaseReadWriteTransaction *)transaction { + if (dataMessage.profile != nil) { + SSKProtoDataMessageLokiProfile *profile = dataMessage.profile; + NSString *displayName = profile.displayName; + NSString *profilePictureURL = profile.profilePicture; + [self.profileManager updateUserProfileWithDisplayName:displayName transaction:transaction]; + if ([dataMessage hasProfileKey]) { + [self.profileManager updateUserProfileKeyData:dataMessage.profileKey avatarURL:profilePictureURL transaction:transaction]; + } + } +} + - (void)handleEndSessionMessageWithEnvelope:(SSKProtoEnvelope *)envelope dataMessage:(SSKProtoDataMessage *)dataMessage transaction:(YapDatabaseReadWriteTransaction *)transaction From b0dfae7974717f618cf4ff20b15289b5ef3caa2a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 2 Dec 2019 09:49:16 +1100 Subject: [PATCH 09/17] Fix multi-device profile picture handling. Fixed note to self. Enabled removal of avatar. --- .../AppSettings/AppSettingsViewController.m | 8 +-- Signal/src/ViewControllers/AvatarViewHelper.m | 3 +- .../HomeView/HomeViewController.m | 4 +- SignalMessaging/profiles/OWSProfileManager.m | 61 ++++++++----------- SignalMessaging/profiles/OWSUserProfile.m | 5 +- .../utils/OWSContactAvatarBuilder.m | 3 +- SignalServiceKit/src/Contacts/TSThread.m | 6 +- .../Public Chat/LokiPublicChatPoller.swift | 6 +- .../src/Messages/OWSMessageManager.m | 27 ++++---- .../src/Protocols/ProfileManagerProtocol.h | 2 - .../src/TestUtils/OWSFakeProfileManager.m | 5 -- 11 files changed, 57 insertions(+), 73 deletions(-) diff --git a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m index 11f0a2834..cd19fd76b 100644 --- a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m @@ -314,6 +314,7 @@ - (UITableViewCell *)profileHeaderCell { NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; + NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey ?: TSAccountManager.localNumber; BOOL isMasterDevice = (masterDeviceHexEncodedPublicKey == nil); UITableViewCell *cell = [OWSTableItem newCell]; @@ -321,7 +322,7 @@ cell.contentView.preservesSuperviewLayoutMargins = YES; cell.selectionStyle = UITableViewCellSelectionStyleNone; - UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager localProfileAvatarImage]; + UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager profileAvatarForRecipientId:hexEncodedPublicKey]; UIImage *avatarImage = (localProfileAvatarImage ?: [[[OWSContactAvatarBuilder alloc] initForLocalUserWithDiameter:kLargeAvatarSize] buildDefaultImage]); OWSAssertDebug(avatarImage); @@ -334,7 +335,7 @@ [avatarView autoSetDimension:ALDimensionHeight toSize:kLargeAvatarSize]; avatarView.contactID = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - if (isMasterDevice && !localProfileAvatarImage) { + if (isMasterDevice && !OWSProfileManager.sharedManager.localProfileAvatarImage) { UIImage *cameraImage = [UIImage imageNamed:@"settings-avatar-camera"]; UIImageView *cameraImageView = [[UIImageView alloc] initWithImage:cameraImage]; [cell.contentView addSubview:cameraImageView]; @@ -348,7 +349,7 @@ [nameView autoPinLeadingToTrailingEdgeOfView:avatarView offset:16.f]; UILabel *titleLabel = [UILabel new]; - NSString *_Nullable localProfileName = [OWSProfileManager.sharedManager localProfileName]; + NSString *_Nullable localProfileName = [OWSProfileManager.sharedManager profileNameForRecipientId:hexEncodedPublicKey]; if (localProfileName.length > 0) { titleLabel.text = localProfileName; titleLabel.textColor = [Theme primaryColor]; @@ -370,7 +371,6 @@ UILabel *subtitleLabel = [UILabel new]; subtitleLabel.textColor = [Theme secondaryColor]; subtitleLabel.font = [UIFont ows_regularFontWithSize:kSubtitlePointSize]; - NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey ?: TSAccountManager.localNumber; subtitleLabel.attributedText = [[NSAttributedString alloc] initWithString:hexEncodedPublicKey]; subtitleLabel.lineBreakMode = NSLineBreakByTruncatingTail; [nameView addSubview:subtitleLabel]; diff --git a/Signal/src/ViewControllers/AvatarViewHelper.m b/Signal/src/ViewControllers/AvatarViewHelper.m index 33ca13b42..649fbbeec 100644 --- a/Signal/src/ViewControllers/AvatarViewHelper.m +++ b/Signal/src/ViewControllers/AvatarViewHelper.m @@ -58,8 +58,7 @@ NS_ASSUME_NONNULL_BEGIN [self.delegate clearAvatar]; }]; - // TODO: enable this once we support removing avatars (as opposed to replacing) - // [actionSheet addAction:clearAction]; + [actionSheet addAction:clearAction]; } [self.delegate.fromViewController presentAlert:actionSheet]; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index c7bd82edc..1563e6c5a 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -759,7 +759,9 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { UIBarButtonItem *settingsButton; if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0)) { const NSUInteger kAvatarSize = 28; - UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager localProfileAvatarImage]; + NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; + NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey ?: TSAccountManager.localNumber; + UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager profileAvatarForRecipientId:hexEncodedPublicKey]; UIImage *avatarImage = (localProfileAvatarImage ?: [[[OWSContactAvatarBuilder alloc] initForLocalUserWithDiameter:kAvatarSize] buildDefaultImage]); OWSAssertDebug(avatarImage); diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 5e0fa88e5..1270663b4 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -341,22 +341,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } } -- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self.localUserProfile updateWithProfileName:displayName transaction:transaction]; -} - -- (void)updateUserProfileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction { - OWSAES256Key *profileKey = [OWSAES256Key keyWithData:profileKeyData]; - if (profileKey != nil) { - [self.localUserProfile updateWithProfileKey:profileKey transaction:transaction completion:^{ - [self.localUserProfile updateWithAvatarUrlPath:avatarURL transaction:transaction completion:^{ - [self downloadAvatarForUserProfile:self.localUserProfile]; - }]; - }]; - } -} - - (nullable NSString *)profilePictureURL { return self.localUserProfile.avatarUrlPath; @@ -422,30 +406,35 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); // We always want to encrypt a profile with a new profile key // This ensures that other users know that our profile picture was updated OWSAES256Key *newProfileKey = [OWSAES256Key generateRandomKey]; - NSData *_Nullable encryptedAvatarData; + if (avatarData) { - encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey]; + NSData *encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey]; OWSAssertDebug(encryptedAvatarData.length > 0); - } - - [[LKStorageAPI setProfilePicture:encryptedAvatarData] - .thenOn(dispatch_get_main_queue(), ^(NSString *url) { - [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ - successBlock(url); - }]; - }) - .catchOn(dispatch_get_main_queue(), ^(id result) { - // There appears to be a bug in PromiseKit that sometimes causes catchOn - // to be invoked with the fulfilled promise's value as the error. The below - // is a quick and dirty workaround. - if ([result isKindOfClass:NSString.class]) { + + [[LKStorageAPI setProfilePicture:encryptedAvatarData] + .thenOn(dispatch_get_main_queue(), ^(NSString *url) { [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ - successBlock(result); + successBlock(url); }]; - } else { - failureBlock(result); - } - }) retainUntilComplete]; + }) + .catchOn(dispatch_get_main_queue(), ^(id result) { + // There appears to be a bug in PromiseKit that sometimes causes catchOn + // to be invoked with the fulfilled promise's value as the error. The below + // is a quick and dirty workaround. + if ([result isKindOfClass:NSString.class]) { + [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ + successBlock(result); + }]; + } else { + failureBlock(result); + } + }) retainUntilComplete]; + } else { + // Update our profile key and set the url to nil if avatar data is nil + [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ + successBlock(nil); + }]; + } }); } diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index ca97889fd..a33538518 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -234,8 +234,9 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId"; if (!didChange) { return; } - - BOOL isLocalUserProfile = [self.recipientId isEqualToString:kLocalProfileUniqueId]; + + NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; + BOOL isLocalUserProfile = [self.recipientId isEqualToString:kLocalProfileUniqueId] || (masterDeviceHexEncodedPublicKey != nil && [self.recipientId isEqualToString:masterDeviceHexEncodedPublicKey]); dispatch_async(dispatch_get_main_queue(), ^{ if (isLocalUserProfile) { diff --git a/SignalMessaging/utils/OWSContactAvatarBuilder.m b/SignalMessaging/utils/OWSContactAvatarBuilder.m index 54e5bec7a..eb6e9b43a 100644 --- a/SignalMessaging/utils/OWSContactAvatarBuilder.m +++ b/SignalMessaging/utils/OWSContactAvatarBuilder.m @@ -93,7 +93,8 @@ NS_ASSUME_NONNULL_BEGIN - (nullable UIImage *)buildSavedImage { - if ([self.signalId isEqualToString:TSAccountManager.localNumber]) { + NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; + if ([self.signalId isEqualToString:TSAccountManager.localNumber] || (masterDeviceHexEncodedPublicKey != nil && [self.signalId isEqualToString:masterDeviceHexEncodedPublicKey])) { NSString *noteToSelfCacheKey = [NSString stringWithFormat:@"%@:note-to-self", self.cacheKey]; UIImage *_Nullable cachedAvatar = [OWSContactAvatarBuilder.contactsManager.avatarCache imageForKey:noteToSelfCacheKey diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index 05703ab35..47609049a 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -211,8 +211,10 @@ ConversationColorName const kConversationColorName_Default = ConversationColorNa if (!IsNoteToSelfEnabled()) { return NO; } - return (!self.isGroupThread && self.contactIdentifier != nil && - [self.contactIdentifier isEqualToString:self.tsAccountManager.localNumber]); + NSString *localNumber = self.tsAccountManager.localNumber; + NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; + bool isOurNumber = [self.contactIdentifier isEqualToString:localNumber] || (masterDeviceHexEncodedPublicKey != nil && [self.contactIdentifier isEqualToString:masterDeviceHexEncodedPublicKey]); + return (!self.isGroupThread && self.contactIdentifier != nil && isOurNumber); } #pragma mark - To be subclassed. diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift index a883e15c7..a0fca72e4 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift @@ -166,9 +166,11 @@ public final class LokiPublicChatPoller : NSObject { // If we got a message from our primary device then we should use its avatar if let avatar = message.avatar, masterHexEncodedPublicKey == message.hexEncodedPublicKey { - SSKEnvironment.shared.profileManager.updateUserProfile(withDisplayName: message.displayName, transaction: transaction) - SSKEnvironment.shared.profileManager.updateUserProfileKeyData(avatar.profileKey, avatarURL: avatar.url, transaction: transaction) + if (message.displayName.count > 0) { + SSKEnvironment.shared.profileManager.updateProfileForContact(withID: masterHexEncodedPublicKey!, displayName: message.displayName, with: transaction) + } SSKEnvironment.shared.profileManager.updateService(withProfileName: message.displayName, avatarUrl: avatar.url) + SSKEnvironment.shared.profileManager.setProfileKeyData(avatar.profileKey, forRecipientId: masterHexEncodedPublicKey!, avatarURL: avatar.url) } } } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 6f1ed547d..ba415af21 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -468,7 +468,8 @@ NS_ASSUME_NONNULL_BEGIN // Set any profile information if (contentProto.dataMessage) { - [self handleLokiProfileForUserIfNeeded:contentProto.dataMessage transaction:transaction]; + [self handleProfileNameUpdateIfNeeded:contentProto.dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction]; + [self handleProfileKeyUpdateIfNeeded:contentProto.dataMessage recipientId:masterHexEncodedPublicKey]; } } else if (slaveSignature != nil) { // Request OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); // Not slaveHexEncodedPublicKey @@ -925,7 +926,8 @@ NS_ASSUME_NONNULL_BEGIN // Loki: Try to update using the provided profile if (wasSentByMasterDevice) { - [self handleLokiProfileForUserIfNeeded:syncMessage.sent.message transaction:transaction]; + [self handleProfileNameUpdateIfNeeded:syncMessage.sent.message recipientId:masterHexEncodedPublicKey transaction:transaction]; + [self handleProfileKeyUpdateIfNeeded:syncMessage.sent.message recipientId:masterHexEncodedPublicKey]; } OWSIncomingSentMessageTranscript *transcript = @@ -1061,18 +1063,6 @@ NS_ASSUME_NONNULL_BEGIN } } -- (void)handleLokiProfileForUserIfNeeded:(SSKProtoDataMessage *)dataMessage transaction:(YapDatabaseReadWriteTransaction *)transaction { - if (dataMessage.profile != nil) { - SSKProtoDataMessageLokiProfile *profile = dataMessage.profile; - NSString *displayName = profile.displayName; - NSString *profilePictureURL = profile.profilePicture; - [self.profileManager updateUserProfileWithDisplayName:displayName transaction:transaction]; - if ([dataMessage hasProfileKey]) { - [self.profileManager updateUserProfileKeyData:dataMessage.profileKey avatarURL:profilePictureURL transaction:transaction]; - } - } -} - - (void)handleEndSessionMessageWithEnvelope:(SSKProtoEnvelope *)envelope dataMessage:(SSKProtoDataMessage *)dataMessage transaction:(YapDatabaseReadWriteTransaction *)transaction @@ -1392,8 +1382,7 @@ NS_ASSUME_NONNULL_BEGIN [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; // Only set the display name here, the logic for updating profile pictures is handled when we're setting profile key - NSString *displayName = dataMessage.profile.displayName; - [self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName with:transaction]; + [self handleProfileNameUpdateIfNeeded:dataMessage recipientId:thread.contactIdentifier transaction:transaction]; switch (dataMessage.group.type) { case SSKProtoGroupContextTypeUpdate: { @@ -1633,6 +1622,12 @@ NS_ASSUME_NONNULL_BEGIN } } +- (void)handleProfileNameUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction { + if (dataMessage.profile != nil) { + [self.profileManager updateProfileForContactWithID:recipientId displayName:dataMessage.profile.displayName with:transaction]; + } +} + - (void)handleProfileKeyUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId { if ([dataMessage hasProfileKey]) { NSData *profileKey = [dataMessage profileKey]; diff --git a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h index 5f2cebc44..f2d13a742 100644 --- a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h @@ -32,8 +32,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)fetchProfileForRecipientId:(NSString *)recipientId; -- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName transaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)updateUserProfileKeyData:(NSData *)profileKeyData avatarURL:(nullable NSString *)avatarURL transaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction; - (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarUrl:(nullable NSString *)avatarURL; diff --git a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m index de7cf2044..ac8bd1446 100644 --- a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m +++ b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m @@ -48,11 +48,6 @@ NS_ASSUME_NONNULL_BEGIN return _localProfileKey; } -- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - // Do nothing -} - - (void)setProfileKeyData:(NSData *)profileKey forRecipientId:(NSString *)recipientId { OWSAES256Key *_Nullable key = [OWSAES256Key keyWithData:profileKey]; From 2b8e74348acafb3982eb2eee9d817ad2fb246791 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 2 Dec 2019 11:01:07 +1100 Subject: [PATCH 10/17] Clean --- SignalMessaging/profiles/OWSUserProfile.h | 3 --- SignalMessaging/profiles/OWSUserProfile.m | 19 --------------- .../src/Messages/OWSMessageManager.m | 24 ++++++++++--------- 3 files changed, 13 insertions(+), 33 deletions(-) diff --git a/SignalMessaging/profiles/OWSUserProfile.h b/SignalMessaging/profiles/OWSUserProfile.h index 8cd728dc7..980602666 100644 --- a/SignalMessaging/profiles/OWSUserProfile.h +++ b/SignalMessaging/profiles/OWSUserProfile.h @@ -81,9 +81,6 @@ extern NSString *const kLocalProfileUniqueId; dbConnection:(YapDatabaseConnection *)dbConnection completion:(nullable OWSUserProfileCompletion)completion; -- (void)updateWithProfileName:(nullable NSString *)profileName transaction:(YapDatabaseReadWriteTransaction*)transaction; -- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction completion:(nullable OWSUserProfileCompletion)completion; - #pragma mark - Profile Avatars Directory + (NSString *)profileAvatarFilepathWithFilename:(NSString *)filename; diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index a33538518..26e20ee55 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -317,25 +317,6 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId"; completion:completion]; } -- (void)updateWithProfileName:(nullable NSString *)profileName transaction:(YapDatabaseReadWriteTransaction*)transaction -{ - [self applyChanges:^(OWSUserProfile *userProfile) { - [userProfile setProfileName:profileName]; - } - functionName:__PRETTY_FUNCTION__ - transaction:transaction - completion:nil]; -} - -- (void)updateWithAvatarUrlPath:(NSString *)avatarUrlPath transaction:(YapDatabaseReadWriteTransaction*)transaction completion:(nullable OWSUserProfileCompletion)completion { - [self applyChanges:^(OWSUserProfile *userProfile) { - [userProfile setAvatarUrlPath:avatarUrlPath]; - } - functionName:__PRETTY_FUNCTION__ - transaction:transaction - completion:completion]; -} - - (void)updateWithAvatarUrlPath:(nullable NSString *)avatarUrlPath avatarFileName:(nullable NSString *)avatarFileName dbConnection:(YapDatabaseConnection *)dbConnection diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index ba415af21..bf96f5127 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -468,8 +468,9 @@ NS_ASSUME_NONNULL_BEGIN // Set any profile information if (contentProto.dataMessage) { - [self handleProfileNameUpdateIfNeeded:contentProto.dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction]; - [self handleProfileKeyUpdateIfNeeded:contentProto.dataMessage recipientId:masterHexEncodedPublicKey]; + SSKProtoDataMessage *dataMessage = contentProto.dataMessage; + [self handleProfileNameUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction]; + [self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey]; } } else if (slaveSignature != nil) { // Request OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); // Not slaveHexEncodedPublicKey @@ -924,12 +925,6 @@ NS_ASSUME_NONNULL_BEGIN BOOL wasSentByMasterDevice = [masterHexEncodedPublicKey isEqual:envelope.source]; if (syncMessage.sent) { - // Loki: Try to update using the provided profile - if (wasSentByMasterDevice) { - [self handleProfileNameUpdateIfNeeded:syncMessage.sent.message recipientId:masterHexEncodedPublicKey transaction:transaction]; - [self handleProfileKeyUpdateIfNeeded:syncMessage.sent.message recipientId:masterHexEncodedPublicKey]; - } - OWSIncomingSentMessageTranscript *transcript = [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction]; @@ -938,6 +933,13 @@ NS_ASSUME_NONNULL_BEGIN OWSFailDebug(@"Missing dataMessage."); return; } + + // Loki: Try to update using the provided profile + if (wasSentByMasterDevice) { + [self handleProfileNameUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction]; + [self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey]; + } + NSString *destination = syncMessage.sent.destination; if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) { // If we observe a linked device sending our profile key to another @@ -1029,7 +1031,7 @@ NS_ASSUME_NONNULL_BEGIN OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destination); [self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction]; } else if (syncMessage.contacts != nil) { - if (false && wasSentByMasterDevice && syncMessage.contacts.data.length > 0) { + if (wasSentByMasterDevice && syncMessage.contacts.data.length > 0) { NSLog(@"[Loki] Received contact sync message."); NSData *data = syncMessage.contacts.data; ContactParser *parser = [[ContactParser alloc] initWithData:data]; @@ -1623,13 +1625,13 @@ NS_ASSUME_NONNULL_BEGIN } - (void)handleProfileNameUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction { - if (dataMessage.profile != nil) { + if (dataMessage != nil && dataMessage.profile != nil) { [self.profileManager updateProfileForContactWithID:recipientId displayName:dataMessage.profile.displayName with:transaction]; } } - (void)handleProfileKeyUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId { - if ([dataMessage hasProfileKey]) { + if (dataMessage != nil && [dataMessage hasProfileKey]) { NSData *profileKey = [dataMessage profileKey]; NSString *url = dataMessage.profile != nil ? dataMessage.profile.profilePicture : nil; if (profileKey.length == kAES256_KeyByteLength) { From ce33d472ebedb4688246349991ead0f4332aca15 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 2 Dec 2019 13:10:01 +1100 Subject: [PATCH 11/17] Clean --- SignalMessaging/profiles/OWSProfileManager.m | 2 +- .../API/Public Chat/LokiPublicChatAPI.swift | 22 +++++++++---------- .../Public Chat/LokiPublicChatPoller.swift | 3 +-- .../src/Loki/Messaging/LKDeviceLinkMessage.m | 4 ++-- .../src/Messages/OWSMessageManager.m | 3 +-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 1270663b4..adf81aa0f 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -454,7 +454,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); for (NSString *server in servers) { [[LKPublicChatAPI setDisplayName:localProfileName on:server] retainUntilComplete]; - [[LKPublicChatAPI setAvatar:avatarURL profileKey:self.localProfileKey.keyData on:server] retainUntilComplete]; + [[LKPublicChatAPI setProfilePictureURL:avatarURL usingProfileKey:self.localProfileKey.keyData on:server] retainUntilComplete]; } successBlock(); diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift index c1bff0e2b..0517cba38 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift @@ -101,8 +101,8 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } var avatar: LokiPublicChatMessage.Avatar? = nil let displayName = user["name"] as? String ?? NSLocalizedString("Anonymous", comment: "") - if let userAnnotations = user["annotations"] as? [JSON], let avatarAnnoration = userAnnotations.first(where: { $0["type"] as? String == avatarType }), - let avatarValue = avatarAnnoration["value"] as? JSON, let profileKeyString = avatarValue["profileKey"] as? String, let profileKey = Data(base64Encoded: profileKeyString), let url = avatarValue["url"] as? String { + if let userAnnotations = user["annotations"] as? [JSON], let avatarAnnotation = userAnnotations.first(where: { $0["type"] as? String == avatarType }), + let avatarValue = avatarAnnotation["value"] as? JSON, let profileKeyString = avatarValue["profileKey"] as? String, let profileKey = Data(base64Encoded: profileKeyString), let url = avatarValue["url"] as? String { avatar = LokiPublicChatMessage.Avatar(profileKey: profileKey, url: url) } let lastMessageServerID = getLastMessageServerID(for: channel, on: server) @@ -292,19 +292,19 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { }.retryingIfNeeded(maxRetryCount: 3) } - public static func setAvatar(with url: String?, profileKey: Data, on server: String) -> Promise { - print("[Loki] Updating avatar on server: \(server).") + public static func setProfilePictureURL(to url: String?, using profileKey: Data, on server: String) -> Promise { + print("[Loki] Updating profile picture on server: \(server).") return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in - var annotation: JSON = ["type" : avatarType] + var annotation: JSON = [ "type" : avatarType ] if let url = url { - annotation["value"] = ["profileKey": profileKey.base64EncodedString(), "url" : url] + annotation["value"] = [ "profileKey" : profileKey.base64EncodedString(), "url" : url ] } - let parameters: JSON = [ "annotations" : [annotation] ] + let parameters: JSON = [ "annotations" : [ annotation ] ] let url = URL(string: "\(server)/users/me")! let request = TSRequest(url: url, method: "PATCH", parameters: parameters) request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in }.recover(on: DispatchQueue.global()) { error in - print("Couldn't update avatar due to error: \(error).") + print("[Loki] Couldn't update profile picture due to error: \(error).") throw error } }.retryingIfNeeded(maxRetryCount: 3) @@ -353,8 +353,8 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return AnyPromise.from(setDisplayName(to: newDisplayName, on: server)) } - @objc(setAvatar:profileKey:on:) - public static func objc_setAvatar(with url: String?, profileKey: Data, on server: String) -> AnyPromise { - return AnyPromise.from(setAvatar(with: url, profileKey: profileKey, on: server)) + @objc(setProfilePictureURL:usingProfileKey:on:) + public static func objc_setProfilePicture(to url: String?, using profileKey: Data, on server: String) -> AnyPromise { + return AnyPromise.from(setProfilePictureURL(to: url, using: profileKey, on: server)) } } diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift index a0fca72e4..ea171ec65 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift @@ -163,8 +163,7 @@ public final class LokiPublicChatPoller : NSObject { transaction.setObject(senderDisplayName, forKey: senderHexEncodedPublicKey, inCollection: publicChat.id) let messageServerID = message.serverID SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: messageServerID ?? 0) - - // If we got a message from our primary device then we should use its avatar + // If we got a message from our master device then we should use its profile picture if let avatar = message.avatar, masterHexEncodedPublicKey == message.hexEncodedPublicKey { if (message.displayName.count > 0) { SSKEnvironment.shared.profileManager.updateProfileForContact(withID: masterHexEncodedPublicKey!, displayName: message.displayName, with: transaction) diff --git a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m index 968aff11b..ef4d7829d 100644 --- a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m +++ b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkMessage.m @@ -37,8 +37,8 @@ - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; NSError *error; - // Build the pre key bundle message if (self.kind == LKDeviceLinkMessageKindRequest) { + // Build the pre key bundle message PreKeyBundle *preKeyBundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId]; SSKProtoPrekeyBundleMessageBuilder *preKeyBundleMessageBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:preKeyBundle]; SSKProtoPrekeyBundleMessage *preKeyBundleMessage = [preKeyBundleMessageBuilder buildAndReturnError:&error]; @@ -49,7 +49,7 @@ [contentBuilder setPrekeyBundleMessage:preKeyBundleMessage]; } } else { - // Loki: Set display name & profile picture + // Set display name & profile picture id profileManager = SSKEnvironment.shared.profileManager; NSString *displayName = profileManager.localProfileName; NSString *profilePictureURL = profileManager.profilePictureURL; diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index bf96f5127..f2a706e83 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -465,8 +465,7 @@ NS_ASSUME_NONNULL_BEGIN if (masterSignature != nil) { // Authorization OWSLogInfo(@"[Loki] Received a device linking authorization from: %@", envelope.source); // Not masterHexEncodedPublicKey [LKDeviceLinkingSession.current processLinkingAuthorizationFrom:masterHexEncodedPublicKey for:slaveHexEncodedPublicKey masterSignature:masterSignature slaveSignature:slaveSignature]; - - // Set any profile information + // Set any profile info if (contentProto.dataMessage) { SSKProtoDataMessage *dataMessage = contentProto.dataMessage; [self handleProfileNameUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction]; From 7fe2cc7511ec82f4bce935ee442eb1f589d75352 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 2 Dec 2019 14:56:39 +1100 Subject: [PATCH 12/17] Fix crash --- SignalServiceKit/src/Loki/Messaging/ContactParser.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift index f863491b9..384e20bfc 100644 --- a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift +++ b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift @@ -14,6 +14,7 @@ guard let size = uncheckedSize else { break } let sizeAsInt = Int(size) index += 4 + guard index + sizeAsInt < data.count else { break } let protoAsData = data[index..<(index+sizeAsInt)] guard let proto = try? SSKProtoContactDetails.parseData(protoAsData) else { break } index += sizeAsInt From 9435c6dd1d3bb552e40f7b04f8a9f2fefb833181 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 2 Dec 2019 16:06:18 +1100 Subject: [PATCH 13/17] Fix contact parsing --- SignalServiceKit/src/Loki/Messaging/ContactParser.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift index 384e20bfc..7b3dea666 100644 --- a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift +++ b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift @@ -10,8 +10,11 @@ var index = 0 var result: [String] = [] while index < data.endIndex { - let uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee } - guard let size = uncheckedSize else { break } + var uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee } + if let size = uncheckedSize, size >= data.count, let intermediate = try? data[index..<(index+4)].reversed() { + uncheckedSize = Data(intermediate).withUnsafeBytes { $0.pointee } + } + guard let size = uncheckedSize, size < data.count else { break } let sizeAsInt = Int(size) index += 4 guard index + sizeAsInt < data.count else { break } From abc6e2020782d79aff36b31bb38a96b7e0073498 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 2 Dec 2019 16:17:42 +1100 Subject: [PATCH 14/17] Fix profile name being cleared when setting profile picture. --- SignalMessaging/profiles/OWSProfileManager.h | 2 -- SignalMessaging/profiles/OWSProfileManager.m | 2 +- SignalMessaging/profiles/OWSUserProfile.m | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.h b/SignalMessaging/profiles/OWSProfileManager.h index ee3b64f51..a0b5a379b 100644 --- a/SignalMessaging/profiles/OWSProfileManager.h +++ b/SignalMessaging/profiles/OWSProfileManager.h @@ -83,8 +83,6 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; profileNameEncrypted:(nullable NSData *)profileNameEncrypted avatarUrlPath:(nullable NSString *)avatarUrlPath; -- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction; - #pragma mark - User Interface - (void)presentAddThreadToProfileWhitelist:(TSThread *)thread diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index adf81aa0f..fb944741f 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -975,7 +975,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); dispatch_async(dispatch_get_main_queue(), ^{ [self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeUnknown recipientId:recipientId]; - [userProfile updateWithProfileName:userProfile.profileName avatarUrlPath:avatarURL dbConnection:self.dbConnection completion:^{ + [userProfile updateWithAvatarUrlPath:avatarURL avatarFileName:nil dbConnection:self.dbConnection completion:^{ [self downloadAvatarForUserProfile:userProfile]; }]; // [self fetchProfileForRecipientId:recipientId]; diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index 26e20ee55..6eb0a0e1a 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -351,7 +351,7 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId"; { [self applyChanges:^(OWSUserProfile *userProfile) { [userProfile setProfileKey:profileKey]; - [userProfile setProfileName:nil]; + // [userProfile setProfileName:nil]; - Loki disabled until we include profile name inside the encrypted profile from the url // Always setAvatarUrlPath: before you setAvatarFileName: since // setAvatarUrlPath: may clear the avatar filename. [userProfile setAvatarUrlPath:nil]; From 976f17a3ecfdeb7237ee91efe1b4825089ef7085 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Dec 2019 09:58:47 +1100 Subject: [PATCH 15/17] Fix linking with desktop --- SignalServiceKit/src/Devices/OWSChunkedOutputStream.m | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m b/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m index c10caeae0..6fad0c122 100644 --- a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m +++ b/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m @@ -65,7 +65,16 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)writeUInt32:(UInt32)value { NSData *data = [[NSData alloc] initWithBytes:&value length:sizeof(value)]; - return [self writeData:data]; + // Both Android and desktop seem to like this better + const char *bytes = data.bytes; + char *reversedBytes = malloc(sizeof(char) * data.length); + int i = data.length - 1; + for (int j = 0; j < data.length; i++) { + reversedBytes[i] = bytes[j]; + i = i - 1; + } + NSData *reversedData = [NSData dataWithBytes:reversedBytes length:data.length]; + return [self writeData:reversedData]; } - (BOOL)writeVariableLengthUInt32:(UInt32)value From 9b51cd9b6781ff89881a7ece74649faf7adc86bf Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Dec 2019 11:13:19 +1100 Subject: [PATCH 16/17] Fix typo --- SignalServiceKit/src/Devices/OWSChunkedOutputStream.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m b/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m index 6fad0c122..f50efc73a 100644 --- a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m +++ b/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m @@ -69,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN const char *bytes = data.bytes; char *reversedBytes = malloc(sizeof(char) * data.length); int i = data.length - 1; - for (int j = 0; j < data.length; i++) { + for (int j = 0; j < data.length; j++) { reversedBytes[i] = bytes[j]; i = i - 1; } From e1373b7fd93bd02f1397e51e8de6b75ca2d4bcc0 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Dec 2019 11:18:55 +1100 Subject: [PATCH 17/17] Update version number --- Signal.xcodeproj/project.pbxproj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 5b8ec972e..96fb745af 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -4098,7 +4098,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -4112,7 +4112,7 @@ INFOPLIST_FILE = SignalShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.5.1; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.share-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4160,7 +4160,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -4179,7 +4179,7 @@ INFOPLIST_FILE = SignalShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.5.1; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.share-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4233,7 +4233,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.5.1; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.utilities"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -4307,7 +4307,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.5.1; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.utilities"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -4492,7 +4492,7 @@ CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4527,7 +4527,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.5.1; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = "Loki Messenger"; @@ -4559,7 +4559,7 @@ CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4594,7 +4594,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.5.1; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = "Loki Messenger";