diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index e694be133..986b6a2f4 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -149,6 +149,8 @@ 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; }; 454EBAB31F2BC08800ACE0BB /* OWSSwiftUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C921F2937CC00D284D6 /* OWSSwiftUtils.swift */; }; 454EBAB41F2BE14C00ACE0BB /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; }; + 4556FA681F54AA9500AF40DD /* DebugUIProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4556FA671F54AA9500AF40DD /* DebugUIProfile.swift */; }; + 4556FA691F54AA9500AF40DD /* DebugUIProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4556FA671F54AA9500AF40DD /* DebugUIProfile.swift */; }; 455A16DD1F1FEA0000F86704 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455A16DB1F1FEA0000F86704 /* Metal.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455A16DC1F1FEA0000F86704 /* MetalKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 455AC69B1F4F79E500134004 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455AC69A1F4F79E500134004 /* ImageCache.swift */; }; @@ -608,6 +610,7 @@ 4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+retainUntilComplete.swift"; sourceTree = ""; }; 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = ""; }; 454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = ""; }; + 4556FA671F54AA9500AF40DD /* DebugUIProfile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugUIProfile.swift; sourceTree = ""; }; 455A16DB1F1FEA0000F86704 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; 455A16DC1F1FEA0000F86704 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; 455AC69A1F4F79E500134004 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; @@ -1136,6 +1139,7 @@ 34D8C0251ED3673300188D7C /* DebugUITableViewController.h */, 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */, 45638BDB1F3DD0D400128435 /* DebugUICalling.swift */, + 4556FA671F54AA9500AF40DD /* DebugUIProfile.swift */, ); path = DebugUI; sourceTree = ""; @@ -2289,6 +2293,7 @@ 34F3089F1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m in Sources */, 34B3F8861E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m in Sources */, 452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */, + 4556FA681F54AA9500AF40DD /* DebugUIProfile.swift in Sources */, 452EA0971EA662330078744B /* AttachmentPointerAdapter.swift in Sources */, 34DFCB851E8E04B500053165 /* AddToBlockListViewController.m in Sources */, 45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */, @@ -2452,6 +2457,7 @@ 954AEE6A1DF33E01002E5410 /* ContactsPickerTest.swift in Sources */, B660F77B1C29988E00687D6E /* Queue.m in Sources */, 455AC69C1F4F79E500134004 /* ImageCache.swift in Sources */, + 4556FA691F54AA9500AF40DD /* DebugUIProfile.swift in Sources */, 45666F581D9B2880008FE134 /* OWSScrubbingLogFormatterTest.m in Sources */, B660F77F1C29988E00687D6E /* DateUtil.m in Sources */, B660F7811C29988E00687D6E /* FunctionalUtil.m in Sources */, diff --git a/Signal/src/Profiles/OWSProfileManager.h b/Signal/src/Profiles/OWSProfileManager.h index 9040033a7..a54bda298 100644 --- a/Signal/src/Profiles/OWSProfileManager.h +++ b/Signal/src/Profiles/OWSProfileManager.h @@ -17,6 +17,7 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; @class TSThread; @class OWSAES256Key; +@class OWSMessageSender; // This class can be safely accessed and used from any thread. @interface OWSProfileManager : NSObject @@ -78,6 +79,12 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; profileNameEncrypted:(nullable NSData *)profileNameEncrypted avatarUrlPath:(nullable NSString *)avatarUrlPath; +#pragma mark - User Interface + +- (void)presentAddThreadToProfileWhitelist:(TSThread *)thread + fromViewController:(UIViewController *)fromViewController + success:(void (^)())successHandler; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index 209b5415c..863ace342 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -1024,7 +1024,10 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; NSURLRequest *request = [NSURLRequest requestWithURL:avatarUrlPath]; NSURLSessionDownloadTask *downloadTask = [self.avatarHTTPManager downloadTaskWithRequest:request progress:^(NSProgress *_Nonnull downloadProgress) { - DDLogVerbose(@"%@ Downloading avatar for %@", self.tag, userProfile.recipientId); + DDLogVerbose(@"%@ Downloading avatar for %@ %f", + self.tag, + userProfile.recipientId, + downloadProgress.fractionCompleted); } destination:^NSURL *_Nonnull(NSURL *_Nonnull targetPath, NSURLResponse *_Nonnull response) { return [NSURL fileURLWithPath:tempFilePath]; @@ -1332,6 +1335,62 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; } } +#pragma mark - User Interface + +- (void)presentAddThreadToProfileWhitelist:(TSThread *)thread + fromViewController:(UIViewController *)fromViewController + success:(void (^)())successHandler +{ + AssertIsOnMainThread(); + + UIAlertController *alertController = + [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + NSString *shareTitle = NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE", + @"Button to confirm that user wants to share their profile with a user or group."); + [alertController addAction:[UIAlertAction actionWithTitle:shareTitle + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + [self userAddedThreadToProfileWhitelist:thread + success:successHandler]; + }]]; + [alertController addAction:[OWSAlerts cancelAction]]; + + [fromViewController presentViewController:alertController animated:YES completion:nil]; +} + +- (void)userAddedThreadToProfileWhitelist:(TSThread *)thread + success:(void (^)())successHandler +{ + AssertIsOnMainThread(); + + OWSProfileKeyMessage *message = + [[OWSProfileKeyMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread]; + + BOOL isFeatureEnabled = NO; + if (!isFeatureEnabled) { + DDLogWarn(@"%@ skipping sending profile-key message because the feature is not yet fully available.", self.tag); + [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; + successHandler(); + return; + } + + [self.messageSender sendMessage:message + success:^{ + DDLogInfo(@"%@ Successfully sent profile key message to thread: %@", self.tag, thread); + [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; + + dispatch_async(dispatch_get_main_queue(), ^{ + successHandler(); + }); + } + failure:^(NSError *_Nonnull error) { + dispatch_async(dispatch_get_main_queue(), ^{ + DDLogError(@"%@ Failed to send profile key message to thread: %@", self.tag, thread); + }); + }]; +} + #pragma mark - Notifications - (void)applicationDidBecomeActive:(NSNotification *)notification diff --git a/Signal/src/Profiles/ProfileFetcherJob.swift b/Signal/src/Profiles/ProfileFetcherJob.swift index 9e32c359d..0a8119c8d 100644 --- a/Signal/src/Profiles/ProfileFetcherJob.swift +++ b/Signal/src/Profiles/ProfileFetcherJob.swift @@ -60,7 +60,7 @@ class ProfileFetcherJob: NSObject { if remainingRetries > 0 { self.updateProfile(recipientId: recipientId, remainingRetries: remainingRetries - 1) } else { - owsFail("\(self.TAG) in \(#function) failed to get profile with error: \(error)") + Logger.error("\(self.TAG) in \(#function) failed to get profile with error: \(error)") } } }.retainUntilComplete() diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index d155318f0..960c613b2 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -70,6 +70,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index 5e714b676..defb1508f 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -78,6 +78,7 @@ #import #import #import +#import #import #import #import @@ -900,22 +901,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { return; } - UIAlertController *alertController = - [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - - UIAlertAction *whitelistAction = [UIAlertAction - actionWithTitle:NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE", - @"Button to confirm that user wants to share their profile with a user or group.") - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction *_Nonnull action) { - [OWSProfileManager.sharedManager addThreadToProfileWhitelist:self.thread]; - - [self ensureBannerState]; - }]; - [alertController addAction:whitelistAction]; - [alertController addAction:[OWSAlerts cancelAction]]; - - [self presentViewController:alertController animated:YES completion:nil]; + [self presentAddThreadToProfileWhitelistWithSuccess:^{ + [self ensureBannerState]; + }]; } - (void)noLongerVerifiedBannerViewWasTapped:(UIGestureRecognizer *)sender @@ -2782,34 +2770,28 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { - (void)tappedAddToProfileWhitelistOfferMessage:(OWSContactOffersInteraction *)interaction { + // This is accessed via the contact offer. Group whitelisting happens via a different interaction. if (![self.thread isKindOfClass:[TSContactThread class]]) { OWSFail(@"%@ unexpected thread: %@ in %s", self.tag, self.thread, __PRETTY_FUNCTION__); return; } TSContactThread *contactThread = (TSContactThread *)self.thread; - UIAlertController *alertController = - [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - - UIAlertAction *leaveAction = [UIAlertAction - actionWithTitle:NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE", - @"Button to confirm that user wants to share their profile with a user or group.") - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction *_Nonnull action) { - [OWSProfileManager.sharedManager addThreadToProfileWhitelist:self.thread]; - - // Delete the offers. - [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - contactThread.hasDismissedOffers = YES; - [contactThread saveWithTransaction:transaction]; - [interaction removeWithTransaction:transaction]; - }]; - }]; - [alertController addAction:leaveAction]; - - [alertController addAction:[OWSAlerts cancelAction]]; + [self presentAddThreadToProfileWhitelistWithSuccess:^() { + // Delete the offers. + [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + contactThread.hasDismissedOffers = YES; + [contactThread saveWithTransaction:transaction]; + [interaction removeWithTransaction:transaction]; + }]; + }]; +} - [self presentViewController:alertController animated:YES completion:nil]; +- (void)presentAddThreadToProfileWhitelistWithSuccess:(void (^)())successHandler +{ + [[OWSProfileManager sharedManager] presentAddThreadToProfileWhitelist:self.thread + fromViewController:self + success:successHandler]; } #pragma mark - OWSSystemMessageCellDelegate diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m index bffd48f61..db6296fc0 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m @@ -53,20 +53,6 @@ NS_ASSUME_NONNULL_BEGIN actionBlock:^{ [DebugUIMisc setManualCensorshipCircumventionEnabled:NO]; }]]; -#ifdef DEBUG - [items addObject:[OWSTableItem itemWithTitle:@"Clear Profile Whitelist" - actionBlock:^{ - [OWSProfileManager.sharedManager clearProfileWhitelist]; - }]]; - [items addObject:[OWSTableItem itemWithTitle:@"Log Profile Whitelist" - actionBlock:^{ - [OWSProfileManager.sharedManager logProfileWhitelist]; - }]]; - [items addObject:[OWSTableItem itemWithTitle:@"Regenerate Profile/ProfileKey" - actionBlock:^{ - [[OWSProfileManager sharedManager] regenerateLocalProfile]; - }]]; -#endif [items addObject:[OWSTableItem itemWithTitle:@"Clear hasDismissedOffers" actionBlock:^{ [DebugUIMisc clearHasDismissedOffers]; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIProfile.swift b/Signal/src/ViewControllers/DebugUI/DebugUIProfile.swift new file mode 100644 index 000000000..27011a500 --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUIProfile.swift @@ -0,0 +1,50 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation + +class DebugUIProfile: DebugUIPage { + + let TAG = "[DebugUIProfile]" + + // MARK: Dependencies + + var messageSender: MessageSender { + return Environment.getCurrent().messageSender + } + var profileManager: OWSProfileManager { + return OWSProfileManager.shared() + } + + // MARK: Overrides + + override func name() -> String { + return "Profile" + } + + override func section(thread aThread: TSThread?) -> OWSTableSection? { + let sectionItems = [ + OWSTableItem(title: "Clear Profile Whitelist") { + self.profileManager.clearProfileWhitelist() + }, + OWSTableItem(title: "Log Profile Whitelist") { + self.profileManager.logProfileWhitelist() + }, + OWSTableItem(title: "Regenerate Profile/ProfileKey") { + self.profileManager.regenerateLocalProfile() + }, + OWSTableItem(title: "Send profile key message.") { + let message = OWSProfileKeyMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: aThread) + self.messageSender.sendPromise(message: message).then { + Logger.info("Successfully sent profile key message to thread: \(String(describing: aThread))") + }.catch { _ in + owsFail("Failed to send profile key message to thread: \(String(describing: aThread))") + } + } + ] + + return OWSTableSection(title: "Profile", items: sectionItems) + } + +} diff --git a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m index d00570938..f1b9e6dad 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m @@ -95,6 +95,7 @@ NS_ASSUME_NONNULL_BEGIN [subsectionItems addObject:[self itemForSubsection:[DebugUICalling new] viewController:viewController thread:thread]]; } + [subsectionItems addObject:[self itemForSubsection:[DebugUIProfile new] viewController:viewController thread:thread]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIMisc new] viewController:viewController thread:thread]]; [contents addSection:[OWSTableSection sectionWithTitle:@"Sections" items:subsectionItems]]; diff --git a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m index 451ed3121..fd197388b 100644 --- a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m @@ -785,27 +785,11 @@ NS_ASSUME_NONNULL_BEGIN { [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES]; - UIAlertController *alertController = - [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - - UIAlertAction *leaveAction = [UIAlertAction - actionWithTitle:NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE", - @"Button to confirm that user wants to share their profile with a user or group.") - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction *_Nonnull action) { - [self shareProfile]; - }]; - [alertController addAction:leaveAction]; - [alertController addAction:[OWSAlerts cancelAction]]; - - [self presentViewController:alertController animated:YES completion:nil]; -} - -- (void)shareProfile -{ - [OWSProfileManager.sharedManager addThreadToProfileWhitelist:self.thread]; - - [self updateTableContents]; + [OWSProfileManager.sharedManager presentAddThreadToProfileWhitelist:self.thread + fromViewController:self + success:^{ + [self updateTableContents]; + }]; } - (void)showVerificationView diff --git a/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto b/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto index b7143f49c..127564f86 100644 --- a/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto +++ b/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto @@ -82,6 +82,7 @@ message DataMessage { enum Flags { END_SESSION = 1; EXPIRATION_TIMER_UPDATE = 2; + PROFILE_KEY = 4; } optional string body = 1; diff --git a/SignalServiceKit/src/Messages/OWSProfileKeyMessage.h b/SignalServiceKit/src/Messages/OWSProfileKeyMessage.h new file mode 100644 index 000000000..cd8c4d73a --- /dev/null +++ b/SignalServiceKit/src/Messages/OWSProfileKeyMessage.h @@ -0,0 +1,13 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "TSOutgoingMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSProfileKeyMessage : TSOutgoingMessage + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSProfileKeyMessage.m b/SignalServiceKit/src/Messages/OWSProfileKeyMessage.m new file mode 100644 index 000000000..06af08c8c --- /dev/null +++ b/SignalServiceKit/src/Messages/OWSProfileKeyMessage.m @@ -0,0 +1,50 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSProfileKeyMessage.h" +#import "OWSSignalServiceProtos.pb.h" +#import "ProfileManagerProtocol.h" +#import "ProtoBuf+OWS.h" +#import "TextSecureKitEnv.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OWSProfileKeyMessage + +- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ + // override superclass with no-op. + // + // There's no need to save this message, since it's not displayed to the user. +} + +- (BOOL)shouldSyncTranscript +{ + return NO; +} + +- (OWSSignalServiceProtosDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId +{ + OWSAssert(self.thread); + + OWSSignalServiceProtosDataMessageBuilder *builder = [self dataMessageBuilder]; + [builder addLocalProfileKey]; + [builder setFlags:OWSSignalServiceProtosDataMessageFlagsProfileKey]; + + if (recipientId.length > 0) { + // Once we've shared our profile key with a user (perhaps due to being + // a member of a whitelisted group), make sure they're whitelisted. + id profileManager = [TextSecureKitEnv sharedEnv].profileManager; + // FIXME PERF avoid this dispatch. It's going to happen for *each* recipient in a group message. + dispatch_async(dispatch_get_main_queue(), ^{ + [profileManager addUserToProfileWhitelist:recipientId]; + }); + } + + return [builder build]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h index af214bbde..37430bee2 100644 --- a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h +++ b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h @@ -112,6 +112,7 @@ NSString *NSStringFromOWSSignalServiceProtosEnvelopeType(OWSSignalServiceProtosE typedef NS_ENUM(SInt32, OWSSignalServiceProtosDataMessageFlags) { OWSSignalServiceProtosDataMessageFlagsEndSession = 1, OWSSignalServiceProtosDataMessageFlagsExpirationTimerUpdate = 2, + OWSSignalServiceProtosDataMessageFlagsProfileKey = 4, }; BOOL OWSSignalServiceProtosDataMessageFlagsIsValidValue(OWSSignalServiceProtosDataMessageFlags value); diff --git a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m index b64ef57be..7c8c8c478 100644 --- a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m +++ b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m @@ -3103,6 +3103,7 @@ BOOL OWSSignalServiceProtosDataMessageFlagsIsValidValue(OWSSignalServiceProtosDa switch (value) { case OWSSignalServiceProtosDataMessageFlagsEndSession: case OWSSignalServiceProtosDataMessageFlagsExpirationTimerUpdate: + case OWSSignalServiceProtosDataMessageFlagsProfileKey: return YES; default: return NO; @@ -3114,6 +3115,8 @@ NSString *NSStringFromOWSSignalServiceProtosDataMessageFlags(OWSSignalServicePro return @"OWSSignalServiceProtosDataMessageFlagsEndSession"; case OWSSignalServiceProtosDataMessageFlagsExpirationTimerUpdate: return @"OWSSignalServiceProtosDataMessageFlagsExpirationTimerUpdate"; + case OWSSignalServiceProtosDataMessageFlagsProfileKey: + return @"OWSSignalServiceProtosDataMessageFlagsProfileKey"; default: return nil; } diff --git a/SignalServiceKit/src/Messages/TSMessagesManager.m b/SignalServiceKit/src/Messages/TSMessagesManager.m index bb7ac133a..8ad350d52 100644 --- a/SignalServiceKit/src/Messages/TSMessagesManager.m +++ b/SignalServiceKit/src/Messages/TSMessagesManager.m @@ -167,10 +167,9 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(envelope != nil); - return [NSString stringWithFormat:@"", + return [NSString stringWithFormat:@"", [self descriptionForEnvelopeType:envelope], - envelope.source, - (unsigned int)envelope.sourceDevice, + envelopeAddress(envelope), envelope.timestamp, (unsigned long)envelope.content.length]; } @@ -205,15 +204,15 @@ NS_ASSUME_NONNULL_BEGIN NSMutableString *description = [NSMutableString new]; if (dataMessage.hasGroup) { - [description appendString:@"GroupDataMessage: "]; - } else { - [description appendString:@"DataMessage: "]; + [description appendString:@"(Group:YES) "]; } if ((dataMessage.flags & OWSSignalServiceProtosDataMessageFlagsEndSession) != 0) { [description appendString:@"EndSession"]; } else if ((dataMessage.flags & OWSSignalServiceProtosDataMessageFlagsExpirationTimerUpdate) != 0) { [description appendString:@"ExpirationTimerUpdate"]; + } else if ((dataMessage.flags & OWSSignalServiceProtosDataMessageFlagsProfileKey) != 0) { + [description appendString:@"ProfileKey"]; } else if (dataMessage.attachments.count > 0) { [description appendString:@"MessageWithAttachment"]; } else { @@ -296,10 +295,9 @@ NS_ASSUME_NONNULL_BEGIN DDLogDebug(@"%@ handled secure message.", self.tag); if (error) { DDLogError( - @"%@ handling secure message from address: %@.%d failed with error: %@", + @"%@ handling secure message from address: %@ failed with error: %@", self.tag, - envelope.source, - (unsigned int)envelope.sourceDevice, + envelopeAddress(envelope), error); OWSProdError( [OWSAnalyticsEvents messageManagerErrorCouldNotHandleSecureMessage]); @@ -314,11 +312,10 @@ NS_ASSUME_NONNULL_BEGIN completion:^(NSError *_Nullable error) { DDLogDebug(@"%@ handled pre-key whisper message", self.tag); if (error) { - DDLogError(@"%@ handling pre-key whisper message from address: %@.%d failed " + DDLogError(@"%@ handling pre-key whisper message from address: %@ failed " @"with error: %@", self.tag, - envelope.source, - (unsigned int)envelope.sourceDevice, + envelopeAddress(envelope), error); OWSProdError( [OWSAnalyticsEvents messageManagerErrorCouldNotHandlePrekeyBundle]); @@ -478,7 +475,10 @@ NS_ASSUME_NONNULL_BEGIN sourceId:envelope.source sourceDeviceId:envelope.sourceDevice]; if (duplicateEnvelope) { - DDLogInfo(@"%@ Ignoring previously received envelope from %@.%d with timestamp: %llu", self.tag, envelope.source, (unsigned int)envelope.sourceDevice, envelope.timestamp); + DDLogInfo(@"%@ Ignoring previously received envelope from %@ with timestamp: %llu", + self.tag, + envelopeAddress(envelope), + envelope.timestamp); return; } @@ -500,7 +500,7 @@ NS_ASSUME_NONNULL_BEGIN } else if (envelope.hasLegacyMessage) { // DEPRECATED - Remove after all clients have been upgraded. OWSSignalServiceProtosDataMessage *dataMessage = [OWSSignalServiceProtosDataMessage parseFromData:plaintextData]; - DDLogInfo(@"%@ handling dataMessage: %@", self.tag, [self descriptionForDataMessage:dataMessage]); + DDLogInfo(@"%@ handling message: ", self.tag, [self descriptionForDataMessage:dataMessage]); [self handleIncomingEnvelope:envelope withDataMessage:dataMessage]; } else { @@ -543,9 +543,9 @@ NS_ASSUME_NONNULL_BEGIN } // FIXME: https://github.com/WhisperSystems/Signal-iOS/issues/1340 - DDLogInfo(@"%@ Received message from group that I left or don't know about from: %@.", + DDLogInfo(@"%@ Received message from group that I left or don't know about from: %@", self.tag, - incomingEnvelope.source); + envelopeAddress(incomingEnvelope)); NSString *recipientId = incomingEnvelope.source; @@ -574,6 +574,8 @@ NS_ASSUME_NONNULL_BEGIN [self handleEndSessionMessageWithEnvelope:incomingEnvelope dataMessage:dataMessage]; } else if ((dataMessage.flags & OWSSignalServiceProtosDataMessageFlagsExpirationTimerUpdate) != 0) { [self handleExpirationTimerUpdateMessageWithEnvelope:incomingEnvelope dataMessage:dataMessage]; + } else if ((dataMessage.flags & OWSSignalServiceProtosDataMessageFlagsProfileKey) != 0) { + [self handleProfileKeyMessageWithEnvelope:incomingEnvelope dataMessage:dataMessage]; } else if (dataMessage.attachments.count > 0) { [self handleReceivedMediaWithEnvelope:incomingEnvelope dataMessage:dataMessage]; } else { @@ -837,6 +839,29 @@ NS_ASSUME_NONNULL_BEGIN [message save]; } +- (void)handleProfileKeyMessageWithEnvelope:(OWSSignalServiceProtosEnvelope *)incomingEnvelope + dataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage +{ + NSString *recipientId = incomingEnvelope.source; + if (!dataMessage.hasProfileKey) { + OWSFail(@"%@ received profile key message without profile key from: %@", + self.tag, + envelopeAddress(incomingEnvelope)); + return; + } + NSData *profileKey = dataMessage.profileKey; + if (profileKey.length != kAES256_KeyByteLength) { + OWSFail(@"%@ received profile key of unexpected length:%lu from:%@", + self.tag, + (unsigned long)profileKey.length, + envelopeAddress(incomingEnvelope)); + return; + } + + id profileManager = [TextSecureKitEnv sharedEnv].profileManager; + [profileManager setProfileKeyData:profileKey forRecipientId:recipientId]; +} + - (void)handleReceivedTextMessageWithEnvelope:(OWSSignalServiceProtosEnvelope *)textMessageEnvelope dataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage { @@ -978,15 +1003,28 @@ NS_ASSUME_NONNULL_BEGIN break; } case OWSSignalServiceProtosGroupContextTypeDeliver: { - incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timestamp - inThread:gThread - authorId:envelope.source - sourceDeviceId:envelope.sourceDevice - messageBody:body - attachmentIds:attachmentIds - expiresInSeconds:dataMessage.expireTimer]; - DDLogDebug(@"%@ incoming group text message: %@", self.tag, incomingMessage.debugDescription); - [incomingMessage saveWithTransaction:transaction]; + if (body.length == 0) { + DDLogWarn(@"%@ ignoring empty incoming message from: %@ for group: %@ with timestampe: %lu", + self.tag, + envelopeAddress(envelope), + groupId, + (unsigned long)timestamp); + } else { + DDLogDebug(@"%@ incoming message from: %@ for group: %@ with timestampe: %lu", + self.tag, + envelopeAddress(envelope), + groupId, + (unsigned long)timestamp); + incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timestamp + inThread:gThread + authorId:envelope.source + sourceDeviceId:envelope.sourceDevice + messageBody:body + attachmentIds:attachmentIds + expiresInSeconds:dataMessage.expireTimer]; + + [incomingMessage saveWithTransaction:transaction]; + } break; } default: { @@ -996,20 +1034,31 @@ NS_ASSUME_NONNULL_BEGIN thread = gThread; } else { - TSContactThread *cThread = [TSContactThread getOrCreateThreadWithContactId:envelope.source - transaction:transaction - relay:envelope.relay]; - - incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timestamp - inThread:cThread - authorId:[cThread contactIdentifier] - sourceDeviceId:envelope.sourceDevice - messageBody:body - attachmentIds:attachmentIds - expiresInSeconds:dataMessage.expireTimer]; - DDLogDebug(@"%@ incoming 1:1 text message: %@", self.tag, incomingMessage.debugDescription); - [incomingMessage saveWithTransaction:transaction]; - thread = cThread; + if (body.length == 0) { + DDLogWarn(@"%@ ignoring empty incoming message from: %@ with timestampe: %lu", + self.tag, + envelopeAddress(envelope), + (unsigned long)timestamp); + } else { + DDLogDebug(@"%@ incoming message from: %@ with timestampe: %lu", + self.tag, + envelopeAddress(envelope), + (unsigned long)timestamp); + TSContactThread *cThread = [TSContactThread getOrCreateThreadWithContactId:envelope.source + transaction:transaction + relay:envelope.relay]; + + incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timestamp + inThread:cThread + authorId:[cThread contactIdentifier] + sourceDeviceId:envelope.sourceDevice + messageBody:body + attachmentIds:attachmentIds + expiresInSeconds:dataMessage.expireTimer]; + + [incomingMessage saveWithTransaction:transaction]; + thread = cThread; + } } if (thread && incomingMessage) { @@ -1025,7 +1074,7 @@ NS_ASSUME_NONNULL_BEGIN // Other clients allow attachments to be sent along with body, we want the text displayed as a separate // message - if ([attachmentIds count] > 0 && body != nil && ![body isEqualToString:@""]) { + if ([attachmentIds count] > 0 && body != nil && body.length > 0) { // We want the text to be displayed under the attachment uint64_t textMessageTimestamp = timestamp + 1; TSIncomingMessage *textMessage = [[TSIncomingMessage alloc] initWithTimestamp:textMessageTimestamp @@ -1041,7 +1090,7 @@ NS_ASSUME_NONNULL_BEGIN } }]; - if (incomingMessage && thread) { + if (thread && incomingMessage) { // In case we already have a read receipt for this new message (happens sometimes). OWSReadReceiptsProcessor *readReceiptsProcessor = [[OWSReadReceiptsProcessor alloc] initWithIncomingMessage:incomingMessage @@ -1091,10 +1140,7 @@ NS_ASSUME_NONNULL_BEGIN } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { // Should no longer get here, since we now record the new identity for incoming messages. OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorUntrustedIdentityKeyException], envelope); - OWSFail(@"%@ Failed to trust identity on incoming message from: %@.%d", - self.tag, - envelope.source, - (unsigned int)envelope.sourceDevice); + OWSFail(@"%@ Failed to trust identity on incoming message from: %@", self.tag, envelopeAddress(envelope)); return; } else { OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorCorruptMessage], envelope); @@ -1117,6 +1163,12 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - helpers +// used in log formatting +NSString *envelopeAddress(OWSSignalServiceProtosEnvelope *envelope) +{ + return [NSString stringWithFormat:@"%@.%d", envelope.source, (unsigned int)envelope.sourceDevice]; +} + - (BOOL)isDataMessageGroupAvatarUpdate:(OWSSignalServiceProtosDataMessage *)dataMessage { return dataMessage.hasGroup diff --git a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h index 3ce1fc1d6..320635475 100644 --- a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h +++ b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSSignalServiceProtosDataMessageBuilder (OWS) - (void)addLocalProfileKeyIfNecessary:(TSThread *)thread recipientId:(NSString *_Nullable)recipientId; +- (void)addLocalProfileKey; @end diff --git a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m index a5afc5a45..00e207d9c 100644 --- a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m +++ b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m @@ -64,6 +64,11 @@ NS_ASSUME_NONNULL_BEGIN } } +- (void)addLocalProfileKey +{ + [self setProfileKey:self.localProfileKey.keyData]; +} + @end #pragma mark -