From 8ef1c2421591b3b350262ee5679984643e136ab5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 22 Mar 2024 17:22:09 +1100 Subject: [PATCH] [WIP] Working on the libQuic onion requests --- LibSession-Util | 2 +- Session.xcodeproj/project.pbxproj | 20 +- .../LibSession+SessionMessagingKit.swift | 65 ----- SessionSnodeKit/Database/Models/Snode.swift | 57 ++--- .../Models/SnodeReceivedMessageInfo.swift | 4 +- .../Database/Models/SnodeSet.swift | 8 +- SessionSnodeKit/Models/GetInfoResponse.swift | 30 +++ .../Models/GetServiceNodesRequest.swift | 4 +- SessionSnodeKit/Models/GetStatsResponse.swift | 16 -- SessionSnodeKit/Models/GetSwarmResponse.swift | 93 +++++++ .../Networking/OnionRequestAPI.swift | 136 ++-------- SessionSnodeKit/Networking/SnodeAPI.swift | 103 ++++---- .../SessionUtil/LibSession+Networking.swift | 241 ++++++++++++++++++ .../Types/OnionRequestAPIDestination.swift | 2 +- SessionSnodeKit/Types/SnodeAPIEndpoint.swift | 2 +- SessionSnodeKit/Types/SnodeAPIError.swift | 50 ++++ .../General/Collection+Utilities.swift | 8 + SignalUtilitiesKit/Configuration.swift | 13 - 18 files changed, 554 insertions(+), 300 deletions(-) create mode 100644 SessionSnodeKit/Models/GetInfoResponse.swift delete mode 100644 SessionSnodeKit/Models/GetStatsResponse.swift create mode 100644 SessionSnodeKit/Models/GetSwarmResponse.swift create mode 100644 SessionSnodeKit/SessionUtil/LibSession+Networking.swift diff --git a/LibSession-Util b/LibSession-Util index 0b48055f5..6dab3b992 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit 0b48055f5f00e15a2fae41fa846f8c9acc2628a7 +Subproject commit 6dab3b99208b9be410952174e72cb38bb0dedb27 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index c1de42af4..d7d549e84 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -571,7 +571,7 @@ FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; - FD29598B2A43BB8100888A17 /* GetStatsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD29598A2A43BB8100888A17 /* GetStatsResponse.swift */; }; + FD29598B2A43BB8100888A17 /* GetInfoResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD29598A2A43BB8100888A17 /* GetInfoResponse.swift */; }; FD29598D2A43BC0B00888A17 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD29598C2A43BC0B00888A17 /* Version.swift */; }; FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD29598F2A43BE5F00888A17 /* VersionSpec.swift */; }; FD2959922A4417A900888A17 /* PreparedSendData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2959912A4417A900888A17 /* PreparedSendData.swift */; }; @@ -717,6 +717,7 @@ FD7F74602BAAA4C7006DDFD8 /* libSessionUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; }; FD7F74632BAAA4CA006DDFD8 /* libSessionUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; }; FD7F74672BAAAC26006DDFD8 /* GetSwarmResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F74662BAAAC26006DDFD8 /* GetSwarmResponse.swift */; }; + FD7F746A2BAB8A6D006DDFD8 /* LibSession+Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F74692BAB8A6D006DDFD8 /* LibSession+Networking.swift */; }; FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */; }; FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; @@ -1718,7 +1719,7 @@ FD23EA6028ED0B260058676E /* CombineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineExtensions.swift; sourceTree = ""; }; FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; - FD29598A2A43BB8100888A17 /* GetStatsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetStatsResponse.swift; sourceTree = ""; }; + FD29598A2A43BB8100888A17 /* GetInfoResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetInfoResponse.swift; sourceTree = ""; }; FD29598C2A43BC0B00888A17 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; FD29598F2A43BE5F00888A17 /* VersionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionSpec.swift; sourceTree = ""; }; FD2959912A4417A900888A17 /* PreparedSendData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedSendData.swift; sourceTree = ""; }; @@ -1854,6 +1855,7 @@ FD7F745C2BAAA38B006DDFD8 /* LibSessionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionError.swift; sourceTree = ""; }; FD7F745E2BAAA3B4006DDFD8 /* TypeConversion+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypeConversion+Utilities.swift"; sourceTree = ""; }; FD7F74662BAAAC26006DDFD8 /* GetSwarmResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSwarmResponse.swift; sourceTree = ""; }; + FD7F74692BAB8A6D006DDFD8 /* LibSession+Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+Networking.swift"; sourceTree = ""; }; FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = ""; }; FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; @@ -3337,6 +3339,7 @@ FDF8488F29405C13007DCAE5 /* Types */, FDF8488C29405C04007DCAE5 /* Jobs */, FDF8489229405C1B007DCAE5 /* Networking */, + FD7F74682BAB8A5D006DDFD8 /* SessionUtil */, C3C2A5CD255385F300C340D1 /* Utilities */, C3C2A5B9255385ED00C340D1 /* Configuration.swift */, ); @@ -4105,6 +4108,14 @@ path = Utilities; sourceTree = ""; }; + FD7F74682BAB8A5D006DDFD8 /* SessionUtil */ = { + isa = PBXGroup; + children = ( + FD7F74692BAB8A6D006DDFD8 /* LibSession+Networking.swift */, + ); + path = SessionUtil; + sourceTree = ""; + }; FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */ = { isa = PBXGroup; children = ( @@ -4529,7 +4540,7 @@ FDF848A629405C5A007DCAE5 /* ONSResolveRequest.swift */, FDF8489E29405C5A007DCAE5 /* ONSResolveResponse.swift */, FDF8489C29405C5A007DCAE5 /* GetServiceNodesRequest.swift */, - FD29598A2A43BB8100888A17 /* GetStatsResponse.swift */, + FD29598A2A43BB8100888A17 /* GetInfoResponse.swift */, ); path = Models; sourceTree = ""; @@ -5783,6 +5794,7 @@ FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */, FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */, C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */, + FD7F746A2BAB8A6D006DDFD8 /* LibSession+Networking.swift in Sources */, FDF848C529405C5B007DCAE5 /* GetSwarmRequest.swift in Sources */, FDF848D729405C5B007DCAE5 /* SnodeBatchRequest.swift in Sources */, C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */, @@ -5812,7 +5824,7 @@ FDF848CC29405C5B007DCAE5 /* SnodeReceivedMessage.swift in Sources */, FDF848C129405C5A007DCAE5 /* UpdateExpiryRequest.swift in Sources */, FDF848C729405C5B007DCAE5 /* SendMessageResponse.swift in Sources */, - FD29598B2A43BB8100888A17 /* GetStatsResponse.swift in Sources */, + FD29598B2A43BB8100888A17 /* GetInfoResponse.swift in Sources */, FDF848CA29405C5B007DCAE5 /* DeleteAllBeforeRequest.swift in Sources */, FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, FDF848CB29405C5B007DCAE5 /* SnodePoolResponse.swift in Sources */, diff --git a/SessionMessagingKit/SessionUtil/LibSession+SessionMessagingKit.swift b/SessionMessagingKit/SessionUtil/LibSession+SessionMessagingKit.swift index 90b3e82a1..92dc98b39 100644 --- a/SessionMessagingKit/SessionUtil/LibSession+SessionMessagingKit.swift +++ b/SessionMessagingKit/SessionUtil/LibSession+SessionMessagingKit.swift @@ -532,68 +532,3 @@ public extension LibSession { return String(cString: cFullUrl) } } - - -public extension LibSession { - static func addNetworkLogger() { - network_add_logger({ logPtr, msgLen in - guard let log: String = String(pointer: logPtr, length: msgLen, encoding: .utf8) else { - print("[quic:info] Null log") - return - } - - print(log.trimmingCharacters(in: .whitespacesAndNewlines)) - }) - } - - static func sendRequest( - ed25519SecretKey: [UInt8]?, - targetPubkey: String, - targetIp: String, - targetPort: UInt16, - endpoint: String, - payload: String, - callback: @escaping (Bool, Int16, Data?) -> Void - ) { - class CWrapper { - let callback: (Bool, Int16, Data?) -> Void - - public init(_ callback: @escaping (Bool, Int16, Data?) -> Void) { - self.callback = callback - } - } - - let callbackWrapper: CWrapper = CWrapper(callback) - let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque() - let cEd25519SecretKey: [UInt8] = ed25519SecretKey! - let cRemoteAddress: remote_address = remote_address( - pubkey: targetPubkey.toLibSession(), - ip: targetIp.toLibSession(), - port: targetPort - ) - let cEndpoint: [CChar] = endpoint.cArray - let cPayload: [CChar] = payload.cArray - - do { - try CExceptionHelper.performSafely { - network_send_request( - cEd25519SecretKey, - cRemoteAddress, - cEndpoint, - cEndpoint.count, - cPayload, - cPayload.count, - { success, statusCode, dataPtr, dataLen, ctx in - let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } - Unmanaged.fromOpaque(ctx!).takeRetainedValue().callback(success, statusCode, data) - }, - cWrapperPtr - ) - } - } - catch { - print("RAWR \(error)") - callback(false, -1, nil) - } - } -} diff --git a/SessionSnodeKit/Database/Models/Snode.swift b/SessionSnodeKit/Database/Models/Snode.swift index 595b37901..8d9879305 100644 --- a/SessionSnodeKit/Database/Models/Snode.swift +++ b/SessionSnodeKit/Database/Models/Snode.swift @@ -1,4 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import GRDB @@ -8,36 +10,28 @@ public struct Snode: Codable, FetchableRecord, PersistableRecord, TableRecord, C public static var databaseTableName: String { "snode" } static let snodeSet = hasMany(SnodeSet.self) static let snodeSetForeignKey = ForeignKey( - [Columns.address, Columns.port], - to: [SnodeSet.Columns.address, SnodeSet.Columns.port] + [Columns.ip, Columns.lmqPort], + to: [SnodeSet.Columns.ip, SnodeSet.Columns.lmqPort] ) public typealias Columns = CodingKeys public enum CodingKeys: String, CodingKey, ColumnExpression { - case address = "public_ip" - case port = "storage_port" - case ed25519PublicKey = "pubkey_ed25519" + case ip = "public_ip" + case lmqPort = "storage_lmq_port" case x25519PublicKey = "pubkey_x25519" + case ed25519PublicKey = "pubkey_ed25519" } - public let address: String - public let port: UInt16 - public let ed25519PublicKey: String + public let ip: String + public let lmqPort: UInt16 public let x25519PublicKey: String - - public var ip: String { - guard let range = address.range(of: "https://"), range.lowerBound == address.startIndex else { - return address - } - - return String(address[range.upperBound.. { request(for: Snode.snodeSet) } - public var description: String { return "\(address):\(port)" } + public var description: String { return "\(ip):\(lmqPort)" } } // MARK: - Decoder @@ -47,15 +41,18 @@ extension Snode { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) do { - let address: String = try container.decode(String.self, forKey: .address) + // Strip the scheme from the IP (if included) + let ip: String = (try container.decode(String.self, forKey: .ip)) + .replacingOccurrences(of: "http://", with: "") + .replacingOccurrences(of: "https://", with: "") - guard address != "0.0.0.0" else { throw SnodeAPIError.invalidIP } + guard !ip.isEmpty && ip != "0.0.0.0" else { throw SnodeAPIError.invalidIP } self = Snode( - address: (address.starts(with: "https://") ? address : "https://\(address)"), - port: try container.decode(UInt16.self, forKey: .port), - ed25519PublicKey: try container.decode(String.self, forKey: .ed25519PublicKey), - x25519PublicKey: try container.decode(String.self, forKey: .x25519PublicKey) + ip: ip, + lmqPort: try container.decode(UInt16.self, forKey: .lmqPort), + x25519PublicKey: try container.decode(String.self, forKey: .x25519PublicKey), + ed25519PublicKey: try container.decode(String.self, forKey: .ed25519PublicKey) ) } catch { @@ -82,8 +79,8 @@ internal extension Snode { struct ResultWrapper: Decodable, FetchableRecord { let key: String let nodeIndex: Int - let address: String - let port: UInt16 + let ip: String + let lmqPort: UInt16 let snode: Snode } @@ -113,7 +110,7 @@ internal extension Snode { internal extension Collection where Element == Snode { - /// This method is used to save Swarms + /// This method is used to save Swarms and paths func save(_ db: Database, key: String) throws { try self.enumerated().forEach { nodeIndex, node in try node.save(db) @@ -121,15 +118,15 @@ internal extension Collection where Element == Snode { try SnodeSet( key: key, nodeIndex: nodeIndex, - address: node.address, - port: node.port + ip: node.ip, + lmqPort: node.lmqPort ).save(db) } } } internal extension Collection where Element == [Snode] { - /// This method is used to save onion reuqest paths + /// This method is used to save onion request paths func save(_ db: Database) throws { try self.enumerated().forEach { pathIndex, path in try path.save(db, key: "\(SnodeSet.onionRequestPathPrefix)\(pathIndex)") diff --git a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift index 4c5a7dc1f..8e8a4c3e6 100644 --- a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift +++ b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift @@ -43,10 +43,10 @@ public struct SnodeReceivedMessageInfo: Codable, FetchableRecord, MutablePersist public extension SnodeReceivedMessageInfo { private static func key(for snode: Snode, publicKey: String, namespace: SnodeAPI.Namespace) -> String { guard namespace != .default else { - return "\(snode.address):\(snode.port).\(publicKey)" + return "\(snode.ip):\(snode.lmqPort).\(publicKey)" } - return "\(snode.address):\(snode.port).\(publicKey).\(namespace.rawValue)" + return "\(snode.ip):\(snode.lmqPort).\(publicKey).\(namespace.rawValue)" } init( diff --git a/SessionSnodeKit/Database/Models/SnodeSet.swift b/SessionSnodeKit/Database/Models/SnodeSet.swift index 209a5d95f..370c9c2d3 100644 --- a/SessionSnodeKit/Database/Models/SnodeSet.swift +++ b/SessionSnodeKit/Database/Models/SnodeSet.swift @@ -13,14 +13,14 @@ public struct SnodeSet: Codable, FetchableRecord, EncodableRecord, PersistableRe public enum CodingKeys: String, CodingKey, ColumnExpression { case key case nodeIndex - case address - case port + case ip + case lmqPort } public let key: String public let nodeIndex: Int - public let address: String - public let port: UInt16 + public let ip: String + public let lmqPort: UInt16 public var node: QueryInterfaceRequest { request(for: SnodeSet.node) diff --git a/SessionSnodeKit/Models/GetInfoResponse.swift b/SessionSnodeKit/Models/GetInfoResponse.swift new file mode 100644 index 000000000..2a8e04f98 --- /dev/null +++ b/SessionSnodeKit/Models/GetInfoResponse.swift @@ -0,0 +1,30 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtilitiesKit + +extension SnodeAPI { + public class GetInfoResponse: SnodeResponse { + private enum CodingKeys: String, CodingKey { + case versionString = "version" + } + + let versionString: String? + + var version: Version? { versionString.map { Version.from($0) } } + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + versionString = (try container.decode([Int]?.self, forKey: .versionString))? + .map { "\($0)" } + .joined(separator: ".") + + try super.init(from: decoder) + } + } +} diff --git a/SessionSnodeKit/Models/GetServiceNodesRequest.swift b/SessionSnodeKit/Models/GetServiceNodesRequest.swift index 6fae67c3e..124fdb4aa 100644 --- a/SessionSnodeKit/Models/GetServiceNodesRequest.swift +++ b/SessionSnodeKit/Models/GetServiceNodesRequest.swift @@ -17,15 +17,15 @@ extension SnodeAPI { public struct Fields: Encodable { enum CodingKeys: String, CodingKey { case publicIp = "public_ip" - case storagePort = "storage_port" case pubkeyEd25519 = "pubkey_ed25519" case pubkeyX25519 = "pubkey_x25519" + case storageLmqPort = "storage_lmq_port" } let publicIp: Bool - let storagePort: Bool let pubkeyEd25519: Bool let pubkeyX25519: Bool + let storageLmqPort: Bool } } } diff --git a/SessionSnodeKit/Models/GetStatsResponse.swift b/SessionSnodeKit/Models/GetStatsResponse.swift deleted file mode 100644 index 5e33cd03b..000000000 --- a/SessionSnodeKit/Models/GetStatsResponse.swift +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import SessionUtilitiesKit - -extension SnodeAPI { - public struct GetStatsResponse: Codable { - private enum CodingKeys: String, CodingKey { - case versionString = "version" - } - - let versionString: String? - - var version: Version? { versionString.map { Version.from($0) } } - } -} diff --git a/SessionSnodeKit/Models/GetSwarmResponse.swift b/SessionSnodeKit/Models/GetSwarmResponse.swift new file mode 100644 index 000000000..1cc725b1e --- /dev/null +++ b/SessionSnodeKit/Models/GetSwarmResponse.swift @@ -0,0 +1,93 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtilitiesKit + +public class GetSwarmResponse: SnodeResponse { + private enum CodingKeys: String, CodingKey { + case swarm + case internalSnodes = "snodes" + } + + fileprivate struct _Snode: Codable { + public enum CodingKeys: String, CodingKey { + case ip + case lmqPort = "port_omq" + case x25519PublicKey = "pubkey_x25519" + case ed25519PublicKey = "pubkey_ed25519" + } + + /// The IPv4 address of the service node. + let ip: String + + /// The storage server port where OxenMQ is listening. + let lmqPort: UInt16 + + /// This is the X25519 pubkey key of this service node, used for encrypting onion requests and for establishing an encrypted connection to the storage server's OxenMQ port. + let x25519PublicKey: String + + /// The Ed25519 public key of this service node. This is the public key the service node uses wherever a signature is required (such as when signing recursive requests). + let ed25519PublicKey: String + } + + /// Contains the target swarm ID, encoded as a hex string. (This ID is a unsigned, 64-bit value and cannot be reliably transported unencoded through JSON) + internal let swarm: String + + /// An array containing the list of service nodes in the target swarm. + private let internalSnodes: [Failable<_Snode>] + + public var snodes: Set { + internalSnodes + .compactMap { $0.value } + .map { responseSnode in + Snode( + ip: responseSnode.ip, + lmqPort: responseSnode.lmqPort, + x25519PublicKey: responseSnode.x25519PublicKey, + ed25519PublicKey: responseSnode.ed25519PublicKey + ) + } + .asSet() + } + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + swarm = try container.decode(String.self, forKey: .swarm) + internalSnodes = try container.decode([Failable<_Snode>].self, forKey: .internalSnodes) + + try super.init(from: decoder) + } +} + +// MARK: - Decoder + +extension GetSwarmResponse._Snode { + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: GetSwarmResponse._Snode.CodingKeys.self) + + do { + // Strip the http from the IP (if included) + let ip: String = (try container.decode(String.self, forKey: .ip)) + .replacingOccurrences(of: "http://", with: "") + .replacingOccurrences(of: "https://", with: "") + + guard !ip.isEmpty && ip != "0.0.0.0" else { throw SnodeAPIError.invalidIP } + + self = GetSwarmResponse._Snode( + ip: ip, + lmqPort: try container.decode(UInt16.self, forKey: .lmqPort), + x25519PublicKey: try container.decode(String.self, forKey: .x25519PublicKey), + ed25519PublicKey: try container.decode(String.self, forKey: .ed25519PublicKey) + ) + } + catch { + SNLog("Failed to parse snode: \(error.localizedDescription).") + throw HTTPError.invalidJSON + } + } +} diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index ea7fd43f0..4e31b7d21 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -79,10 +79,15 @@ public enum OnionRequestAPI { private static func testSnode(_ snode: Snode, using dependencies: Dependencies) -> AnyPublisher { let url = "\(snode.address):\(snode.port)/get_stats/v1" let timeout: TimeInterval = 3 // Use a shorter timeout for testing - - return HTTP.execute(.get, url, timeout: timeout) - .decoded(as: SnodeAPI.GetStatsResponse.self, using: dependencies) - .tryMap { response -> Void in + + return LibSession + .sendRequest( + ed25519SecretKey: Identity.fetchUserEd25519KeyPair()?.secretKey, + snode: snode, + endpoint: SnodeAPI.Endpoint.getInfo.rawValue + ) + .decoded(as: SnodeAPI.GetInfoResponse.self, using: dependencies) + .tryMap { _, response -> Void in guard let version: Version = response.version else { throw OnionRequestAPIError.missingSnodeVersion } guard version >= Version(major: 2, minor: 0, patch: 7) else { SNLog("Unsupported snode version: \(version.stringValue).") @@ -513,117 +518,28 @@ public enum OnionRequestAPI { timeout: TimeInterval = HTTP.defaultTimeout, using dependencies: Dependencies = Dependencies() ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { - var guardSnode: Snode? - return buildOnion(around: payload, targetedAt: destination, using: dependencies) - .flatMap { intermediate -> AnyPublisher<(ResponseInfoType, Data?), Error> in - guardSnode = intermediate.guardSnode - let url = "\(guardSnode!.address):\(guardSnode!.port)/onion_req/v2" - let finalEncryptionResult = intermediate.finalEncryptionResult - let onion = finalEncryptionResult.ciphertext - if case OnionRequestAPIDestination.server = destination, Double(onion.count) > 0.75 * Double(maxRequestSize) { - SNLog("Approaching request size limit: ~\(onion.count) bytes.") - } - let parameters: JSON = [ - "ephemeral_key" : finalEncryptionResult.ephemeralPublicKey.toHexString() - ] - let destinationSymmetricKey = intermediate.destinationSymmetricKey + let snodeToExclude: Snode? = { + switch destination { + case .snode(let snode): return snode + default: return nil + } + }() + + return getPath(excluding: snodeToExclude, using: dependencies) + .tryFlatMap { path -> AnyPublisher<(ResponseInfoType, Data?), Error> in + guard let guardSnode: Snode = path.first else { throw OnionRequestAPIError.insufficientSnodes } - // TODO: Replace 'json' with a codable typed - return encode(ciphertext: onion, json: parameters) - .flatMap { body in HTTP.execute(.post, url, body: body, timeout: timeout) } - .flatMap { responseData in - handleResponse( - responseData: responseData, - destinationSymmetricKey: destinationSymmetricKey, - version: version, - destination: destination - ) - } - .eraseToAnyPublisher() + return LibSession + .sendOnionRequest( + path: path, + ed25519SecretKey: Identity.fetchUserEd25519KeyPair()?.secretKey, + to: destination, + payload: payload//Data()//body + ) } .handleEvents( receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let error): - guard let guardSnode: Snode = guardSnode else { - return SNLog("Request failed with no guardSnode.") - } - guard case HTTPError.httpRequestFailed(let statusCode, let data) = error else { return } - - let path = paths.first { $0.contains(guardSnode) } - - func handleUnspecificError() { - guard let path = path else { return } - - var pathFailureCount: UInt = (OnionRequestAPI.pathFailureCount.wrappedValue[path] ?? 0) - pathFailureCount += 1 - - if pathFailureCount >= pathFailureThreshold { - dropGuardSnode(guardSnode) - path.forEach { snode in - SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw - } - - drop(path) - } - else { - OnionRequestAPI.pathFailureCount.mutate { $0[path] = pathFailureCount } - } - } - - let prefix = "Next node not found: " - let json: JSON? - - if let data: Data = data, let processedJson = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { - json = processedJson - } - else if let data: Data = data, let result: String = String(data: data, encoding: .utf8) { - json = [ "result": result ] - } - else { - json = nil - } - - if let message = json?["result"] as? String, message.hasPrefix(prefix) { - let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..= snodeFailureThreshold { - SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw - do { - try drop(snode) - } - catch { - handleUnspecificError() - } - } - else { - OnionRequestAPI.snodeFailureCount - .mutate { $0[snode] = snodeFailureCount } - } - } else { - // Do nothing - } - } - else if let message = json?["result"] as? String, message == "Loki Server error" { - // Do nothing - } - else if case .server(let host, _, _, _, _) = destination, host == "116.203.70.33" && statusCode == 0 { - // FIXME: Temporary thing to kick out nodes that can't talk to the V2 OGS yet - handleUnspecificError() - } - else if statusCode == 0 { // Timeout - // Do nothing - } - else { - handleUnspecificError() - } - } } ) .eraseToAnyPublisher() diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index ce8b13749..4ee67a45a 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -47,15 +47,27 @@ public final class SnodeAPI { private static let maxRetryCount: Int = 8 private static let minSwarmSnodeCount: Int = 3 - private static let seedNodePool: Set = { + private static let seedNodePool: Set = { guard !Features.useTestnet else { - return [ "http://public.loki.foundation:38157" ] + // public.loki.foundation + return [ + Snode( + ip: "144.76.164.202", + lmqPort: 20200, + x25519PublicKey: "", + ed25519PublicKey: "1f000f09a7b07828dcb72af7cd16857050c10c02bd58afb0e38111fb6cda1fef" + ) + ] } return [ - "https://seed1.getsession.org:4432", - "https://seed2.getsession.org:4432", - "https://seed3.getsession.org:4432" + // seed2.getsession.org + Snode( + ip: "144.76.164.202", + lmqPort: 20203, + x25519PublicKey: "", + ed25519PublicKey: "1f003f0b6544c1050c9a052deafdb8cd1b4d2fbbf1dfb9d80f47ee2a0c316112" + ), ] }() private static let snodeFailureThreshold: Int = 3 @@ -308,9 +320,10 @@ public final class SnodeAPI { .retry(4) .eraseToAnyPublisher() } - .map { _, responseData in parseSnodes(from: responseData) } + .decoded(as: GetSwarmResponse.self, using: dependencies) + .map { _, response in response.snodes } .handleEvents( - receiveOutput: { swarm in setSwarm(to: swarm, for: publicKey) } + receiveOutput: { snodes in setSwarm(to: snodes, for: publicKey) } ) .eraseToAnyPublisher() } @@ -1097,37 +1110,28 @@ public final class SnodeAPI { private static func getSnodePoolFromSeedNode( using dependencies: Dependencies ) -> AnyPublisher, Error> { - let request: SnodeRequest = SnodeRequest( - endpoint: .jsonGetNServiceNodes, - body: GetServiceNodesRequest( - activeOnly: true, - limit: 256, - fields: GetServiceNodesRequest.Fields( - publicIp: true, - storagePort: true, - pubkeyEd25519: true, - pubkeyX25519: true - ) - ) - ) - - guard let target: String = seedNodePool.randomElement() else { + guard let targetSeedNode: Snode = seedNodePool.randomElement() else { return Fail(error: SnodeAPIError.snodePoolUpdatingFailed) .eraseToAnyPublisher() } - guard let payload: Data = try? JSONEncoder().encode(request) else { - return Fail(error: HTTPError.invalidJSON) - .eraseToAnyPublisher() - } - SNLog("Populating snode pool using seed node: \(target).") + SNLog("Populating snode pool using seed node: \(targetSeedNode).") - return HTTP - .execute( - .post, - "\(target)/json_rpc", - body: payload, - useSeedNodeURLSession: true + return LibSession + .sendRequest( + ed25519SecretKey: Identity.fetchUserEd25519KeyPair()?.secretKey, + snode: targetSeedNode, + endpoint: SnodeAPI.Endpoint.jsonGetNServiceNodes.rawValue, + payload: GetServiceNodesRequest( + activeOnly: true, + limit: 256, + fields: GetServiceNodesRequest.Fields( + publicIp: true, + pubkeyEd25519: true, + pubkeyX25519: true, + storageLmqPort: true + ) + ) ) .decoded(as: SnodePoolResponse.self, using: dependencies) .mapError { error in @@ -1136,7 +1140,7 @@ public final class SnodeAPI { default: return error } } - .map { snodePool -> Set in + .map { _, snodePool -> Set in snodePool.result .serviceNodeStates .compactMap { $0.value } @@ -1146,8 +1150,8 @@ public final class SnodeAPI { .handleEvents( receiveCompletion: { result in switch result { - case .finished: SNLog("Got snode pool from seed node: \(target).") - case .failure: SNLog("Failed to contact seed node at: \(target).") + case .finished: SNLog("Got snode pool from seed node: \(targetSeedNode).") + case .failure: SNLog("Failed to contact seed node at: \(targetSeedNode).") } } ) @@ -1184,9 +1188,9 @@ public final class SnodeAPI { limit: nil, fields: GetServiceNodesRequest.Fields( publicIp: true, - storagePort: true, pubkeyEd25519: true, - pubkeyX25519: true + pubkeyX25519: true, + storageLmqPort: true ) ) ) @@ -1227,7 +1231,6 @@ public final class SnodeAPI { } .eraseToAnyPublisher() } - public static var otherReuquestCallback: ((Snode, Data) -> Void)? private static func send( request: SnodeRequest, @@ -1241,33 +1244,30 @@ public final class SnodeAPI { } guard Features.useOnionRequests else { - return HTTP - .execute( - .post, - "\(snode.address):\(snode.port)/storage_rpc/v1", - body: payload + return LibSession + .sendRequest( + ed25519SecretKey: Identity.fetchUserEd25519KeyPair()?.secretKey, + snode: snode, + endpoint: request.endpoint.rawValue, + payload: request.body ) - .map { response in (HTTP.ResponseInfo(code: -1, headers: [:]), response) } .mapError { error in switch error { case HTTPError.httpRequestFailed(let statusCode, let data): - return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error) + return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey, using: dependencies) ?? error) default: return error } } .eraseToAnyPublisher() } - if let callback = otherReuquestCallback { - callback(snode, payload) - } return dependencies.network .send(.onionRequest(payload, to: snode)) .mapError { error in switch error { case HTTPError.httpRequestFailed(let statusCode, let data): - return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error) + return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey, using: dependencies) ?? error) default: return error } @@ -1342,7 +1342,8 @@ public final class SnodeAPI { withStatusCode statusCode: UInt, data: Data?, forSnode snode: Snode, - associatedWith publicKey: String? = nil + associatedWith publicKey: String? = nil, + using dependencies: Dependencies ) -> Error? { func handleBadSnode() { let oldFailureCount = (SnodeAPI.snodeFailureCount.wrappedValue[snode] ?? 0) diff --git a/SessionSnodeKit/SessionUtil/LibSession+Networking.swift b/SessionSnodeKit/SessionUtil/LibSession+Networking.swift new file mode 100644 index 000000000..44b7c43e9 --- /dev/null +++ b/SessionSnodeKit/SessionUtil/LibSession+Networking.swift @@ -0,0 +1,241 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine +import SessionUtil +import SessionUtilitiesKit + +// MARK: - LibSession + +public extension LibSession { + private static func sendRequest( + ed25519SecretKey: [UInt8], + targetPubkey: String, + targetIp: String, + targetPort: UInt16, + endpoint: String, + payload: [UInt8]?, + callback: @escaping (Bool, Bool, Int16, Data?) -> Void + ) { + class CWrapper { + let callback: (Bool, Bool, Int16, Data?) -> Void + + public init(_ callback: @escaping (Bool, Bool, Int16, Data?) -> Void) { + self.callback = callback + } + } + + let callbackWrapper: CWrapper = CWrapper(callback) + let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque() + let cRemoteAddress: remote_address = remote_address( + pubkey: targetPubkey.toLibSession(), + ip: targetIp.toLibSession(), + port: targetPort + ) + let cEndpoint: [CChar] = endpoint.cArray + let cPayload: [UInt8] = (payload ?? []) + + network_send_request( + ed25519SecretKey, + cRemoteAddress, + cEndpoint, + cEndpoint.count, + cPayload, + cPayload.count, + { success, timeout, statusCode, dataPtr, dataLen, ctx in + let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } + Unmanaged.fromOpaque(ctx!).takeRetainedValue().callback(success, timeout, statusCode, data) + }, + cWrapperPtr + ) + } + + private static func sendOnionRequest( + path: [Snode], + ed25519SecretKey: [UInt8], + to destination: OnionRequestAPIDestination, + payload: [UInt8]?, + callback: @escaping (Bool, Bool, Int16, Data?) -> Void + ) { + class CWrapper { + let callback: (Bool, Bool, Int16, Data?) -> Void + + public init(_ callback: @escaping (Bool, Bool, Int16, Data?) -> Void) { + self.callback = callback + } + } + + let callbackWrapper: CWrapper = CWrapper(callback) + let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque() + let cPayload: [UInt8] = (payload ?? []) + var x25519Pubkeys: [UnsafePointer?] = path.map { $0.x25519PublicKey.cArray }.unsafeCopy() + var ed25519Pubkeys: [UnsafePointer?] = path.map { $0.ed25519PublicKey.cArray }.unsafeCopy() + let cNodes: UnsafePointer? = path + .enumerated() + .map { index, snode in + onion_request_service_node( + ip: snode.ip.toLibSession(), + lmq_port: snode.lmqPort, + x25519_pubkey_hex: x25519Pubkeys[index], + ed25519_pubkey_hex: ed25519Pubkeys[index], + failure_count: 0 + ) + } + .unsafeCopy() + let cOnionPath: onion_request_path = onion_request_path( + nodes: cNodes, + nodes_count: path.count, + failure_count: 0 + ) + + switch destination { + case .snode(let snode): + let cX25519Pubkey: UnsafePointer? = snode.x25519PublicKey.cArray.unsafeCopy() + let cEd25519Pubkey: UnsafePointer? = snode.ed25519PublicKey.cArray.unsafeCopy() + + network_send_onion_request_to_snode_destination( + cOnionPath, + ed25519SecretKey, + onion_request_service_node( + ip: snode.ip.toLibSession(), + lmq_port: snode.lmqPort, + x25519_pubkey_hex: cX25519Pubkey, + ed25519_pubkey_hex: cEd25519Pubkey, + failure_count: 0 + ), + cPayload, + cPayload.count, + { success, timeout, statusCode, dataPtr, dataLen, ctx in + let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } + Unmanaged.fromOpaque(ctx!).takeRetainedValue().callback(success, timeout, statusCode, data) + }, + cWrapperPtr + ) + + case .server(let host, let target, let x25519PublicKey, let scheme, let port): + let cMethod: [CChar] = "GET".cArray + let targetScheme: String = (scheme ?? "https") + + network_send_onion_request_to_server_destination( + cOnionPath, + ed25519SecretKey, + cMethod, + host.cArray, + target.cArray, + targetScheme.cArray, + x25519PublicKey.cArray, + (port ?? (targetScheme == "https" ? 443 : 80)), + nil, + nil, + 0, + cPayload, + cPayload.count, + { success, timeout, statusCode, dataPtr, dataLen, ctx in + let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } + Unmanaged.fromOpaque(ctx!).takeRetainedValue().callback(success, timeout, statusCode, data) + }, + cWrapperPtr + ) + } + } + + private static func sendRequest( + ed25519SecretKey: [UInt8]?, + snode: Snode, + endpoint: String, + payloadBytes: [UInt8]? + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + return Deferred { + Future { resolver in + guard let ed25519SecretKey: [UInt8] = ed25519SecretKey else { + return resolver(Result.failure(SnodeAPIError.missingSecretKey)) + } + + LibSession.sendRequest( + ed25519SecretKey: ed25519SecretKey, + targetPubkey: snode.ed25519PublicKey, + targetIp: snode.ip, + targetPort: snode.lmqPort, + endpoint: endpoint,//.rawValue, + payload: payloadBytes, + callback: { success, timeout, statusCode, data in + switch SnodeAPIError(success: success, timeout: timeout, statusCode: statusCode, data: data) { + case .some(let error): resolver(Result.failure(error)) + case .none: resolver(Result.success((HTTP.ResponseInfo(code: Int(statusCode), headers: [:]), data))) + } + } + ) + } + }.eraseToAnyPublisher() + } + + static func sendRequest( + ed25519SecretKey: [UInt8]?, + snode: Snode, + endpoint: String + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + return sendRequest(ed25519SecretKey: ed25519SecretKey, snode: snode, endpoint: endpoint, payloadBytes: nil) + } + + static func sendRequest( + ed25519SecretKey: [UInt8]?, + snode: Snode, + endpoint: String, + payload: T + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + let payloadBytes: [UInt8] + + switch payload { + case let data as Data: payloadBytes = Array(data) + case let bytes as [UInt8]: payloadBytes = bytes + default: + guard let encodedPayload: Data = try? JSONEncoder().encode(payload) else { + return Fail(error: SnodeAPIError.invalidPayload).eraseToAnyPublisher() + } + + payloadBytes = Array(encodedPayload) + } + + return sendRequest(ed25519SecretKey: ed25519SecretKey, snode: snode, endpoint: endpoint, payloadBytes: payloadBytes) + } + + static func sendOnionRequest( + path: [Snode], + ed25519SecretKey: [UInt8]?, + to destination: OnionRequestAPIDestination, + payload: T + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + let payloadBytes: [UInt8] + switch payload { + case let data as Data: payloadBytes = Array(data) + case let bytes as [UInt8]: payloadBytes = bytes + default: + guard let encodedPayload: Data = try? JSONEncoder().encode(payload) else { + return Fail(error: SnodeAPIError.invalidPayload).eraseToAnyPublisher() + } + + payloadBytes = Array(encodedPayload) + } + + return Deferred { + Future { resolver in + guard let ed25519SecretKey: [UInt8] = ed25519SecretKey else { + return resolver(Result.failure(SnodeAPIError.missingSecretKey)) + } + + LibSession.sendOnionRequest( + path: path, + ed25519SecretKey: ed25519SecretKey, + to: destination, + payload: payloadBytes, + callback: { success, timeout, statusCode, data in + switch SnodeAPIError(success: success, timeout: timeout, statusCode: statusCode, data: data) { + case .some(let error): resolver(Result.failure(error)) + case .none: resolver(Result.success((HTTP.ResponseInfo(code: Int(statusCode), headers: [:]), data))) + } + } + ) + } + }.eraseToAnyPublisher() + } +} diff --git a/SessionSnodeKit/Types/OnionRequestAPIDestination.swift b/SessionSnodeKit/Types/OnionRequestAPIDestination.swift index 235bb817e..4ac56481c 100644 --- a/SessionSnodeKit/Types/OnionRequestAPIDestination.swift +++ b/SessionSnodeKit/Types/OnionRequestAPIDestination.swift @@ -8,7 +8,7 @@ public enum OnionRequestAPIDestination: CustomStringConvertible, Codable { public var description: String { switch self { - case .snode(let snode): return "Service node \(snode.ip):\(snode.port)" + case .snode(let snode): return "Service node \(snode.ip):\(snode.lmqPort)" case .server(let host, _, _, _, _): return host } } diff --git a/SessionSnodeKit/Types/SnodeAPIEndpoint.swift b/SessionSnodeKit/Types/SnodeAPIEndpoint.swift index 4fca79e84..73e00bfe9 100644 --- a/SessionSnodeKit/Types/SnodeAPIEndpoint.swift +++ b/SessionSnodeKit/Types/SnodeAPIEndpoint.swift @@ -17,7 +17,7 @@ public extension SnodeAPI { case sequence = "sequence" case getInfo = "info" - case getSwarm = "get_snodes_for_pubkey" + case getSwarm = "get_swarm" case jsonRPCCall = "json_rpc" case oxenDaemonRPCCall = "oxend_request" diff --git a/SessionSnodeKit/Types/SnodeAPIError.swift b/SessionSnodeKit/Types/SnodeAPIError.swift index ef132736b..1d54863c7 100644 --- a/SessionSnodeKit/Types/SnodeAPIError.swift +++ b/SessionSnodeKit/Types/SnodeAPIError.swift @@ -3,6 +3,7 @@ // stringlint:disable import Foundation +import SessionUtilitiesKit public enum SnodeAPIError: LocalizedError { case generic @@ -20,6 +21,15 @@ public enum SnodeAPIError: LocalizedError { case decryptionFailed case hashingFailed case validationFailed + + // Quic + case invalidPayload + case missingSecretKey + case requestFailed(error: String, rawData: Data?) + case timeout + case unreachable + case unassociatedPubkey + case unknown public var errorDescription: String? { switch self { @@ -38,6 +48,46 @@ public enum SnodeAPIError: LocalizedError { case .decryptionFailed: return "Couldn't decrypt ONS name." case .hashingFailed: return "Couldn't compute ONS name hash." case .validationFailed: return "ONS name validation failed." + + // Quic + case .invalidPayload: return "Invalid payload." + case .missingSecretKey: return "Missing secret key." + case .requestFailed(let error, _): return error + case .timeout: return "The request timed out." + case .unreachable: return "The service node is unreachable." + case .unassociatedPubkey: return "The service node is no longer associated with the public key." + case .unknown: return "An unknown error occurred." + } + } +} + +public extension SnodeAPIError { + init?(success: Bool, timeout: Bool, statusCode: Int16, data: Data?) { + guard !success || statusCode < 200 || statusCode > 299 else { return nil } + guard !timeout else { + self = .timeout + return + } + + // Handle status codes with specific meanings + switch (statusCode, data.map { String(data: $0, encoding: .utf8) }) { + /// A snode will return a `406` but onion requests v4 seems to return `425` so handle both + case (406, _), (425, _): + SNLog("The user's clock is out of sync with the service node network.") + self = .clockOutOfSync + + case (401, _): + SNLog("Failed to verify the signature.") + self = .signatureVerificationFailed + + case (421, _): + self = .unassociatedPubkey + + case (500, _), (502, _), (503, _): + self = .unreachable + + case (_, .none): self = .unknown + case (_, .some(let responseString)): self = .requestFailed(error: responseString, rawData: data) } } } diff --git a/SessionUtilitiesKit/General/Collection+Utilities.swift b/SessionUtilitiesKit/General/Collection+Utilities.swift index 0422552df..73d4351c9 100644 --- a/SessionUtilitiesKit/General/Collection+Utilities.swift +++ b/SessionUtilitiesKit/General/Collection+Utilities.swift @@ -16,6 +16,14 @@ public extension Collection { _ = copy.initialize(from: self) return copy } + + /// This creates an UnsafePointer to access data in memory directly. This result pointer provides no automated + /// memory management so after use you are responsible for handling the life cycle and need to call `deallocate()`. + func unsafeCopy() -> UnsafePointer? { + let copy = UnsafeMutableBufferPointer.allocate(capacity: self.underestimatedCount) + _ = copy.initialize(from: self) + return UnsafePointer(copy.baseAddress) + } } public extension Collection where Element == [CChar] { diff --git a/SignalUtilitiesKit/Configuration.swift b/SignalUtilitiesKit/Configuration.swift index 052dcb29f..6383b49ac 100644 --- a/SignalUtilitiesKit/Configuration.swift +++ b/SignalUtilitiesKit/Configuration.swift @@ -13,19 +13,6 @@ public enum Configuration { SNMessagingKit.configure() SNSnodeKit.configure() SNUIKit.configure() - let secKey = Identity.fetchUserEd25519KeyPair()?.secretKey - SnodeAPI.otherReuquestCallback = { snode, payload in - SessionUtil.sendRequest( - ed25519SecretKey: secKey, - targetPubkey: snode.x25519PublicKey, - targetIp: snode.ip, - targetPort: snode.port, - endpoint: "/storage_rpc/v1", - payload: payload - ) { success, statusCode, data in - print("RAWR") - } - } } }