diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index f8656e996..5873d8114 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -29,6 +29,7 @@ public final class LokiAPI : NSObject { internal static let userHexEncodedPublicKey = getUserHexEncodedPublicKey() // MARK: Settings + private static let useOnionRequests = true private static let maxRetryCount: UInt = 4 private static let defaultTimeout: TimeInterval = 20 private static let longPollingTimeout: TimeInterval = 40 @@ -99,9 +100,14 @@ public final class LokiAPI : NSObject { let request = TSRequest(url: url, method: "POST", parameters: [ "method" : method.rawValue, "params" : parameters ]) if let headers = headers { request.allHTTPHeaderFields = headers } request.timeoutInterval = timeout ?? defaultTimeout - return LokiSnodeProxy(for: target).perform(request, withCompletionQueue: DispatchQueue.global()) - .handlingSnodeErrorsIfNeeded(for: target, associatedWith: hexEncodedPublicKey) - .recoveringNetworkErrorsIfNeeded() + if useOnionRequests { + return OnionRequestAPI.sendOnionRequest(invoking: method, on: target, with: parameters).map { $0 as Any } + } else { + return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()) + .map { $0.responseObject } + .handlingSnodeErrorsIfNeeded(for: target, associatedWith: hexEncodedPublicKey) + .recoveringNetworkErrorsIfNeeded() + } } internal static func getRawMessages(from target: LokiAPITarget, usingLongPolling useLongPolling: Bool) -> RawResponsePromise { diff --git a/SignalServiceKit/src/Loki/API/LokiSnodeProxy.swift b/SignalServiceKit/src/Loki/API/LokiSnodeProxy.swift deleted file mode 100644 index 1e4012a06..000000000 --- a/SignalServiceKit/src/Loki/API/LokiSnodeProxy.swift +++ /dev/null @@ -1,98 +0,0 @@ -import PromiseKit -import SignalMetadataKit - -internal class LokiSnodeProxy : LokiHTTPClient { - private let target: LokiAPITarget - private let keyPair = Curve25519.generateKeyPair() - - // MARK: Error - internal enum Error : LocalizedError { - case targetPublicKeySetMissing - case symmetricKeyGenerationFailed - case proxyResponseParsingFailed - case targetSnodeHTTPError(code: Int, message: Any?) - - internal var errorDescription: String? { - switch self { - case .targetPublicKeySetMissing: return "Missing target public key set." - case .symmetricKeyGenerationFailed: return "Couldn't generate symmetric key." - case .proxyResponseParsingFailed: return "Couldn't parse snode proxy response." - case .targetSnodeHTTPError(let httpStatusCode, let message): return "Target snode returned error \(httpStatusCode) with description: \(message ?? "no description provided.")." - } - } - } - - // MARK: Initialization - internal init(for target: LokiAPITarget) { - self.target = target - super.init() - } - - // MARK: Proxying - override internal func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> LokiAPI.RawResponsePromise { - guard let targetHexEncodedPublicKeySet = target.publicKeySet else { return Promise(error: Error.targetPublicKeySetMissing) } - let headers = getCanonicalHeaders(for: request) - return Promise { [target = self.target, keyPair = self.keyPair, httpSession = self.httpSession] seal in - DispatchQueue.global().async { - let uncheckedSymmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: Data(hex: targetHexEncodedPublicKeySet.x25519Key), privateKey: keyPair.privateKey) - guard let symmetricKey = uncheckedSymmetricKey else { return seal.reject(Error.symmetricKeyGenerationFailed) } - LokiAPI.getRandomSnode().then(on: DispatchQueue.global()) { proxy -> Promise in - let url = "\(proxy.address):\(proxy.port)/proxy" - let parametersAsData = try JSONSerialization.data(withJSONObject: request.parameters, options: []) - let proxyRequestParameters: JSON = [ - "method" : request.httpMethod, - "body" : String(bytes: parametersAsData, encoding: .utf8), - "headers" : headers - ] - let proxyRequestParametersAsData = try JSONSerialization.data(withJSONObject: proxyRequestParameters, options: []) - let ivAndCipherText = try DiffieHellman.encrypt(proxyRequestParametersAsData, using: symmetricKey) - let proxyRequestHeaders = [ - "X-Sender-Public-Key" : keyPair.publicKey.toHexString(), - "X-Target-Snode-Key" : targetHexEncodedPublicKeySet.ed25519Key - ] - let (promise, resolver) = LokiAPI.RawResponsePromise.pending() - let proxyRequest = AFHTTPRequestSerializer().request(withMethod: "POST", urlString: url, parameters: nil, error: nil) - proxyRequest.allHTTPHeaderFields = proxyRequestHeaders - proxyRequest.httpBody = ivAndCipherText - proxyRequest.timeoutInterval = request.timeoutInterval - var task: URLSessionDataTask! - task = httpSession.dataTask(with: proxyRequest as URLRequest) { response, result, error in - if let error = error { - let nmError = NetworkManagerError.taskError(task: task, underlyingError: error) - let nsError: NSError = nmError as NSError - nsError.isRetryable = false - resolver.reject(nsError) - } else { - resolver.fulfill(result) - } - } - task.resume() - return promise - }.map(on: DispatchQueue.global()) { rawResponse in - guard let responseAsData = rawResponse as? Data, let cipherText = Data(base64Encoded: responseAsData) else { - print("[Loki] Received a non-string encoded response.") - return rawResponse - } - let response = try DiffieHellman.decrypt(cipherText, using: symmetricKey) - let uncheckedJSON = try? JSONSerialization.jsonObject(with: response, options: .allowFragments) as? JSON - guard let json = uncheckedJSON, let statusCode = json["status"] as? Int else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) } - let isSuccess = (200...299) ~= statusCode - var body: Any? = nil - if let bodyAsString = json["body"] as? String { - body = bodyAsString - if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? JSON { - body = bodyAsJSON - } - } - guard isSuccess else { throw HTTPError.networkError(code: statusCode, response: body, underlyingError: Error.targetSnodeHTTPError(code: statusCode, message: body)) } - return body - }.done { rawResponse in - seal.fulfill(rawResponse) - }.catch { error in - print("[Loki] Proxy request failed with error: \(error.localizedDescription).") - seal.reject(HTTPError.from(error: error) ?? error) - } - } - } - } -} diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift index c4368afb7..1e8ae4e97 100644 --- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift +++ b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift @@ -18,7 +18,7 @@ internal enum OnionRequestAPI { // MARK: Settings private static let pathCount: UInt = 2 /// The number of snodes (including the guard snode) in a path. - private static let pathSize: UInt = 3 + private static let pathSize: UInt = 1 private static var guardSnodeCount: UInt { return pathCount } // One per path @@ -178,7 +178,7 @@ internal enum OnionRequestAPI { // MARK: Internal API /// Sends an onion request to `snode`. Builds new paths as needed. - internal static func invoke(_ method: LokiAPITarget.Method, on snode: LokiAPITarget, with parameters: JSON) -> Promise { + internal static func sendOnionRequest(invoking method: LokiAPITarget.Method, on snode: LokiAPITarget, with parameters: JSON) -> Promise { let (promise, seal) = Promise.pending() workQueue.async { let payload: JSON = [ "method" : method.rawValue, "params" : parameters ]