mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
9.6 KiB
Objective-C
238 lines
9.6 KiB
Objective-C
//
|
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "ContactsUpdater.h"
|
|
|
|
#import "Contact.h"
|
|
#import "Cryptography.h"
|
|
#import "PhoneNumber.h"
|
|
#import "OWSError.h"
|
|
#import "TSContactsIntersectionRequest.h"
|
|
#import "TSNetworkManager.h"
|
|
#import "TSStorageManager.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
@implementation ContactsUpdater
|
|
|
|
+ (instancetype)sharedUpdater {
|
|
static dispatch_once_t onceToken;
|
|
static id sharedInstance = nil;
|
|
dispatch_once(&onceToken, ^{
|
|
sharedInstance = [self new];
|
|
});
|
|
return sharedInstance;
|
|
}
|
|
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
OWSSingletonAssert();
|
|
|
|
return self;
|
|
}
|
|
|
|
- (nullable SignalRecipient *)synchronousLookup:(NSString *)identifier error:(NSError **)error
|
|
{
|
|
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
|
|
|
__block SignalRecipient *recipient;
|
|
|
|
// Assigning to a pointer parameter within the block is not preventing the referenced error from being dealloc
|
|
// Instead, we avoid ambiguity in ownership by assigning to a local __block variable ensuring the error will be
|
|
// retained until our error parameter can take ownership.
|
|
__block NSError *retainedError;
|
|
[self lookupIdentifier:identifier
|
|
success:^(SignalRecipient *fetchedRecipient) {
|
|
recipient = fetchedRecipient;
|
|
dispatch_semaphore_signal(sema);
|
|
}
|
|
failure:^(NSError *lookupError) {
|
|
retainedError = lookupError;
|
|
dispatch_semaphore_signal(sema);
|
|
}];
|
|
|
|
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
|
*error = retainedError;
|
|
return recipient;
|
|
}
|
|
|
|
- (void)lookupIdentifier:(NSString *)identifier
|
|
success:(void (^)(SignalRecipient *recipient))success
|
|
failure:(void (^)(NSError *error))failure
|
|
{
|
|
// This should never happen according to nullability annotations... but IIRC it does. =/
|
|
if (!identifier) {
|
|
OWSAssert(NO);
|
|
failure(OWSErrorWithCodeDescription(OWSErrorCodeInvalidMethodParameters, @"Cannot lookup nil identifier"));
|
|
return;
|
|
}
|
|
|
|
[self contactIntersectionWithSet:[NSSet setWithObject:identifier]
|
|
success:^(NSSet<NSString *> *_Nonnull matchedIds) {
|
|
if (matchedIds.count == 1) {
|
|
success([SignalRecipient recipientWithTextSecureIdentifier:identifier]);
|
|
} else {
|
|
failure(OWSErrorMakeNoSuchSignalRecipientError());
|
|
}
|
|
}
|
|
failure:failure];
|
|
}
|
|
|
|
- (void)lookupIdentifiers:(NSArray<NSString *> *)identifiers
|
|
success:(void (^)(NSArray<SignalRecipient *> *recipients))success
|
|
failure:(void (^)(NSError *error))failure
|
|
{
|
|
if (identifiers.count < 1) {
|
|
OWSAssert(NO);
|
|
failure(OWSErrorWithCodeDescription(OWSErrorCodeInvalidMethodParameters, @"Cannot lookup zero identifiers"));
|
|
return;
|
|
}
|
|
|
|
[self contactIntersectionWithSet:[NSSet setWithArray:identifiers]
|
|
success:^(NSSet<NSString *> *_Nonnull matchedIds) {
|
|
if (matchedIds.count > 0) {
|
|
NSMutableArray<SignalRecipient *> *recipients = [NSMutableArray new];
|
|
for (NSString *identifier in matchedIds) {
|
|
[recipients addObject:[SignalRecipient recipientWithTextSecureIdentifier:identifier]];
|
|
}
|
|
success([recipients copy]);
|
|
} else {
|
|
failure(OWSErrorMakeNoSuchSignalRecipientError());
|
|
}
|
|
}
|
|
failure:failure];
|
|
}
|
|
|
|
- (void)updateSignalContactIntersectionWithABContacts:(NSArray<Contact *> *)abContacts
|
|
success:(void (^)())success
|
|
failure:(void (^)(NSError *error))failure {
|
|
NSMutableSet<NSString *> *abPhoneNumbers = [NSMutableSet set];
|
|
|
|
for (Contact *contact in abContacts) {
|
|
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
|
|
[abPhoneNumbers addObject:phoneNumber.toE164];
|
|
}
|
|
}
|
|
|
|
NSMutableSet *recipientIds = [NSMutableSet set];
|
|
[[TSStorageManager sharedManager].dbReadConnection
|
|
readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
|
NSArray *allRecipientKeys = [transaction allKeysInCollection:[SignalRecipient collection]];
|
|
[recipientIds addObjectsFromArray:allRecipientKeys];
|
|
}];
|
|
|
|
NSMutableSet<NSString *> *allContacts = [[abPhoneNumbers setByAddingObjectsFromSet:recipientIds] mutableCopy];
|
|
|
|
[self contactIntersectionWithSet:allContacts
|
|
success:^(NSSet<NSString *> *matchedIds) {
|
|
[recipientIds minusSet:matchedIds];
|
|
|
|
// Cleaning up unregistered identifiers
|
|
[[TSStorageManager sharedManager].dbReadWriteConnection
|
|
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
for (NSString *identifier in recipientIds) {
|
|
SignalRecipient *recipient =
|
|
[SignalRecipient fetchObjectWithUniqueID:identifier
|
|
transaction:transaction];
|
|
|
|
[recipient removeWithTransaction:transaction];
|
|
}
|
|
}];
|
|
|
|
DDLogInfo(@"%@ successfully intersected contacts.", self.tag);
|
|
success();
|
|
}
|
|
failure:failure];
|
|
}
|
|
|
|
- (void)contactIntersectionWithSet:(NSSet<NSString *> *)idSet
|
|
success:(void (^)(NSSet<NSString *> *matchedIds))success
|
|
failure:(void (^)(NSError *error))failure {
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
NSMutableDictionary *phoneNumbersByHashes = [NSMutableDictionary dictionary];
|
|
for (NSString *identifier in idSet) {
|
|
[phoneNumbersByHashes setObject:identifier
|
|
forKey:[Cryptography truncatedSHA1Base64EncodedWithoutPadding:identifier]];
|
|
}
|
|
NSArray *hashes = [phoneNumbersByHashes allKeys];
|
|
|
|
TSRequest *request = [[TSContactsIntersectionRequest alloc] initWithHashesArray:hashes];
|
|
[[TSNetworkManager sharedManager] makeRequest:request
|
|
success:^(NSURLSessionDataTask *tsTask, id responseDict) {
|
|
NSMutableDictionary *attributesForIdentifier = [NSMutableDictionary dictionary];
|
|
NSArray *contactsArray = [(NSDictionary *)responseDict objectForKey:@"contacts"];
|
|
|
|
// Map attributes to phone numbers
|
|
if (contactsArray) {
|
|
for (NSDictionary *dict in contactsArray) {
|
|
NSString *hash = [dict objectForKey:@"token"];
|
|
NSString *identifier = [phoneNumbersByHashes objectForKey:hash];
|
|
|
|
if (!identifier) {
|
|
DDLogWarn(@"%@ An interesecting hash wasn't found in the mapping.", self.tag);
|
|
break;
|
|
}
|
|
|
|
[attributesForIdentifier setObject:dict forKey:identifier];
|
|
}
|
|
}
|
|
|
|
// Insert or update contact attributes
|
|
[[TSStorageManager sharedManager].dbReadWriteConnection
|
|
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
for (NSString *identifier in attributesForIdentifier) {
|
|
SignalRecipient *recipient = [SignalRecipient recipientWithTextSecureIdentifier:identifier
|
|
withTransaction:transaction];
|
|
if (!recipient) {
|
|
recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:identifier relay:nil];
|
|
}
|
|
|
|
NSDictionary *attributes = [attributesForIdentifier objectForKey:identifier];
|
|
|
|
recipient.relay = attributes[@"relay"];
|
|
|
|
[recipient saveWithTransaction:transaction];
|
|
}
|
|
}];
|
|
|
|
success([NSSet setWithArray:attributesForIdentifier.allKeys]);
|
|
}
|
|
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
|
if (!IsNSErrorNetworkFailure(error)) {
|
|
OWSProdErrorWNSError(@"contacts_error_contacts_intersection_failed", error);
|
|
}
|
|
|
|
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
|
|
if (response.statusCode == 413) {
|
|
failure(OWSErrorWithCodeDescription(
|
|
OWSErrorCodeContactsUpdaterRateLimit, @"Contacts Intersection Rate Limit"));
|
|
} else {
|
|
failure(error);
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
#pragma mark - Logging
|
|
|
|
+ (NSString *)tag
|
|
{
|
|
return [NSString stringWithFormat:@"[%@]", self.class];
|
|
}
|
|
|
|
- (NSString *)tag
|
|
{
|
|
return self.class.tag;
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|