From 8a29469eb515b52198d397c25a22f15fa507aabd Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 23 Jul 2021 13:42:13 +1000 Subject: [PATCH] Handle incorrect clock setting --- .../Sending & Receiving/MessageSender.swift | 3 +- SessionSnodeKit/OnionRequestAPI.swift | 32 +++++++++---------- SessionSnodeKit/SnodeAPI.swift | 5 +++ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 587587108..456ba6e23 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -200,7 +200,8 @@ public final class MessageSender : NSObject { } // Send the result let base64EncodedData = wrappedMessage.base64EncodedString() - let snodeMessage = SnodeMessage(recipient: message.recipient!, data: base64EncodedData, ttl: message.ttl, timestamp: message.sentTimestamp!) + let timestamp = UInt64(Int64(message.sentTimestamp!) + SnodeAPI.clockOffset) + let snodeMessage = SnodeMessage(recipient: message.recipient!, data: base64EncodedData, ttl: message.ttl, timestamp: timestamp) SnodeAPI.sendMessage(snodeMessage).done(on: DispatchQueue.global(qos: .userInitiated)) { promises in var isSuccess = false let promiseCount = promises.count diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index dc04ae57e..771a6fb92 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -310,7 +310,7 @@ public enum OnionRequestAPI { } /// Sends an onion request to `server`. Builds new paths as needed. - public static func sendOnionRequest(_ request: NSURLRequest, to server: String, target: String = "/loki/v3/lsrpc", using x25519PublicKey: String, isJSONRequired: Bool = true) -> Promise { + public static func sendOnionRequest(_ request: NSURLRequest, to server: String, target: String = "/loki/v3/lsrpc", using x25519PublicKey: String) -> Promise { var rawHeaders = request.allHTTPHeaderFields ?? [:] rawHeaders.removeValue(forKey: "User-Agent") var headers: JSON = rawHeaders.mapValues { value in @@ -352,14 +352,14 @@ public enum OnionRequestAPI { "headers" : headers ] let destination = Destination.server(host: host, target: target, x25519PublicKey: x25519PublicKey, scheme: scheme, port: port) - let promise = sendOnionRequest(with: payload, to: destination, isJSONRequired: isJSONRequired) + let promise = sendOnionRequest(with: payload, to: destination) promise.catch2 { error in SNLog("Couldn't reach server: \(url) due to error: \(error).") } return promise } - public static func sendOnionRequest(with payload: JSON, to destination: Destination, isJSONRequired: Bool = true) -> Promise { + public static func sendOnionRequest(with payload: JSON, to destination: Destination) -> Promise { let (promise, seal) = Promise.pending() var guardSnode: Snode? Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths` @@ -386,28 +386,28 @@ public enum OnionRequestAPI { let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= AESGCM.ivSize else { return seal.reject(HTTP.Error.invalidJSON) } do { let data = try AESGCM.decrypt(ivAndCiphertext, with: destinationSymmetricKey) - // The old open group server and file server implementations put the status code in the JSON under the "status" - // key, whereas the new implementations put it under the "status_code" key guard let json = try JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON, let statusCode = json["status_code"] as? Int ?? json["status"] as? Int else { return seal.reject(HTTP.Error.invalidJSON) } if statusCode == 406 { // Clock out of sync SNLog("The user's clock is out of sync with the service node network.") seal.reject(SnodeAPI.Error.clockOutOfSync) } else if let bodyAsString = json["body"] as? String { - // This clause is only used by the old open group and file server implementations. The new implementations will - // always go to the next clause. - let body: JSON - if !isJSONRequired { - body = [ "result" : bodyAsString ] - } else { - guard let bodyAsData = bodyAsString.data(using: .utf8), - let b = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) } - body = b + guard let bodyAsData = bodyAsString.data(using: .utf8), + let body = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) } + if let timestamp = body["t"] as? Int64 { + let offset = timestamp - Int64(NSDate.millisecondTimestamp()) + if abs(offset) > 20 * 1000 { // If we're off by more than 20 seconds + SnodeAPI.clockOffset = offset + } + } + guard 200...299 ~= statusCode else { + return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: body, destination: destination)) } - guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: body, destination: destination)) } seal.fulfill(body) } else { - guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: json, destination: destination)) } + guard 200...299 ~= statusCode else { + return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: json, destination: destination)) + } seal.fulfill(json) } } catch { diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 214bef8f9..50c12c59d 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -13,6 +13,11 @@ public final class SnodeAPI : NSObject { /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. internal static var snodePool: Set = [] + /// The offset between the user's clock and the Service Node's clock. Used in cases where the + /// user's clock is incorrect. + /// + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. + public static var clockOffset: Int64 = 0 /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. public static var swarmCache: [String:Set] = [:]