// // ContactsManager+updater.m // Signal // // Created by Frederic Jacobs on 21/11/15. // Copyright © 2015 Open Whisper Systems. All rights reserved. // #import "ContactsManager+updater.h" #import "Cryptography.h" #import "Environment.h" #import "TSContactsIntersectionRequest.h" #import "TSNetworkManager.h" @implementation ContactsManager (updater) - (void)synchronousLookup:(NSString*)identifier success:(void (^)(SignalRecipient*))success failure:(void (^)(NSError *error))failure { __block dispatch_semaphore_t sema = dispatch_semaphore_create(0); __block SignalRecipient *recipient = nil; __block NSError *error = nil; [self lookupIdentifier:identifier success:^(NSSet *matchedIds) { if ([matchedIds count] == 1) { [[TSStorageManager sharedManager].dbConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { recipient = [SignalRecipient recipientWithTextSecureIdentifier:identifier withTransaction:transaction]; }]; } else { error = [NSError errorWithDomain:@"contactsmanager.notfound" code:NOTFOUND_ERROR userInfo:nil]; } dispatch_semaphore_signal(sema); } failure:^(NSError *blockerror) { error = blockerror; dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if (error) { SYNC_BLOCK_SAFE_RUN(failure, error); } else { SYNC_BLOCK_SAFE_RUN(success, recipient); } return; } - (void)lookupIdentifier:(NSString*)identifier success:(void (^)(NSSet *matchedIds))success failure:(void (^)(NSError *error))failure { [self contactIntersectionWithSet:[NSSet setWithObject:identifier] success:^(NSSet *matchedIds){ BLOCK_SAFE_RUN(success, matchedIds); } failure:^(NSError *error) { BLOCK_SAFE_RUN(failure, error); }]; } - (void)intersectContacts { [self updateSignalContactIntersectionWithSuccess:nil failure:^(NSError *error) { [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(intersectContacts) userInfo:nil repeats:NO]; }]; } - (void)updateSignalContactIntersectionWithSuccess:(void (^)())success failure:(void (^)(NSError *error))failure { NSArray *abContacts = [[[Environment getCurrent] contactsManager] allContacts]; NSMutableSet *abPhoneNumbers = [NSMutableSet set]; for (Contact *contact in abContacts) { for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { [abPhoneNumbers addObject:phoneNumber.toE164]; } } __block NSMutableSet *recipientIds = [NSMutableSet set]; [[TSStorageManager sharedManager].dbConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { NSArray *allRecipientKeys = [transaction allKeysInCollection:[SignalRecipient collection]]; [recipientIds addObjectsFromArray:allRecipientKeys]; }]; NSMutableSet *allContacts = [[abPhoneNumbers setByAddingObjectsFromSet:recipientIds] mutableCopy]; [self contactIntersectionWithSet:allContacts success:^(NSSet *matchedIds) { [recipientIds minusSet:matchedIds]; // Cleaning up unregistered identifiers [[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) { for (NSString *identifier in recipientIds) { SignalRecipient *recipient = [SignalRecipient fetchObjectWithUniqueID:identifier transaction:transaction]; [recipient removeWithTransaction:transaction]; } }]; BLOCK_SAFE_RUN(success); } failure:^(NSError *error) { BLOCK_SAFE_RUN(failure, error); }]; } - (void) contactIntersectionWithSet:(NSSet*)idSet success:(void (^)(NSSet *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] queueAuthenticatedRequest: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."); break; } [attributesForIdentifier setObject:dict forKey:identifier]; } } // Insert or update contact attributes [[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { for (NSString *identifier in attributesForIdentifier) { SignalRecipient *recipient = [SignalRecipient recipientWithTextSecureIdentifier:identifier withTransaction:transaction]; if (!recipient) { recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:identifier relay:nil supportsVoice:NO]; } NSDictionary *attributes = [attributesForIdentifier objectForKey:identifier]; NSString *relay = [attributes objectForKey:@"relay"]; if (relay) { recipient.relay = relay; } else { recipient.relay = nil; } BOOL supportsVoice = [[attributes objectForKey:@"voice"] boolValue]; if (supportsVoice) { recipient.supportsVoice = YES; } else { recipient.supportsVoice = NO; } [recipient saveWithTransaction:transaction]; } }]; BLOCK_SAFE_RUN(success, [NSSet setWithArray:attributesForIdentifier.allKeys]); } failure:^(NSURLSessionDataTask *task, NSError *error) { BLOCK_SAFE_RUN(failure, error); }]; }); } @end