diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 5f9be61a3..1801d86cd 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1204,6 +1204,8 @@ static NSTimeInterval launchStartedAt; { OWSAssertIsOnMainThread(); OWSLogInfo(@"storageIsReady"); + + [LokiAPI loadSwarmCache]; [self checkIfAppIsReady]; } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 31bbb70a4..6748384e9 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -1,6 +1,6 @@ import PromiseKit -extension LokiAPI { +public extension LokiAPI { // MARK: Settings private static let minimumSnodeCount = 2 @@ -8,7 +8,26 @@ extension LokiAPI { private static let defaultSnodePort: UInt16 = 8080 // MARK: Caching - fileprivate static var swarmCache: [String:[Target]] = [:] // TODO: Persist on disk + private static let swarmCacheKey = "swarmCacheKey" + private static let swarmCacheCollection = "swarmCacheCollection" + + fileprivate static var swarmCache: [String:[Target]] = [:] + + @objc public static func loadSwarmCache() { + var result: [String:[Target]]? = nil + storage.dbReadConnection.read { transaction in + let intermediate = transaction.object(forKey: swarmCacheKey, inCollection: swarmCacheCollection) as! [String:[TargetWrapper]] + result = intermediate.mapValues { $0.map { Target(from: $0) } } + } + swarmCache = result ?? [:] + } + + private static func saveSwarmCache() { + let intermediate = swarmCache.mapValues { $0.map { TargetWrapper(from: $0) } } + storage.dbReadWriteConnection.readWrite { transaction in + transaction.setObject(intermediate, forKey: swarmCacheKey, inCollection: swarmCacheCollection) + } + } // MARK: Internal API private static func getRandomSnode() -> Promise { @@ -22,7 +41,10 @@ extension LokiAPI { return Promise<[Target]> { $0.fulfill(cachedSwarm) } } else { let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey ] - return getRandomSnode().then { invoke(.getSwarm, on: $0, associatedWith: hexEncodedPublicKey, parameters: parameters) }.map { parseTargets(from: $0) }.get { swarmCache[hexEncodedPublicKey] = $0 } + return getRandomSnode().then { invoke(.getSwarm, on: $0, associatedWith: hexEncodedPublicKey, parameters: parameters) }.map { parseTargets(from: $0) }.get { swarm in + swarmCache[hexEncodedPublicKey] = swarm + saveSwarmCache() + } } } @@ -46,9 +68,10 @@ extension LokiAPI { } } +// MARK: Error Handling internal extension Promise { - func handlingSwarmSpecificErrorsIfNeeded(for target: LokiAPI.Target, associatedWith hexEncodedPublicKey: String) -> Promise { + internal func handlingSwarmSpecificErrorsIfNeeded(for target: LokiAPI.Target, associatedWith hexEncodedPublicKey: String) -> Promise { return recover { error -> Promise in if let error = error as? NetworkManagerError { switch error.statusCode { diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Target.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Target.swift new file mode 100644 index 000000000..cd15175f6 --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Target.swift @@ -0,0 +1,26 @@ + +internal extension LokiAPI { + + internal struct Target : Hashable { + internal let address: String + internal let port: UInt16 + + internal init(address: String, port: UInt16) { + self.address = address + self.port = port + } + + internal init(from targetWrapper: TargetWrapper) { + self.address = targetWrapper.address + self.port = targetWrapper.port + } + + internal enum Method : String { + /// Only supported by snode targets. + case getSwarm = "get_snodes_for_pubkey" + /// Only supported by snode targets. + case getMessages = "retrieve" + case sendMessage = "store" + } + } +} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+TargetWrapper.swift b/SignalServiceKit/src/Loki/API/LokiAPI+TargetWrapper.swift new file mode 100644 index 000000000..f8f14fba1 --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiAPI+TargetWrapper.swift @@ -0,0 +1,22 @@ + +@objc internal final class TargetWrapper : NSObject, NSCoding { + internal let address: String + internal let port: UInt16 + + internal init(from target: LokiAPI.Target) { + address = target.address + port = target.port + super.init() + } + + internal init?(coder: NSCoder) { + address = coder.decodeObject(forKey: "address") as! String + port = coder.decodeObject(forKey: "port") as! UInt16 + super.init() + } + + internal func encode(with coder: NSCoder) { + coder.encode(address, forKey: "address") + coder.encode(port, forKey: "port") + } +} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 04dd160b9..efc0ea18a 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -1,26 +1,15 @@ import PromiseKit @objc public final class LokiAPI : NSObject { - private static let storage = OWSPrimaryStorage.shared() + internal static let storage = OWSPrimaryStorage.shared() // MARK: Settings private static let version = "v1" public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 + private static let receivedMessageHashValuesKey = "receivedMessageHashValuesKey" + private static let receivedMessageHashValuesCollection = "receivedMessageHashValuesCollection" // MARK: Types - internal struct Target : Hashable { - let address: String - let port: UInt16 - - enum Method : String { - /// Only supported by snode targets. - case getSwarm = "get_snodes_for_pubkey" - /// Only supported by snode targets. - case getMessages = "retrieve" - case sendMessage = "store" - } - } - public typealias RawResponse = Any public enum Error : LocalizedError { @@ -149,21 +138,21 @@ import PromiseKit private static func getReceivedMessageHashValues() -> Set? { var result: Set? = nil storage.dbReadConnection.read { transaction in - result = storage.getReceivedMessageHashes(with: transaction) + result = transaction.object(forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) as! Set? } return result } private static func setReceivedMessageHashValues(to receivedMessageHashValues: Set) { storage.dbReadWriteConnection.readWrite { transaction in - storage.setReceivedMessageHashes(receivedMessageHashValues, with: transaction) + transaction.setObject(receivedMessageHashValues, forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) } } } private extension AnyPromise { - static func from(_ promise: Promise) -> AnyPromise { + fileprivate static func from(_ promise: Promise) -> AnyPromise { let result = AnyPromise(promise) result.retainUntilComplete() return result diff --git a/SignalServiceKit/src/Loki/Crypto/ECKeyPair+Loki.swift b/SignalServiceKit/src/Loki/Crypto/ECKeyPair+Loki.swift index d96aa0b06..57fa63440 100644 --- a/SignalServiceKit/src/Loki/Crypto/ECKeyPair+Loki.swift +++ b/SignalServiceKit/src/Loki/Crypto/ECKeyPair+Loki.swift @@ -1,11 +1,11 @@ public extension ECKeyPair { - @objc var hexEncodedPrivateKey: String { + @objc public var hexEncodedPrivateKey: String { return privateKey.map { String(format: "%02hhx", $0) }.joined() } - @objc var hexEncodedPublicKey: String { + @objc public var hexEncodedPublicKey: String { // Prefixing with "05" is necessary for what seems to be a sort of Signal public key versioning system // Ref: [NSData prependKeyType] in AxolotKit return "05" + publicKey.map { String(format: "%02hhx", $0) }.joined() diff --git a/SignalServiceKit/src/Loki/Crypto/FallbackSessionCipher.swift b/SignalServiceKit/src/Loki/Crypto/FallbackSessionCipher.swift index 8a4ae5039..336bdc4a6 100644 --- a/SignalServiceKit/src/Loki/Crypto/FallbackSessionCipher.swift +++ b/SignalServiceKit/src/Loki/Crypto/FallbackSessionCipher.swift @@ -4,7 +4,7 @@ import Curve25519Kit private extension String { // Convert hex string to Data - var hexData: Data { + fileprivate var hexData: Data { var hex = self var data = Data() while(hex.count > 0) { diff --git a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h index beb789e9e..d74f63696 100644 --- a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h +++ b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h @@ -95,9 +95,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setLastMessageHashForServiceNode:(NSString *)serviceNode hash:(NSString *)hash expiresAt:(u_int64_t)expiresAt transaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(setLastMessageHash(forServiceNode:hash:expiresAt:transaction:)); -- (NSSet *_Nullable)getReceivedMessageHashesWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (void)setReceivedMessageHashes:(NSSet *)receivedMessageHashes withTransaction:(YapDatabaseReadWriteTransaction *)transaction; - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m index 26744fbec..b737ef437 100644 --- a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m +++ b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m @@ -153,12 +153,4 @@ [transaction removeObjectForKey:serviceNode inCollection:LKLastMessageHashCollection]; } -- (NSSet *_Nullable)getReceivedMessageHashesWithTransaction:(YapDatabaseReadTransaction *)transaction { - return (NSSet *)[[transaction objectForKey:LKReceivedMessageHashesKey inCollection:LKReceivedMessageHashesCollection] as:NSSet.class]; -} - -- (void)setReceivedMessageHashes:(NSSet *)receivedMessageHashes withTransaction:(YapDatabaseReadWriteTransaction *)transaction { - [transaction setObject:receivedMessageHashes forKey:LKReceivedMessageHashesKey inCollection:LKReceivedMessageHashesCollection]; -} - @end diff --git a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift index 1073318fe..7d2afd2a2 100644 --- a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift +++ b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift @@ -2,12 +2,12 @@ import CryptoSwift private extension UInt64 { - init(_ decimal: Decimal) { + fileprivate init(_ decimal: Decimal) { self.init(truncating: decimal as NSDecimalNumber) } // Convert a UInt8 array to a UInt64 - init(_ bytes: [UInt8]) { + fileprivate init(_ bytes: [UInt8]) { precondition(bytes.count <= MemoryLayout.size) var value: UInt64 = 0 for byte in bytes { @@ -24,7 +24,7 @@ private extension MutableCollection where Element == UInt8, Index == Int { /// /// - Parameter amount: The amount to increment by /// - Returns: The incremented collection - func increment(by amount: Int) -> Self { + fileprivate func increment(by amount: Int) -> Self { var result = self var increment = amount for i in (0..