diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index cd7cca74b..287443b2c 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -9,6 +9,7 @@ #import "ViewControllerUtils.h" #import #import +#import #import #import @@ -205,6 +206,8 @@ NSString *const kTSStorageManager_AccountLastNames = @"kTSStorageManager_Account self.signalAccountMap = [signalAccountMap copy]; self.signalAccounts = [signalAccounts copy]; + [OWSProfilesManager.sharedManager setContactRecipientIds:signalAccountMap.allKeys]; + [self updateCachedDisplayNames]; }); }); diff --git a/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto b/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto index ab87cbf65..e1b81b1e0 100644 --- a/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto +++ b/SignalServiceKit/protobuf/OWSSignalServiceProtos.proto @@ -35,7 +35,7 @@ message Content { optional SyncMessage syncMessage = 2; optional CallMessage callMessage = 3; optional NullMessage nullMessage = 4; - optional string profileKey = 5; + optional bytes profileKey = 5; } message NullMessage { diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m index 7365aae9e..c2a827135 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m @@ -3,8 +3,9 @@ // #import "OWSOutgoingSyncMessage.h" -#import "OWSSignalServiceProtos.pb.h" #import "Cryptography.h" +#import "OWSProfilesManager.h" +#import "OWSSignalServiceProtos.pb.h" NS_ASSUME_NONNULL_BEGIN @@ -44,6 +45,12 @@ NS_ASSUME_NONNULL_BEGIN { OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new]; [contentBuilder setSyncMessage:[self buildSyncMessage]]; + +#ifndef SKIP_PROFILE_KEYS + if (OWSProfilesManager.sharedManager.localProfileKey) { + [contentBuilder setProfileKey:OWSProfilesManager.sharedManager.localProfileKey]; + } +#endif return [[contentBuilder build] data]; } diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index bfbb17390..c6e78154a 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -4,6 +4,7 @@ #import "TSOutgoingMessage.h" #import "NSDate+millisecondTimeStamp.h" +#import "OWSProfilesManager.h" #import "OWSSignalServiceProtos.pb.h" #import "TSAttachmentStream.h" #import "TSContactThread.h" @@ -459,6 +460,19 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec { OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new]; contentBuilder.dataMessage = [self buildDataMessage]; + +#ifndef SKIP_PROFILE_KEYS + OWSAssert([self.thread isKindOfClass:[TSContactThread class]]); + if ([self.thread isKindOfClass:[TSContactThread class]]) { + TSContactThread *contactThread = (TSContactThread *)self.thread; + NSString *recipientId = contactThread.contactIdentifier; + + if (OWSProfilesManager.sharedManager.localProfileKey && + [OWSProfilesManager.sharedManager isUserInProfileWhitelist:recipientId]) { + [contentBuilder setProfileKey:OWSProfilesManager.sharedManager.localProfileKey]; + } + } +#endif return [[contentBuilder build] data]; } diff --git a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m b/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m index b1907b8a1..52dadd83a 100644 --- a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m +++ b/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m @@ -9,7 +9,9 @@ #import "OWSCallHangupMessage.h" #import "OWSCallIceUpdateMessage.h" #import "OWSCallOfferMessage.h" +#import "OWSProfilesManager.h" #import "OWSSignalServiceProtos.pb.h" +#import "TSContactThread.h" NS_ASSUME_NONNULL_BEGIN @@ -124,6 +126,19 @@ NS_ASSUME_NONNULL_BEGIN { OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new]; [contentBuilder setCallMessage:[self asProtobuf]]; + +#ifndef SKIP_PROFILE_KEYS + OWSAssert([self.thread isKindOfClass:[TSContactThread class]]); + if ([self.thread isKindOfClass:[TSContactThread class]]) { + TSContactThread *contactThread = (TSContactThread *)self.thread; + NSString *recipientId = contactThread.contactIdentifier; + + if (OWSProfilesManager.sharedManager.localProfileKey && + [OWSProfilesManager.sharedManager isUserInProfileWhitelist:recipientId]) { + [contentBuilder setProfileKey:OWSProfilesManager.sharedManager.localProfileKey]; + } + } +#endif return [[contentBuilder build] data]; } diff --git a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m b/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m index 6794fa291..372af0e61 100644 --- a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m +++ b/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m @@ -3,11 +3,12 @@ // #import "OWSOutgoingNullMessage.h" -#import "OWSSignalServiceProtos.pb.h" #import "Cryptography.h" -#import "OWSVerificationStateSyncMessage.h" #import "NSDate+millisecondTimeStamp.h" #import "TSContactThread.h" +#import "OWSProfilesManager.h" +#import "OWSSignalServiceProtos.pb.h" +#import "OWSVerificationStateSyncMessage.h" NS_ASSUME_NONNULL_BEGIN @@ -57,6 +58,19 @@ NS_ASSUME_NONNULL_BEGIN contentBuilder.nullMessage = [nullMessageBuilder build]; +#ifndef SKIP_PROFILE_KEYS + OWSAssert([self.thread isKindOfClass:[TSContactThread class]]); + if ([self.thread isKindOfClass:[TSContactThread class]]) { + TSContactThread *contactThread = (TSContactThread *)self.thread; + NSString *recipientId = contactThread.contactIdentifier; + + if (OWSProfilesManager.sharedManager.localProfileKey && + [OWSProfilesManager.sharedManager isUserInProfileWhitelist:recipientId]) { + [contentBuilder setProfileKey:OWSProfilesManager.sharedManager.localProfileKey]; + } + } +#endif + return [contentBuilder build].data; } diff --git a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h index a7830d9e3..32e1aef3c 100644 --- a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h +++ b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.h @@ -278,16 +278,16 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro #define Content_profileKey @"profileKey" @interface OWSSignalServiceProtosContent : PBGeneratedMessage { @private - BOOL hasProfileKey_:1; BOOL hasDataMessage_:1; BOOL hasSyncMessage_:1; BOOL hasCallMessage_:1; BOOL hasNullMessage_:1; - NSString* profileKey; + BOOL hasProfileKey_:1; OWSSignalServiceProtosDataMessage* dataMessage; OWSSignalServiceProtosSyncMessage* syncMessage; OWSSignalServiceProtosCallMessage* callMessage; OWSSignalServiceProtosNullMessage* nullMessage; + NSData* profileKey; } - (BOOL) hasDataMessage; - (BOOL) hasSyncMessage; @@ -298,7 +298,7 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro @property (readonly, strong) OWSSignalServiceProtosSyncMessage* syncMessage; @property (readonly, strong) OWSSignalServiceProtosCallMessage* callMessage; @property (readonly, strong) OWSSignalServiceProtosNullMessage* nullMessage; -@property (readonly, strong) NSString* profileKey; +@property (readonly, strong) NSData* profileKey; + (instancetype) defaultInstance; - (instancetype) defaultInstance; @@ -364,8 +364,8 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro - (OWSSignalServiceProtosContentBuilder*) clearNullMessage; - (BOOL) hasProfileKey; -- (NSString*) profileKey; -- (OWSSignalServiceProtosContentBuilder*) setProfileKey:(NSString*) value; +- (NSData*) profileKey; +- (OWSSignalServiceProtosContentBuilder*) setProfileKey:(NSData*) value; - (OWSSignalServiceProtosContentBuilder*) clearProfileKey; @end diff --git a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m index 44af13083..e741fba39 100644 --- a/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m +++ b/SignalServiceKit/src/Messages/OWSSignalServiceProtos.pb.m @@ -560,7 +560,7 @@ NSString *NSStringFromOWSSignalServiceProtosEnvelopeType(OWSSignalServiceProtosE @property (strong) OWSSignalServiceProtosSyncMessage* syncMessage; @property (strong) OWSSignalServiceProtosCallMessage* callMessage; @property (strong) OWSSignalServiceProtosNullMessage* nullMessage; -@property (strong) NSString* profileKey; +@property (strong) NSData* profileKey; @end @implementation OWSSignalServiceProtosContent @@ -606,7 +606,7 @@ NSString *NSStringFromOWSSignalServiceProtosEnvelopeType(OWSSignalServiceProtosE self.syncMessage = [OWSSignalServiceProtosSyncMessage defaultInstance]; self.callMessage = [OWSSignalServiceProtosCallMessage defaultInstance]; self.nullMessage = [OWSSignalServiceProtosNullMessage defaultInstance]; - self.profileKey = @""; + self.profileKey = [NSData data]; } return self; } @@ -639,7 +639,7 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan [output writeMessage:4 value:self.nullMessage]; } if (self.hasProfileKey) { - [output writeString:5 value:self.profileKey]; + [output writeData:5 value:self.profileKey]; } [self.unknownFields writeToCodedOutputStream:output]; } @@ -663,7 +663,7 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan size_ += computeMessageSize(4, self.nullMessage); } if (self.hasProfileKey) { - size_ += computeStringSize(5, self.profileKey); + size_ += computeDataSize(5, self.profileKey); } size_ += self.unknownFields.serializedSize; memoizedSerializedSize = size_; @@ -909,7 +909,7 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan break; } case 42: { - [self setProfileKey:[input readString]]; + [self setProfileKey:[input readData]]; break; } } @@ -1038,17 +1038,17 @@ static OWSSignalServiceProtosContent* defaultOWSSignalServiceProtosContentInstan - (BOOL) hasProfileKey { return resultContent.hasProfileKey; } -- (NSString*) profileKey { +- (NSData*) profileKey { return resultContent.profileKey; } -- (OWSSignalServiceProtosContentBuilder*) setProfileKey:(NSString*) value { +- (OWSSignalServiceProtosContentBuilder*) setProfileKey:(NSData*) value { resultContent.hasProfileKey = YES; resultContent.profileKey = value; return self; } - (OWSSignalServiceProtosContentBuilder*) clearProfileKey { resultContent.hasProfileKey = NO; - resultContent.profileKey = @""; + resultContent.profileKey = [NSData data]; return self; } @end diff --git a/SignalServiceKit/src/Messages/TSMessagesManager.m b/SignalServiceKit/src/Messages/TSMessagesManager.m index 1b48a95cd..9873c0818 100644 --- a/SignalServiceKit/src/Messages/TSMessagesManager.m +++ b/SignalServiceKit/src/Messages/TSMessagesManager.m @@ -19,6 +19,7 @@ #import "OWSIncomingMessageFinder.h" #import "OWSIncomingSentMessageTranscript.h" #import "OWSMessageSender.h" +#import "OWSProfilesManager.h" #import "OWSReadReceiptsProcessor.h" #import "OWSRecordTranscriptJob.h" #import "OWSSyncContactsMessage.h" @@ -494,6 +495,16 @@ NS_ASSUME_NONNULL_BEGIN if (envelope.hasContent) { OWSSignalServiceProtosContent *content = [OWSSignalServiceProtosContent parseFromData:plaintextData]; DDLogInfo(@"%@ handling content: ", self.tag, [self descriptionForContent:content]); + +#ifndef SKIP_PROFILE_KEYS + if ([content hasProfileKey]) { + NSData *profileKey = [content profileKey]; + NSString *recipientId = envelope.source; + [OWSProfilesManager.sharedManager setProfileKey:profileKey + forRecipientId:recipientId]; + } +#endif + if (content.hasSyncMessage) { [self handleIncomingEnvelope:envelope withSyncMessage:content.syncMessage]; } else if (content.hasDataMessage) { diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/SignalServiceKit/src/Profiles/OWSProfilesManager.h index 95c115d5a..067cb914d 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.h @@ -6,6 +6,9 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *const kNSNotificationName_LocalProfileDidChange; +// TODO: Remove feature flag +//#define SKIP_PROFILE_KEYS + // This class can be safely accessed and used from any thread. @interface OWSProfilesManager : NSObject @@ -15,6 +18,7 @@ extern NSString *const kNSNotificationName_LocalProfileDidChange; #pragma mark - Local Profile +@property (atomic, readonly) NSData *localProfileKey; @property (atomic, nullable, readonly) NSString *localProfileName; @property (atomic, nullable, readonly) UIImage *localProfileAvatarImage; @@ -34,6 +38,8 @@ extern NSString *const kNSNotificationName_LocalProfileDidChange; - (BOOL)isUserInProfileWhitelist:(NSString *)recipientId; +- (void)setContactRecipientIds:(NSArray *)contactRecipientIds; + #pragma mark - Known Profile Keys - (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId; diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/SignalServiceKit/src/Profiles/OWSProfilesManager.m index 5b08e4196..4f5926f55 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.m @@ -94,22 +94,22 @@ static const NSInteger kProfileKeyLength = 16; @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (atomic, readonly, nullable) NSData *localProfileKey; - // These properties should only be mutated on the main thread, // but they may be accessed on other threads. @property (atomic, nullable) NSString *localProfileName; @property (atomic, nullable) UIImage *localProfileAvatarImage; @property (atomic, nullable) AvatarMetadata *localProfileAvatarMetadata; +// These caches are lazy-populated. The single point truth is the database. +@property (nonatomic, readonly) NSMutableDictionary*profileWhitelistCache; +@property (nonatomic, readonly) NSMutableDictionary*knownProfileKeyCache; + @end #pragma mark - @implementation OWSProfilesManager -@synthesize localProfileKey = _localProfileKey; - + (instancetype)sharedManager { static OWSProfilesManager *sharedMyManager = nil; @@ -137,11 +137,14 @@ static const NSInteger kProfileKeyLength = 16; return self; } + OWSAssert([NSThread isMainThread]); OWSAssert(storageManager); OWSAssert(messageSender); _messageSender = messageSender; _dbConnection = storageManager.newDatabaseConnection; + _profileWhitelistCache = [NSMutableDictionary new]; + _knownProfileKeyCache = [NSMutableDictionary new]; OWSSingletonAssert(); @@ -414,13 +417,35 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert(recipientId.length > 0); [self.dbConnection setObject:@(1) forKey:recipientId inCollection:kOWSProfilesManager_WhitelistCollection]; + self.profileWhitelistCache[recipientId] = @(YES); } - (BOOL)isUserInProfileWhitelist:(NSString *)recipientId { OWSAssert(recipientId.length > 0); - return (nil != [self.dbConnection objectForKey:recipientId inCollection:kOWSProfilesManager_WhitelistCollection]); + NSNumber *_Nullable value = self.profileWhitelistCache[recipientId]; + if (value) { + return [value boolValue]; + } + + value = @(nil != [self.dbConnection objectForKey:recipientId inCollection:kOWSProfilesManager_WhitelistCollection]); + self.profileWhitelistCache[recipientId] = value; + return [value boolValue]; +} + +- (void)setContactRecipientIds:(NSArray *)contactRecipientIds +{ + OWSAssert(contactRecipientIds); + + // TODO: The persisted whitelist could either be: + // + // * Just users manually added to the whitelist. + // * Also include users auto-added by, for example, being in the user's + // contacts or when the user initiates a 1:1 conversation with them, etc. + for (NSString *recipientId in contactRecipientIds) { + [self addUserToProfileWhitelist:recipientId]; + } } #pragma mark - Known Profile Keys @@ -429,20 +454,37 @@ static const NSInteger kProfileKeyLength = 16; { OWSAssert(profileKey.length == kProfileKeyLength); OWSAssert(recipientId.length > 0); + if (profileKey.length != kProfileKeyLength) { + return; + } + NSData *_Nullable existingProfileKey = [self profileKeyForRecipientId:recipientId]; + if (existingProfileKey && + [existingProfileKey isEqual:profileKey]) { + // Ignore redundant update. + return; + } + [self.dbConnection setObject:profileKey forKey:recipientId inCollection:kOWSProfilesManager_KnownProfileKeysCollection]; + self.knownProfileKeyCache[recipientId] = profileKey; } - (nullable NSData *)profileKeyForRecipientId:(NSString *)recipientId { OWSAssert(recipientId.length > 0); - NSData *_Nullable profileKey = - [self.dbConnection objectForKey:recipientId inCollection:kOWSProfilesManager_KnownProfileKeysCollection]; + NSData *_Nullable profileKey = self.knownProfileKeyCache[recipientId]; + if (profileKey.length > 0) { + return profileKey; + } + + profileKey = + [self.dbConnection objectForKey:recipientId inCollection:kOWSProfilesManager_KnownProfileKeysCollection]; if (profileKey) { OWSAssert(profileKey.length == kProfileKeyLength); + self.knownProfileKeyCache[recipientId] = profileKey; } return profileKey; }