Handle incorrect clock setting

This commit is contained in:
Niels Andriesse 2021-07-23 13:42:13 +10:00
parent ac730e00b4
commit 8a29469eb5
3 changed files with 23 additions and 17 deletions

View File

@ -200,7 +200,8 @@ public final class MessageSender : NSObject {
} }
// Send the result // Send the result
let base64EncodedData = wrappedMessage.base64EncodedString() 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 SnodeAPI.sendMessage(snodeMessage).done(on: DispatchQueue.global(qos: .userInitiated)) { promises in
var isSuccess = false var isSuccess = false
let promiseCount = promises.count let promiseCount = promises.count

View File

@ -310,7 +310,7 @@ public enum OnionRequestAPI {
} }
/// Sends an onion request to `server`. Builds new paths as needed. /// 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<JSON> { public static func sendOnionRequest(_ request: NSURLRequest, to server: String, target: String = "/loki/v3/lsrpc", using x25519PublicKey: String) -> Promise<JSON> {
var rawHeaders = request.allHTTPHeaderFields ?? [:] var rawHeaders = request.allHTTPHeaderFields ?? [:]
rawHeaders.removeValue(forKey: "User-Agent") rawHeaders.removeValue(forKey: "User-Agent")
var headers: JSON = rawHeaders.mapValues { value in var headers: JSON = rawHeaders.mapValues { value in
@ -352,14 +352,14 @@ public enum OnionRequestAPI {
"headers" : headers "headers" : headers
] ]
let destination = Destination.server(host: host, target: target, x25519PublicKey: x25519PublicKey, scheme: scheme, port: port) 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 promise.catch2 { error in
SNLog("Couldn't reach server: \(url) due to error: \(error).") SNLog("Couldn't reach server: \(url) due to error: \(error).")
} }
return promise return promise
} }
public static func sendOnionRequest(with payload: JSON, to destination: Destination, isJSONRequired: Bool = true) -> Promise<JSON> { public static func sendOnionRequest(with payload: JSON, to destination: Destination) -> Promise<JSON> {
let (promise, seal) = Promise<JSON>.pending() let (promise, seal) = Promise<JSON>.pending()
var guardSnode: Snode? var guardSnode: Snode?
Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths` 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) } let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= AESGCM.ivSize else { return seal.reject(HTTP.Error.invalidJSON) }
do { do {
let data = try AESGCM.decrypt(ivAndCiphertext, with: destinationSymmetricKey) 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, 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) } 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 if statusCode == 406 { // Clock out of sync
SNLog("The user's clock is out of sync with the service node network.") SNLog("The user's clock is out of sync with the service node network.")
seal.reject(SnodeAPI.Error.clockOutOfSync) seal.reject(SnodeAPI.Error.clockOutOfSync)
} else if let bodyAsString = json["body"] as? String { } 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 guard let bodyAsData = bodyAsString.data(using: .utf8),
// always go to the next clause. let body = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) }
let body: JSON if let timestamp = body["t"] as? Int64 {
if !isJSONRequired { let offset = timestamp - Int64(NSDate.millisecondTimestamp())
body = [ "result" : bodyAsString ] if abs(offset) > 20 * 1000 { // If we're off by more than 20 seconds
} else { SnodeAPI.clockOffset = offset
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 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) seal.fulfill(body)
} else { } 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) seal.fulfill(json)
} }
} catch { } catch {

View File

@ -13,6 +13,11 @@ public final class SnodeAPI : NSObject {
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
internal static var snodePool: Set<Snode> = [] internal static var snodePool: Set<Snode> = []
/// 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. /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var swarmCache: [String:Set<Snode>] = [:] public static var swarmCache: [String:Set<Snode>] = [:]