From 99c473ee345234c6297ee6d5517d075cc7695abb Mon Sep 17 00:00:00 2001 From: gmbnt Date: Wed, 25 Mar 2020 15:52:25 +1100 Subject: [PATCH] Use only updated snodes for file server proxying --- .../src/Loki/API/LokiAPI+SwarmAPI.swift | 39 +++++++++++++++++-- SignalServiceKit/src/Loki/API/LokiAPI.swift | 1 + .../src/Loki/API/LokiFileServerProxy.swift | 2 +- .../src/Network/OWSSignalService.m | 3 ++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 50d363008..29e2febbb 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -1,14 +1,14 @@ import PromiseKit public extension LokiAPI { + private static var snodeVersion: [LokiAPITarget:String] = [:] /// Only ever accessed from `LokiAPI.errorHandlingQueue` to avoid race conditions. fileprivate static var failureCount: [LokiAPITarget:UInt] = [:] - + // MARK: Settings private static let minimumSnodeCount = 2 private static let targetSnodeCount = 3 - private static let maxRandomSnodePoolSize = 1024 fileprivate static let failureThreshold = 2 // MARK: Caching @@ -84,11 +84,44 @@ public extension LokiAPI { } } - // MARK: Public API internal static func getTargetSnodes(for hexEncodedPublicKey: String) -> Promise<[LokiAPITarget]> { // shuffled() uses the system's default random generator, which is cryptographically secure return getSwarm(for: hexEncodedPublicKey).map { Array($0.shuffled().prefix(targetSnodeCount)) } } + + internal static func getFileServerProxy() -> Promise { + let (promise, seal) = Promise.pending() + func getVersion(for snode: LokiAPITarget) -> Promise { + let url = URL(string: "\(snode.address):\(snode.port)/get_stats/v1")! + let request = TSRequest(url: url) + if let version = snodeVersion[snode] { + return Promise { $0.fulfill(version) } + } else { + return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map(on: DispatchQueue.global()) { intermediate in + let rawResponse = intermediate.responseObject + guard let json = rawResponse as? JSON, let version = json["version"] as? String else { throw LokiAPIError.missingSnodeVersion } + snodeVersion[snode] = version + return version + } + } + } + getRandomSnode().done(on: DispatchQueue.global()) { snode in + getVersion(for: snode).then(on: DispatchQueue.global()) { version -> Promise in + if version >= "2.0.2" { + print("[Loki] Using file server proxy with version number \(version).") + return Promise { $0.fulfill(snode) } + } else { + print("[Loki] Rejecting file server proxy with version number \(version).") + return getFileServerProxy() + } + }.recover(on: DispatchQueue.global()) { error in + return getFileServerProxy() + } + }.catch(on: DispatchQueue.global()) { error in + seal.reject(error) + } + return promise + } // MARK: Parsing private static func parseTargets(from rawResponse: Any) -> [LokiAPITarget] { diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 020979087..ea60e5440 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -47,6 +47,7 @@ public final class LokiAPI : NSObject { @objc public static let messageConversionFailed = LokiAPIError(domain: "LokiAPIErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Failed to construct message." ]) @objc public static let clockOutOfSync = LokiAPIError(domain: "LokiAPIErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Your clock is out of sync with the service node network." ]) @objc public static let randomSnodePoolUpdatingFailed = LokiAPIError(domain: "LokiAPIErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Failed to update random service node pool." ]) + @objc public static let missingSnodeVersion = LokiAPIError(domain: "LokiAPIErrorDomain", code: 5, userInfo: [ NSLocalizedDescriptionKey : "Missing service node version." ]) } @objc(LKDestination) diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift b/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift index a291edcf0..76463b711 100644 --- a/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift +++ b/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift @@ -50,7 +50,7 @@ internal class LokiFileServerProxy : LokiHTTPClient { DispatchQueue.global().async { let uncheckedSymmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: LokiFileServerProxy.fileServerPublicKey, privateKey: keyPair.privateKey) guard let symmetricKey = uncheckedSymmetricKey else { return seal.reject(Error.symmetricKeyGenerationFailed) } - LokiAPI.getRandomSnode().then(on: DispatchQueue.global()) { proxy -> Promise in + LokiAPI.getFileServerProxy().then(on: DispatchQueue.global()) { proxy -> Promise in let url = "\(proxy.address):\(proxy.port)/file_proxy" guard let urlAsString = request.url?.absoluteString, let serverURLEndIndex = urlAsString.range(of: server)?.upperBound, serverURLEndIndex < urlAsString.endIndex else { throw Error.endpointParsingFailed } diff --git a/SignalServiceKit/src/Network/OWSSignalService.m b/SignalServiceKit/src/Network/OWSSignalService.m index 57498b2fc..4cd89785d 100644 --- a/SignalServiceKit/src/Network/OWSSignalService.m +++ b/SignalServiceKit/src/Network/OWSSignalService.m @@ -194,6 +194,9 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange = sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; sessionManager.requestSerializer.HTTPShouldHandleCookies = NO; sessionManager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments]; + NSMutableSet *acceptableContentTypes = sessionManager.responseSerializer.acceptableContentTypes.mutableCopy; + [acceptableContentTypes addObject:@"text/plain"]; + sessionManager.responseSerializer.acceptableContentTypes = acceptableContentTypes; return sessionManager; }