Enforce assumptions
This commit is contained in:
parent
226ef41d58
commit
557a851dce
|
@ -4,8 +4,11 @@ import SessionUtilitiesKit
|
||||||
|
|
||||||
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
||||||
public enum OnionRequestAPI {
|
public enum OnionRequestAPI {
|
||||||
|
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
||||||
private static var pathFailureCount: [Path:UInt] = [:]
|
private static var pathFailureCount: [Path:UInt] = [:]
|
||||||
|
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
||||||
private static var snodeFailureCount: [Snode:UInt] = [:]
|
private static var snodeFailureCount: [Snode:UInt] = [:]
|
||||||
|
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
||||||
public static var guardSnodes: Set<Snode> = []
|
public static var guardSnodes: Set<Snode> = []
|
||||||
public static var paths: [Path] = [] // Not a set to ensure we consistently show the same path to the user
|
public static var paths: [Path] = [] // Not a set to ensure we consistently show the same path to the user
|
||||||
|
|
||||||
|
@ -196,10 +199,16 @@ public enum OnionRequestAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func dropGuardSnode(_ snode: Snode) {
|
private static func dropGuardSnode(_ snode: Snode) {
|
||||||
|
#if DEBUG
|
||||||
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
|
#endif
|
||||||
guardSnodes = guardSnodes.filter { $0 != snode }
|
guardSnodes = guardSnodes.filter { $0 != snode }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func drop(_ snode: Snode) throws {
|
private static func drop(_ snode: Snode) throws {
|
||||||
|
#if DEBUG
|
||||||
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
|
#endif
|
||||||
// We repair the path here because we can do it sync. In the case where we drop a whole
|
// We repair the path here because we can do it sync. In the case where we drop a whole
|
||||||
// path we leave the re-building up to getPath(excluding:) because re-building the path
|
// path we leave the re-building up to getPath(excluding:) because re-building the path
|
||||||
// in that case is async.
|
// in that case is async.
|
||||||
|
@ -224,6 +233,9 @@ public enum OnionRequestAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func drop(_ path: Path) {
|
private static func drop(_ path: Path) {
|
||||||
|
#if DEBUG
|
||||||
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
|
#endif
|
||||||
OnionRequestAPI.pathFailureCount[path] = 0
|
OnionRequestAPI.pathFailureCount[path] = 0
|
||||||
var paths = OnionRequestAPI.paths
|
var paths = OnionRequestAPI.paths
|
||||||
guard let pathIndex = paths.firstIndex(of: path) else { return }
|
guard let pathIndex = paths.firstIndex(of: path) else { return }
|
||||||
|
@ -392,7 +404,7 @@ public enum OnionRequestAPI {
|
||||||
seal.reject(error)
|
seal.reject(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
promise.catch2 { error in // Must be invoked on LokiAPI.workQueue
|
promise.catch2 { error in // Must be invoked on Threading.workQueue
|
||||||
guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { return }
|
guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { return }
|
||||||
let path = paths.first { $0.contains(guardSnode) }
|
let path = paths.first { $0.contains(guardSnode) }
|
||||||
func handleUnspecificError() {
|
func handleUnspecificError() {
|
||||||
|
|
|
@ -77,33 +77,35 @@ public final class SnodeAPI : NSObject {
|
||||||
]
|
]
|
||||||
SNLog("Populating snode pool using: \(target).")
|
SNLog("Populating snode pool using: \(target).")
|
||||||
let (promise, seal) = Promise<Snode>.pending()
|
let (promise, seal) = Promise<Snode>.pending()
|
||||||
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
Threading.workQueue.async {
|
||||||
HTTP.execute(.post, url, parameters: parameters, useSSLURLSession: true).map2 { json -> Snode in
|
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
||||||
guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw Error.randomSnodePoolUpdatingFailed }
|
HTTP.execute(.post, url, parameters: parameters, useSSLURLSession: true).map2 { json -> Snode in
|
||||||
snodePool = Set(rawSnodes.compactMap { rawSnode in
|
guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw Error.randomSnodePoolUpdatingFailed }
|
||||||
guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int,
|
snodePool = Set(rawSnodes.compactMap { rawSnode in
|
||||||
let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else {
|
guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int,
|
||||||
SNLog("Failed to parse target from: \(rawSnode).")
|
let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else {
|
||||||
return nil
|
SNLog("Failed to parse target from: \(rawSnode).")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey))
|
||||||
|
})
|
||||||
|
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||||
|
if !snodePool.isEmpty {
|
||||||
|
return snodePool.randomElement()!
|
||||||
|
} else {
|
||||||
|
throw Error.randomSnodePoolUpdatingFailed
|
||||||
}
|
}
|
||||||
return Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey))
|
|
||||||
})
|
|
||||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
|
||||||
if !snodePool.isEmpty {
|
|
||||||
return snodePool.randomElement()!
|
|
||||||
} else {
|
|
||||||
throw Error.randomSnodePoolUpdatingFailed
|
|
||||||
}
|
}
|
||||||
|
}.done2 { snode in
|
||||||
|
seal.fulfill(snode)
|
||||||
|
SNSnodeKitConfiguration.shared.storage.with { transaction in
|
||||||
|
SNLog("Persisting snode pool to database.")
|
||||||
|
SNSnodeKitConfiguration.shared.storage.setSnodePool(to: SnodeAPI.snodePool, using: transaction)
|
||||||
|
}
|
||||||
|
}.catch2 { error in
|
||||||
|
SNLog("Failed to contact seed node at: \(target).")
|
||||||
|
seal.reject(error)
|
||||||
}
|
}
|
||||||
}.done2 { snode in
|
|
||||||
seal.fulfill(snode)
|
|
||||||
SNSnodeKitConfiguration.shared.storage.with { transaction in
|
|
||||||
SNLog("Persisting snode pool to database.")
|
|
||||||
SNSnodeKitConfiguration.shared.storage.setSnodePool(to: SnodeAPI.snodePool, using: transaction)
|
|
||||||
}
|
|
||||||
}.catch2 { error in
|
|
||||||
SNLog("Failed to contact seed node at: \(target).")
|
|
||||||
seal.reject(error)
|
|
||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
} else {
|
} else {
|
||||||
|
@ -115,6 +117,9 @@ public final class SnodeAPI : NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static func dropSnodeFromSnodePool(_ snode: Snode) {
|
internal static func dropSnodeFromSnodePool(_ snode: Snode) {
|
||||||
|
#if DEBUG
|
||||||
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
|
#endif
|
||||||
var snodePool = SnodeAPI.snodePool
|
var snodePool = SnodeAPI.snodePool
|
||||||
snodePool.remove(snode)
|
snodePool.remove(snode)
|
||||||
SnodeAPI.snodePool = snodePool
|
SnodeAPI.snodePool = snodePool
|
||||||
|
@ -132,6 +137,9 @@ public final class SnodeAPI : NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) {
|
public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) {
|
||||||
|
#if DEBUG
|
||||||
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
|
#endif
|
||||||
let swarm = SnodeAPI.swarmCache[publicKey]
|
let swarm = SnodeAPI.swarmCache[publicKey]
|
||||||
if var swarm = swarm, let index = swarm.firstIndex(of: snode) {
|
if var swarm = swarm, let index = swarm.firstIndex(of: snode) {
|
||||||
swarm.remove(at: index)
|
swarm.remove(at: index)
|
||||||
|
@ -282,6 +290,9 @@ public final class SnodeAPI : NSObject {
|
||||||
/// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions.
|
/// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
internal static func handleError(withStatusCode statusCode: UInt, json: JSON?, forSnode snode: Snode, associatedWith publicKey: String? = nil) -> Error? {
|
internal static func handleError(withStatusCode statusCode: UInt, json: JSON?, forSnode snode: Snode, associatedWith publicKey: String? = nil) -> Error? {
|
||||||
|
#if DEBUG
|
||||||
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
|
#endif
|
||||||
func handleBadSnode() {
|
func handleBadSnode() {
|
||||||
let oldFailureCount = SnodeAPI.snodeFailureCount[snode] ?? 0
|
let oldFailureCount = SnodeAPI.snodeFailureCount[snode] ?? 0
|
||||||
let newFailureCount = oldFailureCount + 1
|
let newFailureCount = oldFailureCount + 1
|
||||||
|
|
Loading…
Reference in New Issue