mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			137 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			137 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import Foundation
 | |
| import GRDB
 | |
| import SessionUtilitiesKit
 | |
| 
 | |
| public struct Snode: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, Hashable, CustomStringConvertible {
 | |
|     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]
 | |
|     )
 | |
|     
 | |
|     public typealias Columns = CodingKeys
 | |
|     public enum CodingKeys: String, CodingKey, ColumnExpression {
 | |
|         case address = "public_ip"
 | |
|         case port = "storage_port"
 | |
|         case ed25519PublicKey = "pubkey_ed25519"
 | |
|         case x25519PublicKey = "pubkey_x25519"
 | |
|     }
 | |
| 
 | |
|     public let address: String
 | |
|     public let port: UInt16
 | |
|     public let ed25519PublicKey: String
 | |
|     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..<address.endIndex])
 | |
|     }
 | |
|     
 | |
|     public var snodeSet: QueryInterfaceRequest<SnodeSet> {
 | |
|         request(for: Snode.snodeSet)
 | |
|     }
 | |
|     
 | |
|     public var description: String { return "\(address):\(port)" }
 | |
| }
 | |
| 
 | |
| // MARK: - Decoder
 | |
| 
 | |
| extension Snode {
 | |
|     public init(from decoder: Decoder) throws {
 | |
|         let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
 | |
|         
 | |
|         do {
 | |
|             let address: String = try container.decode(String.self, forKey: .address)
 | |
|             
 | |
|             guard address != "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)
 | |
|             )
 | |
|         }
 | |
|         catch {
 | |
|             SNLog("Failed to parse snode: \(error.localizedDescription).")
 | |
|             throw HTTP.Error.invalidJSON
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - GRDB Interactions
 | |
| 
 | |
| internal extension Snode {
 | |
|     static func fetchSet(_ db: Database, publicKey: String) throws -> Set<Snode> {
 | |
|         return try Snode
 | |
|             .joining(
 | |
|                 required: Snode.snodeSet
 | |
|                     .filter(SnodeSet.Columns.key == publicKey)
 | |
|                     .order(SnodeSet.Columns.nodeIndex)
 | |
|             )
 | |
|             .fetchSet(db)
 | |
|     }
 | |
| 
 | |
|     static func fetchAllOnionRequestPaths(_ db: Database) throws -> [[Snode]] {
 | |
|         struct ResultWrapper: Decodable, FetchableRecord {
 | |
|             let key: String
 | |
|             let nodeIndex: Int
 | |
|             let address: String
 | |
|             let port: UInt16
 | |
|             let snode: Snode
 | |
|         }
 | |
|         
 | |
|         return try SnodeSet
 | |
|             .filter(SnodeSet.Columns.key.like("\(SnodeSet.onionRequestPathPrefix)%"))
 | |
|             .order(SnodeSet.Columns.nodeIndex)
 | |
|             .order(SnodeSet.Columns.key)
 | |
|             .including(required: SnodeSet.node)
 | |
|             .asRequest(of: ResultWrapper.self)
 | |
|             .fetchAll(db)
 | |
|             .reduce(into: [:]) { prev, next in  // Reducing will lose the 'key' sorting
 | |
|                 prev[next.key] = (prev[next.key] ?? []).appending(next.snode)
 | |
|             }
 | |
|             .asArray()
 | |
|             .sorted(by: { lhs, rhs in lhs.key < rhs.key })
 | |
|             .compactMap { _, nodes in !nodes.isEmpty ? nodes : nil }  // Exclude empty sets
 | |
|     }
 | |
|     
 | |
|     static func clearOnionRequestPaths(_ db: Database) throws {
 | |
|         try SnodeSet
 | |
|             .filter(SnodeSet.Columns.key.like("\(SnodeSet.onionRequestPathPrefix)%"))
 | |
|             .deleteAll(db)
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| internal extension Collection where Element == Snode {
 | |
|     /// This method is used to save Swarms
 | |
|     func save(_ db: Database, key: String) throws {
 | |
|         try self.enumerated().forEach { nodeIndex, node in
 | |
|             try node.save(db)
 | |
|             
 | |
|             try SnodeSet(
 | |
|                 key: key,
 | |
|                 nodeIndex: nodeIndex,
 | |
|                 address: node.address,
 | |
|                 port: node.port
 | |
|             ).save(db)
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| internal extension Collection where Element == [Snode] {
 | |
|     /// This method is used to save onion reuqest paths
 | |
|     func save(_ db: Database) throws {
 | |
|         try self.enumerated().forEach { pathIndex, path in
 | |
|             try path.save(db, key: "\(SnodeSet.onionRequestPathPrefix)\(pathIndex)")
 | |
|         }
 | |
|     }
 | |
| }
 |