mirror of https://github.com/oxen-io/session-ios
Merge branch 'mkirk/batch-contact-intersections'
commit
0513077d29
@ -1 +1 @@
|
|||||||
Subproject commit 2bace50e8e41e281a9ddd525bc4b8a97446301ca
|
Subproject commit e9badd4baacc97c7159f7f8ffd2a51f4581fb869
|
@ -0,0 +1,281 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objc(OWSContactDiscoveryOperation)
|
||||||
|
class ContactDiscoveryOperation: OWSOperation, LegacyContactDiscoveryBatchOperationDelegate {
|
||||||
|
|
||||||
|
let batchSize = 2048
|
||||||
|
let recipientIdsToLookup: [String]
|
||||||
|
|
||||||
|
@objc
|
||||||
|
var registeredRecipientIds: Set<String>
|
||||||
|
|
||||||
|
@objc
|
||||||
|
required init(recipientIdsToLookup: [String]) {
|
||||||
|
self.recipientIdsToLookup = recipientIdsToLookup
|
||||||
|
self.registeredRecipientIds = Set()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
Logger.debug("\(logTag) in \(#function) with recipientIdsToLookup: \(recipientIdsToLookup.count)")
|
||||||
|
for batchIds in recipientIdsToLookup.chunked(by: batchSize) {
|
||||||
|
let batchOperation = LegacyContactDiscoveryBatchOperation(recipientIdsToLookup: batchIds)
|
||||||
|
batchOperation.delegate = self
|
||||||
|
self.addDependency(batchOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Mandatory overrides
|
||||||
|
|
||||||
|
// Called every retry, this is where the bulk of the operation's work should go.
|
||||||
|
override func run() {
|
||||||
|
Logger.debug("\(logTag) in \(#function)")
|
||||||
|
|
||||||
|
for dependency in self.dependencies {
|
||||||
|
guard let batchOperation = dependency as? LegacyContactDiscoveryBatchOperation else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) unexpected dependency: \(dependency)")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
self.registeredRecipientIds.formUnion(batchOperation.registeredRecipientIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reportSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: LegacyContactDiscoveryBatchOperationDelegate
|
||||||
|
func contactDiscoverBatchOperation(_ contactDiscoverBatchOperation: LegacyContactDiscoveryBatchOperation, didFailWithError error: Error) {
|
||||||
|
Logger.debug("\(logTag) in \(#function) canceling self and all dependencies.")
|
||||||
|
|
||||||
|
self.dependencies.forEach { $0.cancel() }
|
||||||
|
self.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol LegacyContactDiscoveryBatchOperationDelegate: class {
|
||||||
|
func contactDiscoverBatchOperation(_ contactDiscoverBatchOperation: LegacyContactDiscoveryBatchOperation, didFailWithError error: Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
class LegacyContactDiscoveryBatchOperation: OWSOperation {
|
||||||
|
|
||||||
|
var registeredRecipientIds: Set<String>
|
||||||
|
weak var delegate: LegacyContactDiscoveryBatchOperationDelegate?
|
||||||
|
|
||||||
|
private let recipientIdsToLookup: [String]
|
||||||
|
private var networkManager: TSNetworkManager {
|
||||||
|
return TSNetworkManager.shared()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Initializers
|
||||||
|
|
||||||
|
required init(recipientIdsToLookup: [String]) {
|
||||||
|
self.recipientIdsToLookup = recipientIdsToLookup
|
||||||
|
self.registeredRecipientIds = Set()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
Logger.debug("\(logTag) in \(#function) with recipientIdsToLookup: \(recipientIdsToLookup.count)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: OWSOperation Overrides
|
||||||
|
|
||||||
|
// Called every retry, this is where the bulk of the operation's work should go.
|
||||||
|
override func run() {
|
||||||
|
Logger.debug("\(logTag) in \(#function)")
|
||||||
|
|
||||||
|
guard !isCancelled else {
|
||||||
|
Logger.info("\(logTag) in \(#function) no work to do, since we were canceled")
|
||||||
|
self.reportCancelled()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var phoneNumbersByHashes: [String: String] = [:]
|
||||||
|
|
||||||
|
for recipientId in recipientIdsToLookup {
|
||||||
|
let hash = Cryptography.truncatedSHA1Base64EncodedWithoutPadding(recipientId)
|
||||||
|
assert(phoneNumbersByHashes[hash] == nil)
|
||||||
|
phoneNumbersByHashes[hash] = recipientId
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashes: [String] = Array(phoneNumbersByHashes.keys)
|
||||||
|
|
||||||
|
let request = OWSRequestFactory.contactsIntersectionRequest(withHashesArray: hashes)
|
||||||
|
|
||||||
|
self.networkManager.makeRequest(request,
|
||||||
|
success: { (task, responseDict) in
|
||||||
|
do {
|
||||||
|
self.registeredRecipientIds = try self.parse(response: responseDict, phoneNumbersByHashes: phoneNumbersByHashes)
|
||||||
|
self.reportSuccess()
|
||||||
|
} catch {
|
||||||
|
self.reportError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
failure: { (task, error) in
|
||||||
|
guard let response = task.response as? HTTPURLResponse else {
|
||||||
|
let responseError: NSError = OWSErrorMakeUnableToProcessServerResponseError() as NSError
|
||||||
|
responseError.isRetryable = true
|
||||||
|
self.reportError(responseError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard response.statusCode != 413 else {
|
||||||
|
let rateLimitError = OWSErrorWithCodeDescription(OWSErrorCode.contactsUpdaterRateLimit, "Contacts Intersection Rate Limit")
|
||||||
|
self.reportError(rateLimitError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reportError(error)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called at most one time.
|
||||||
|
override func didSucceed() {
|
||||||
|
// Compare against new CDS service
|
||||||
|
let newCDSBatchOperation = CDSBatchOperation(recipientIdsToLookup: self.recipientIdsToLookup)
|
||||||
|
let cdsFeedbackOperation = CDSFeedbackOperation(legacyRegisteredRecipientIds: self.registeredRecipientIds)
|
||||||
|
cdsFeedbackOperation.addDependency(newCDSBatchOperation)
|
||||||
|
|
||||||
|
CDSFeedbackOperation.operationQueue.addOperations([newCDSBatchOperation, cdsFeedbackOperation], waitUntilFinished: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called at most one time.
|
||||||
|
override func didFail(error: Error) {
|
||||||
|
self.delegate?.contactDiscoverBatchOperation(self, didFailWithError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Private Helpers
|
||||||
|
|
||||||
|
private func parse(response: Any?, phoneNumbersByHashes: [String: String]) throws -> Set<String> {
|
||||||
|
|
||||||
|
guard let responseDict = response as? [String: AnyObject] else {
|
||||||
|
let responseError: NSError = OWSErrorMakeUnableToProcessServerResponseError() as NSError
|
||||||
|
responseError.isRetryable = true
|
||||||
|
|
||||||
|
throw responseError
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let contactDicts = responseDict["contacts"] as? [[String: AnyObject]] else {
|
||||||
|
let responseError: NSError = OWSErrorMakeUnableToProcessServerResponseError() as NSError
|
||||||
|
responseError.isRetryable = true
|
||||||
|
|
||||||
|
throw responseError
|
||||||
|
}
|
||||||
|
|
||||||
|
var registeredRecipientIds: Set<String> = Set()
|
||||||
|
|
||||||
|
for contactDict in contactDicts {
|
||||||
|
guard let hash = contactDict["token"] as? String, hash.count > 0 else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) hash was unexpectedly nil")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let recipientId = phoneNumbersByHashes[hash], recipientId.count > 0 else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) recipientId was unexpectedly nil")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard recipientIdsToLookup.contains(recipientId) else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) unexpected recipientId")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
registeredRecipientIds.insert(recipientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return registeredRecipientIds
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSBatchOperation: OWSOperation {
|
||||||
|
|
||||||
|
private let recipientIdsToLookup: [String]
|
||||||
|
var registeredRecipientIds: Set<String>
|
||||||
|
|
||||||
|
// MARK: Initializers
|
||||||
|
|
||||||
|
required init(recipientIdsToLookup: [String]) {
|
||||||
|
self.recipientIdsToLookup = recipientIdsToLookup
|
||||||
|
self.registeredRecipientIds = Set()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
Logger.debug("\(logTag) in \(#function) with recipientIdsToLookup: \(recipientIdsToLookup.count)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: OWSOperationOverrides
|
||||||
|
|
||||||
|
// Called every retry, this is where the bulk of the operation's work should go.
|
||||||
|
override func run() {
|
||||||
|
Logger.debug("\(logTag) in \(#function)")
|
||||||
|
|
||||||
|
Logger.debug("\(logTag) in \(#function) FAKING intersection (TODO)")
|
||||||
|
self.registeredRecipientIds = Set(self.recipientIdsToLookup)
|
||||||
|
self.reportSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSFeedbackOperation: OWSOperation {
|
||||||
|
|
||||||
|
static let operationQueue: OperationQueue = {
|
||||||
|
let queue = OperationQueue()
|
||||||
|
queue.maxConcurrentOperationCount = 1
|
||||||
|
|
||||||
|
return queue
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let legacyRegisteredRecipientIds: Set<String>
|
||||||
|
|
||||||
|
// MARK: Initializers
|
||||||
|
|
||||||
|
required init(legacyRegisteredRecipientIds: Set<String>) {
|
||||||
|
self.legacyRegisteredRecipientIds = legacyRegisteredRecipientIds
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
Logger.debug("\(logTag) in \(#function)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: OWSOperation Overrides
|
||||||
|
|
||||||
|
// Called every retry, this is where the bulk of the operation's work should go.
|
||||||
|
override func run() {
|
||||||
|
guard let cdsOperation = dependencies.first as? CDSBatchOperation else {
|
||||||
|
let error = OWSErrorMakeAssertionError("\(self.logTag) in \(#function) cdsOperation was unexpectedly nil")
|
||||||
|
self.reportError(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cdsRegisteredRecipientIds = cdsOperation.registeredRecipientIds
|
||||||
|
|
||||||
|
if cdsRegisteredRecipientIds == legacyRegisteredRecipientIds {
|
||||||
|
Logger.debug("\(logTag) in \(#function) TODO: PUT /v1/directory/feedback/ok")
|
||||||
|
} else {
|
||||||
|
Logger.debug("\(logTag) in \(#function) TODO: PUT /v1/directory/feedback/mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reportSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didFail(error: Error) {
|
||||||
|
// dependency failed.
|
||||||
|
// Depending on error, PUT one of:
|
||||||
|
// /v1/directory/feedback/server-error:
|
||||||
|
// /v1/directory/feedback/client-error:
|
||||||
|
// /v1/directory/feedback/attestation-error:
|
||||||
|
// /v1/directory/feedback/unexpected-error:
|
||||||
|
Logger.debug("\(logTag) in \(#function) TODO: PUT /v1/directory/feedback/*-error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array {
|
||||||
|
func chunked(by chunkSize: Int) -> [[Element]] {
|
||||||
|
return stride(from: 0, to: self.count, by: chunkSize).map {
|
||||||
|
Array(self[$0..<Swift.min($0 + chunkSize, self.count)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// ObjC classes from which Swift classes inherit must be included in this framework header.
|
||||||
|
#import "OWSOperation.h"
|
Loading…
Reference in New Issue