mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge pull request #296 from loki-project/onion-requests-2
Further Improved Path Maintenance
This commit is contained in:
commit
b72620cd66
|
@ -4157,7 +4157,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -4219,7 +4219,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -4273,7 +4273,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
|
@ -4343,7 +4343,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
|
@ -4405,7 +4405,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -4468,7 +4468,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -4669,7 +4669,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -4736,7 +4736,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 130;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
|
@ -16,10 +16,10 @@ final class PathStatusView : UIView {
|
|||
private func setUpViewHierarchy() {
|
||||
layer.cornerRadius = Values.pathStatusViewSize / 2
|
||||
layer.masksToBounds = false
|
||||
if OnionRequestAPI.paths.count < OnionRequestAPI.pathCount {
|
||||
if OnionRequestAPI.paths.isEmpty {
|
||||
OnionRequestAPI.paths = Storage.getOnionRequestPaths()
|
||||
}
|
||||
let color = (OnionRequestAPI.paths.count >= OnionRequestAPI.pathCount) ? Colors.accent : Colors.pathsBuilding
|
||||
let color = (!OnionRequestAPI.paths.isEmpty) ? Colors.accent : Colors.pathsBuilding
|
||||
setColor(to: color, isAnimated: false)
|
||||
}
|
||||
|
||||
|
|
|
@ -51,11 +51,11 @@ final class IP2Country {
|
|||
}
|
||||
|
||||
func populateCacheIfNeeded() -> Bool {
|
||||
if OnionRequestAPI.paths.count < OnionRequestAPI.pathCount {
|
||||
if OnionRequestAPI.paths.isEmpty {
|
||||
OnionRequestAPI.paths = Storage.getOnionRequestPaths()
|
||||
}
|
||||
let paths = OnionRequestAPI.paths
|
||||
guard paths.count >= OnionRequestAPI.pathCount else { return false }
|
||||
guard !paths.isEmpty else { return false }
|
||||
let pathToDisplay = paths.first!
|
||||
pathToDisplay.forEach { snode in
|
||||
let _ = self.cacheCountry(for: snode.ip) // Preload if needed
|
||||
|
|
|
@ -105,7 +105,7 @@ final class PathVC : BaseVC {
|
|||
|
||||
private func update() {
|
||||
pathStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
if OnionRequestAPI.paths.count >= OnionRequestAPI.pathCount {
|
||||
if !OnionRequestAPI.paths.isEmpty {
|
||||
let pathToDisplay = OnionRequestAPI.paths.first!
|
||||
let dotAnimationRepeatInterval = Double(pathToDisplay.count) + 2
|
||||
let snodeRows: [UIStackView] = pathToDisplay.enumerated().map { index, snode in
|
||||
|
|
|
@ -8,6 +8,7 @@ public class DotNetAPI : NSObject {
|
|||
|
||||
// MARK: Settings
|
||||
private static let attachmentType = "network.loki"
|
||||
private static let maxRetryCount: UInt = 4
|
||||
|
||||
// MARK: Error
|
||||
@objc(LKDotNetAPIError)
|
||||
|
@ -123,13 +124,15 @@ public class DotNetAPI : NSObject {
|
|||
}
|
||||
let serverPublicKeyPromise = FileServerAPI.server.contains(host) ? Promise.value(FileServerAPI.fileServerPublicKey)
|
||||
: PublicChatAPI.getOpenGroupServerPublicKey(for: host)
|
||||
return serverPublicKeyPromise.then2 { serverPublicKey in
|
||||
return OnionRequestAPI.sendOnionRequest(request, to: host, using: serverPublicKey, isJSONRequired: false).map2 { json in
|
||||
guard let body = json["body"] as? JSON, let data = body["data"] as? [UInt8] else {
|
||||
print("[Loki] Couldn't parse attachment from: \(json).")
|
||||
throw DotNetAPIError.parsingFailed
|
||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: SnodeAPI.workQueue) {
|
||||
serverPublicKeyPromise.then2 { serverPublicKey in
|
||||
return OnionRequestAPI.sendOnionRequest(request, to: host, using: serverPublicKey, isJSONRequired: false).map2 { json in
|
||||
guard let body = json["body"] as? JSON, let data = body["data"] as? [UInt8] else {
|
||||
print("[Loki] Couldn't parse attachment from: \(json).")
|
||||
throw DotNetAPIError.parsingFailed
|
||||
}
|
||||
return Data(data)
|
||||
}
|
||||
return Data(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,23 @@ import PromiseKit
|
|||
|
||||
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
||||
public enum OnionRequestAPI {
|
||||
private static var pathFailureCount: [Path:UInt] = [:]
|
||||
private static var snodeFailureCount: [Snode:UInt] = [:]
|
||||
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
|
||||
|
||||
// MARK: Settings
|
||||
/// The number of snodes (including the guard snode) in a path.
|
||||
private static let pathSize: UInt = 3
|
||||
/// The number of times a path can fail before it's replaced.
|
||||
private static let pathFailureThreshold: UInt = 3
|
||||
/// The number of times a snode can fail before it's replaced.
|
||||
private static let snodeFailureThreshold: UInt = 3
|
||||
/// The number of paths to maintain.
|
||||
public static let targetPathCount: UInt = 2
|
||||
|
||||
public static let pathCount: UInt = 2
|
||||
|
||||
private static var guardSnodeCount: UInt { return pathCount } // One per path
|
||||
/// The number of guard snodes required to maintain `targetPathCount` paths.
|
||||
private static var targetGuardSnodeCount: UInt { return targetPathCount } // One per path
|
||||
|
||||
// MARK: Destination
|
||||
internal enum Destination {
|
||||
|
@ -69,16 +76,17 @@ public enum OnionRequestAPI {
|
|||
return promise
|
||||
}
|
||||
|
||||
/// Finds `guardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes`
|
||||
/// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes`
|
||||
/// if not enough (reliable) snodes are available.
|
||||
private static func getGuardSnodes() -> Promise<Set<Snode>> {
|
||||
if guardSnodes.count >= guardSnodeCount {
|
||||
private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> Promise<Set<Snode>> {
|
||||
if guardSnodes.count >= targetGuardSnodeCount {
|
||||
return Promise<Set<Snode>> { $0.fulfill(guardSnodes) }
|
||||
} else {
|
||||
print("[Loki] [Onion Request API] Populating guard snode cache.")
|
||||
return SnodeAPI.getRandomSnode().then2 { _ -> Promise<Set<Snode>> in // Just used to populate the snode pool
|
||||
var unusedSnodes = SnodeAPI.snodePool // Sync on LokiAPI.workQueue
|
||||
guard unusedSnodes.count >= guardSnodeCount else { throw Error.insufficientSnodes }
|
||||
var unusedSnodes = SnodeAPI.snodePool.subtracting(reusableGuardSnodes) // Sync on LokiAPI.workQueue
|
||||
let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count)
|
||||
guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { throw Error.insufficientSnodes }
|
||||
func getGuardSnode() -> Promise<Snode> {
|
||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||
guard let candidate = unusedSnodes.randomElement() else { return Promise<Snode> { $0.reject(Error.insufficientSnodes) } }
|
||||
|
@ -86,12 +94,12 @@ public enum OnionRequestAPI {
|
|||
print("[Loki] [Onion Request API] Testing guard snode: \(candidate).")
|
||||
// Loop until a reliable guard snode is found
|
||||
return testSnode(candidate).map2 { candidate }.recover(on: DispatchQueue.main) { _ in
|
||||
withDelay(0.25, completionQueue: SnodeAPI.workQueue) { getGuardSnode() }
|
||||
withDelay(0.1, completionQueue: SnodeAPI.workQueue) { getGuardSnode() }
|
||||
}
|
||||
}
|
||||
let promises = (0..<guardSnodeCount).map { _ in getGuardSnode() }
|
||||
let promises = (0..<(targetGuardSnodeCount - reusableGuardSnodeCount)).map { _ in getGuardSnode() }
|
||||
return when(fulfilled: promises).map2 { guardSnodes in
|
||||
let guardSnodesAsSet = Set(guardSnodes)
|
||||
let guardSnodesAsSet = Set(guardSnodes + reusableGuardSnodes)
|
||||
OnionRequestAPI.guardSnodes = guardSnodesAsSet
|
||||
return guardSnodesAsSet
|
||||
}
|
||||
|
@ -99,20 +107,22 @@ public enum OnionRequestAPI {
|
|||
}
|
||||
}
|
||||
|
||||
/// Builds and returns `pathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
||||
/// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
||||
/// if not enough (reliable) snodes are available.
|
||||
private static func buildPaths() -> Promise<[Path]> {
|
||||
private static func buildPaths(reusing reusablePaths: [Path]) -> Promise<[Path]> {
|
||||
print("[Loki] [Onion Request API] Building onion request paths.")
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .buildingPaths, object: nil)
|
||||
}
|
||||
return SnodeAPI.getRandomSnode().then2 { _ -> Promise<[Path]> in // Just used to populate the snode pool
|
||||
return getGuardSnodes().map2 { guardSnodes -> [Path] in
|
||||
var unusedSnodes = SnodeAPI.snodePool.subtracting(guardSnodes)
|
||||
let pathSnodeCount = guardSnodeCount * pathSize - guardSnodeCount
|
||||
let reusableGuardSnodes = reusablePaths.map { $0[0] }
|
||||
return getGuardSnodes(reusing: reusableGuardSnodes).map2 { guardSnodes -> [Path] in
|
||||
var unusedSnodes = SnodeAPI.snodePool.subtracting(guardSnodes).subtracting(reusablePaths.flatMap { $0 })
|
||||
let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count)
|
||||
let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount)
|
||||
guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes }
|
||||
// Don't test path snodes as this would reveal the user's IP to them
|
||||
return guardSnodes.map { guardSnode in
|
||||
return guardSnodes.subtracting(reusableGuardSnodes).map { guardSnode in
|
||||
let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in
|
||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||
let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above
|
||||
|
@ -123,7 +133,7 @@ public enum OnionRequestAPI {
|
|||
return result
|
||||
}
|
||||
}.map2 { paths in
|
||||
OnionRequestAPI.paths = paths
|
||||
OnionRequestAPI.paths = paths + reusablePaths
|
||||
try! Storage.writeSync { transaction in
|
||||
print("[Loki] Persisting onion request paths to database.")
|
||||
Storage.setOnionRequestPaths(paths, using: transaction)
|
||||
|
@ -140,24 +150,39 @@ public enum OnionRequestAPI {
|
|||
private static func getPath(excluding snode: Snode?) -> Promise<Path> {
|
||||
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
|
||||
var paths = OnionRequestAPI.paths
|
||||
if paths.count < pathCount {
|
||||
if paths.isEmpty {
|
||||
paths = Storage.getOnionRequestPaths()
|
||||
OnionRequestAPI.paths = paths
|
||||
if paths.count >= pathCount {
|
||||
guardSnodes.formUnion([ paths[0][0], paths[1][0] ])
|
||||
if !paths.isEmpty {
|
||||
guardSnodes.formUnion([ paths[0][0] ])
|
||||
if paths.count >= 2 {
|
||||
guardSnodes.formUnion([ paths[1][0] ])
|
||||
}
|
||||
}
|
||||
}
|
||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||
if paths.count >= pathCount {
|
||||
return Promise<Path> { seal in
|
||||
if let snode = snode {
|
||||
seal.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!)
|
||||
if paths.count >= targetPathCount {
|
||||
if let snode = snode {
|
||||
return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) }
|
||||
} else {
|
||||
return Promise { $0.fulfill(paths.randomElement()!) }
|
||||
}
|
||||
} else if !paths.isEmpty {
|
||||
if let snode = snode {
|
||||
if let path = paths.first(where: { !$0.contains(snode) }) {
|
||||
buildPaths(reusing: paths).retainUntilComplete() // Re-build paths in the background
|
||||
return Promise { $0.fulfill(path) }
|
||||
} else {
|
||||
seal.fulfill(paths.randomElement()!)
|
||||
return buildPaths(reusing: paths).map2 { paths in
|
||||
return paths.filter { !$0.contains(snode) }.randomElement()!
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buildPaths(reusing: paths).retainUntilComplete() // Re-build paths in the background
|
||||
return Promise { $0.fulfill(paths.randomElement()!) }
|
||||
}
|
||||
} else {
|
||||
return buildPaths().map2 { paths in
|
||||
return buildPaths(reusing: []).map2 { paths in
|
||||
if let snode = snode {
|
||||
return paths.filter { !$0.contains(snode) }.randomElement()!
|
||||
} else {
|
||||
|
@ -172,6 +197,10 @@ public enum OnionRequestAPI {
|
|||
}
|
||||
|
||||
private static func drop(_ snode: Snode) throws {
|
||||
// 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
|
||||
// in that case is async.
|
||||
OnionRequestAPI.snodeFailureCount[snode] = 0
|
||||
var oldPaths = paths
|
||||
guard let pathIndex = oldPaths.firstIndex(where: { $0.contains(snode) }) else { return }
|
||||
var path = oldPaths[pathIndex]
|
||||
|
@ -191,10 +220,19 @@ public enum OnionRequestAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private static func dropAllPaths() {
|
||||
paths.removeAll()
|
||||
private static func drop(_ path: Path) {
|
||||
OnionRequestAPI.pathFailureCount[path] = 0
|
||||
var paths = OnionRequestAPI.paths
|
||||
guard let pathIndex = paths.firstIndex(of: path) else { return }
|
||||
paths.remove(at: pathIndex)
|
||||
OnionRequestAPI.paths = paths
|
||||
try! Storage.writeSync { transaction in
|
||||
Storage.clearOnionRequestPaths(using: transaction)
|
||||
if !paths.isEmpty {
|
||||
print("[Loki] Persisting onion request paths to database.")
|
||||
Storage.setOnionRequestPaths(paths, using: transaction)
|
||||
} else {
|
||||
Storage.clearOnionRequestPaths(using: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,10 +323,11 @@ public enum OnionRequestAPI {
|
|||
"headers" : headers
|
||||
]
|
||||
let destination = Destination.server(host: host, x25519PublicKey: x25519PublicKey)
|
||||
return sendOnionRequest(with: payload, to: destination, isJSONRequired: isJSONRequired).recover2 { error -> Promise<JSON> in
|
||||
let promise = sendOnionRequest(with: payload, to: destination, isJSONRequired: isJSONRequired)
|
||||
promise.catch2 { error in
|
||||
print("[Loki] [Onion Request API] Couldn't reach server: \(url) due to error: \(error).")
|
||||
throw error
|
||||
}
|
||||
return promise
|
||||
}
|
||||
|
||||
internal static func sendOnionRequest(with payload: JSON, to destination: Destination, isJSONRequired: Bool = true) -> Promise<JSON> {
|
||||
|
@ -352,25 +391,40 @@ public enum OnionRequestAPI {
|
|||
guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { return }
|
||||
let path = paths.first { $0.contains(guardSnode) }
|
||||
func handleUnspecificError() {
|
||||
path?.forEach { snode in
|
||||
SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw
|
||||
guard let path = path else { return }
|
||||
var pathFailureCount = OnionRequestAPI.pathFailureCount[path] ?? 0
|
||||
pathFailureCount += 1
|
||||
if pathFailureCount >= pathFailureThreshold {
|
||||
dropGuardSnode(guardSnode)
|
||||
path.forEach { snode in
|
||||
SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw
|
||||
}
|
||||
drop(path)
|
||||
} else {
|
||||
OnionRequestAPI.pathFailureCount[path] = pathFailureCount
|
||||
}
|
||||
dropAllPaths()
|
||||
dropGuardSnode(guardSnode)
|
||||
}
|
||||
let prefix = "Next node not found: "
|
||||
if let message = json?["result"] as? String, message.hasPrefix(prefix) {
|
||||
let ed25519PublicKey = message.substring(from: prefix.count)
|
||||
if let path = path, let snode = path.first(where: { $0.publicKeySet?.ed25519Key == ed25519PublicKey }) {
|
||||
SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw
|
||||
do {
|
||||
try drop(snode)
|
||||
} catch {
|
||||
handleUnspecificError()
|
||||
var snodeFailureCount = OnionRequestAPI.snodeFailureCount[snode] ?? 0
|
||||
snodeFailureCount += 1
|
||||
if snodeFailureCount >= snodeFailureThreshold {
|
||||
SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw
|
||||
do {
|
||||
try drop(snode)
|
||||
} catch {
|
||||
handleUnspecificError()
|
||||
}
|
||||
} else {
|
||||
OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount
|
||||
}
|
||||
} else {
|
||||
handleUnspecificError()
|
||||
}
|
||||
} else if let message = json?["result"] as? String, message == "Loki Server error" {
|
||||
// Do nothing
|
||||
} else {
|
||||
handleUnspecificError()
|
||||
}
|
||||
|
|
|
@ -5,32 +5,38 @@ public extension Storage {
|
|||
internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
|
||||
|
||||
internal static func setOnionRequestPaths(_ paths: [OnionRequestAPI.Path], using transaction: YapDatabaseReadWriteTransaction) {
|
||||
// FIXME: This approach assumes 2 paths of length 3 each. We should do better than this.
|
||||
guard paths.count == 2 else { return }
|
||||
let path0 = paths[0]
|
||||
let path1 = paths[1]
|
||||
guard path0.count == 3, path1.count == 3 else { return }
|
||||
let collection = onionRequestPathCollection
|
||||
// FIXME: This approach assumes either 1 or 2 paths of length 3 each. We should do better than this.
|
||||
clearOnionRequestPaths(using: transaction)
|
||||
guard paths.count >= 1 else { return }
|
||||
let path0 = paths[0]
|
||||
guard path0.count == 3 else { return }
|
||||
transaction.setObject(path0[0], forKey: "0-0", inCollection: collection)
|
||||
transaction.setObject(path0[1], forKey: "0-1", inCollection: collection)
|
||||
transaction.setObject(path0[2], forKey: "0-2", inCollection: collection)
|
||||
guard paths.count >= 2 else { return }
|
||||
let path1 = paths[1]
|
||||
guard path1.count == 3 else { return }
|
||||
transaction.setObject(path1[0], forKey: "1-0", inCollection: collection)
|
||||
transaction.setObject(path1[1], forKey: "1-1", inCollection: collection)
|
||||
transaction.setObject(path1[2], forKey: "1-2", inCollection: collection)
|
||||
}
|
||||
|
||||
public static func getOnionRequestPaths() -> [OnionRequestAPI.Path] {
|
||||
let collection = onionRequestPathCollection
|
||||
var result: [OnionRequestAPI.Path] = []
|
||||
read { transaction in
|
||||
let collection = onionRequestPathCollection
|
||||
if
|
||||
let path0Snode0 = transaction.object(forKey: "0-0", inCollection: collection) as? Snode,
|
||||
let path0Snode1 = transaction.object(forKey: "0-1", inCollection: collection) as? Snode,
|
||||
let path0Snode2 = transaction.object(forKey: "0-2", inCollection: collection) as? Snode,
|
||||
let path1Snode0 = transaction.object(forKey: "1-0", inCollection: collection) as? Snode,
|
||||
let path1Snode1 = transaction.object(forKey: "1-1", inCollection: collection) as? Snode,
|
||||
let path1Snode2 = transaction.object(forKey: "1-2", inCollection: collection) as? Snode {
|
||||
result = [ [ path0Snode0, path0Snode1, path0Snode2 ], [ path1Snode0, path1Snode1, path1Snode2 ] ]
|
||||
let path0Snode2 = transaction.object(forKey: "0-2", inCollection: collection) as? Snode {
|
||||
result.append([ path0Snode0, path0Snode1, path0Snode2 ])
|
||||
if
|
||||
let path1Snode0 = transaction.object(forKey: "1-0", inCollection: collection) as? Snode,
|
||||
let path1Snode1 = transaction.object(forKey: "1-1", inCollection: collection) as? Snode,
|
||||
let path1Snode2 = transaction.object(forKey: "1-2", inCollection: collection) as? Snode {
|
||||
result.append([ path1Snode0, path1Snode1, path1Snode2 ])
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
|
Loading…
Reference in a new issue