From f48cdd02ee7bbd4561afdc4563047ab5f2437d24 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 26 Apr 2022 15:59:37 +1000 Subject: [PATCH 01/19] fix an issue where current call implementation won't compile for simulators --- .../Calls/Views & Modals/CallVideoView.swift | 17 ++++++++++++++--- Session/Calls/Views & Modals/MiniCallView.swift | 12 ++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Session/Calls/Views & Modals/CallVideoView.swift b/Session/Calls/Views & Modals/CallVideoView.swift index f32d513e4..6bf5ce6f7 100644 --- a/Session/Calls/Views & Modals/CallVideoView.swift +++ b/Session/Calls/Views & Modals/CallVideoView.swift @@ -2,9 +2,17 @@ import WebRTC import Foundation +#if arch(arm64) +// Note: 'RTCMTLVideoView' requires arm64 (so won't work on the simulator which +// we need to build for x86_64 due to WebRTC not supporting arm64 simulator builds) +typealias TargetView = RTCMTLVideoView +#else +typealias TargetView = RTCEAGLVideoView +#endif + // MARK: RemoteVideoView -class RemoteVideoView: RTCMTLVideoView { +class RemoteVideoView: TargetView { override func renderFrame(_ frame: RTCVideoFrame?) { super.renderFrame(frame) @@ -39,7 +47,7 @@ class RemoteVideoView: RTCMTLVideoView { // Assume we're already setup for the correct orientation. break } - +#if arch(arm64) if let rotationOverride = rotationOverride { self.rotationOverride = NSNumber(value: rotationOverride.rawValue) if [ RTCVideoRotation._0, RTCVideoRotation._180 ].contains(rotationOverride) { @@ -59,13 +67,14 @@ class RemoteVideoView: RTCMTLVideoView { if frameRatio < 1.5 { self.videoContentMode = .scaleAspectFit } +#endif } } } // MARK: LocalVideoView -class LocalVideoView: RTCMTLVideoView { +class LocalVideoView: TargetView { static let width: CGFloat = 80 static let height: CGFloat = 173 @@ -77,7 +86,9 @@ class LocalVideoView: RTCMTLVideoView { // sometimes the rotationOverride is not working // if it is only set once on initialization self.rotationOverride = NSNumber(value: RTCVideoRotation._0.rawValue) +#if arch(arm64) self.videoContentMode = .scaleAspectFill +#endif } } } diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift index 6c7d5a424..dec4bbe9c 100644 --- a/Session/Calls/Views & Modals/MiniCallView.swift +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -16,6 +16,9 @@ final class MiniCallView: UIView, RTCVideoViewDelegate { private var top: NSLayoutConstraint? private var bottom: NSLayoutConstraint? +#if arch(arm64) + // Note: 'RTCMTLVideoView' requires arm64 (so won't work on the simulator which + // we need to build for x86_64 due to WebRTC not supporting arm64 simulator builds) private lazy var remoteVideoView: RTCMTLVideoView = { let result = RTCMTLVideoView() result.delegate = self @@ -24,6 +27,15 @@ final class MiniCallView: UIView, RTCVideoViewDelegate { result.backgroundColor = .black return result }() +#else + private lazy var remoteVideoView: RTCEAGLVideoView = { + let result = RTCEAGLVideoView() + result.delegate = self + result.alpha = self.callVC.call.isRemoteVideoEnabled ? 1 : 0 + result.backgroundColor = .black + return result + }() +#endif // MARK: Initialization public static var current: MiniCallView? From 0b0f0a37870c47e89f3479148bd8a6a1ea8b6bfb Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 2 May 2022 11:35:48 +1000 Subject: [PATCH 02/19] retrieve messages authenticated for 1-1 chats --- .../Pollers/ClosedGroupPoller.swift | 2 +- SessionSnodeKit/SnodeAPI.swift | 52 +++++++++++++++---- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 481a9b336..ade1badd4 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -103,7 +103,7 @@ public final class ClosedGroupPoller : NSObject { // randomElement() uses the system's default random generator, which is cryptographically secure guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } guard let self = self, self.isPolling(for: groupPublicKey) else { return Promise(error: Error.pollingCanceled) } - return SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey).map2 { + return SnodeAPI.getRawMessagesUnauthenticated(from: snode, associatedWith: groupPublicKey).map2 { let (rawMessages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) return (snode, rawMessages, lastRawMessage) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index ae1382e67..7e957f72c 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -22,6 +22,11 @@ public final class SnodeAPI : NSObject { public static var clockOffset: Int64 = 0 /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. public static var swarmCache: [String:Set] = [:] + + // MARK: Namespaces + private static let defaultNamespace = 0 + private static let unauthenticatedNamespace = -10 + private static let configNamespace = 5 // MARK: Settings private static let maxRetryCount: UInt = 8 @@ -391,36 +396,61 @@ public final class SnodeAPI : NSObject { } public static func getRawMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + let (promise, seal) = RawResponsePromise.pending() + Threading.workQueue.async { + getMessagesWithAuthentication(from: snode, associatedWith: publicKey).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } + } + return promise + } + + public static func getRawMessagesUnauthenticated(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { let (promise, seal) = RawResponsePromise.pending() Threading.workQueue.async { getMessagesInternal(from: snode, associatedWith: publicKey).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } } return promise } - - private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + + private static func getMessagesWithAuthentication(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { let storage = SNSnodeKitConfiguration.shared.storage - // NOTE: All authentication logic is currently commented out, the reason being that we can't currently support + // NOTE: All authentication logic is only apply to 1-1 chats, the reason being that we can't currently support // it yet for closed groups. The Storage Server requires an ed25519 key pair, but we don't have that for our // closed groups. -// guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) } + guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) } // Get last message hash storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey) let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" // Construct signature -// let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset) -// let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() -// let verificationData = ("retrieve" + String(timestamp)).data(using: String.Encoding.utf8)! -// let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey)! + let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset) + let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() + let verificationData = ("retrieve" + String(timestamp)).data(using: String.Encoding.utf8)! + let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey)! + // Make the request + let parameters: JSON = [ + "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey, + "namespace": defaultNamespace, + "lastHash" : lastHash, + "timestamp" : timestamp, + "pubkey_ed25519" : ed25519PublicKey, + "signature" : signature.toBase64() + ] + return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) + } + + private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + let storage = SNSnodeKitConfiguration.shared.storage + + // Get last message hash + storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey) + let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" + // Make the request let parameters: JSON = [ "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey, + "namespace": unauthenticatedNamespace, "lastHash" : lastHash, -// "timestamp" : timestamp, -// "pubkey_ed25519" : ed25519PublicKey, -// "signature" : signature.toBase64()! ] return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } From 034f2ecc22478b0df32dd7e5f50b19294e254414 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 2 May 2022 13:51:50 +1000 Subject: [PATCH 03/19] send messages with authentication --- .../Sending & Receiving/MessageSender.swift | 5 +- .../Pollers/ClosedGroupPoller.swift | 2 +- SessionSnodeKit/SnodeAPI.swift | 79 ++++++++++++++++--- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index b1203c82c..1eadc5013 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -204,7 +204,10 @@ public final class MessageSender : NSObject { let base64EncodedData = wrappedMessage.base64EncodedString() let timestamp = UInt64(Int64(message.sentTimestamp!) + SnodeAPI.clockOffset) let snodeMessage = SnodeMessage(recipient: message.recipient!, data: base64EncodedData, ttl: message.ttl, timestamp: timestamp) - SnodeAPI.sendMessage(snodeMessage).done(on: DispatchQueue.global(qos: .userInitiated)) { promises in + SnodeAPI.sendMessage(snodeMessage, + authenticated: (kind != .closedGroupMessage), + isConfigMessage: message.isKind(of: ConfigurationMessage.self)) + .done(on: DispatchQueue.global(qos: .userInitiated)) { promises in var isSuccess = false let promiseCount = promises.count var errorCount = 0 diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index ade1badd4..7ea185b16 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -103,7 +103,7 @@ public final class ClosedGroupPoller : NSObject { // randomElement() uses the system's default random generator, which is cryptographically secure guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } guard let self = self, self.isPolling(for: groupPublicKey) else { return Promise(error: Error.pollingCanceled) } - return SnodeAPI.getRawMessagesUnauthenticated(from: snode, associatedWith: groupPublicKey).map2 { + return SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey, authenticated: false).map2 { let (rawMessages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) return (snode, rawMessages, lastRawMessage) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 7e957f72c..d0d7357bc 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -395,23 +395,30 @@ public final class SnodeAPI : NSObject { } } - public static func getRawMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + // MARK: Retrieve + + public static func getConfigMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { let (promise, seal) = RawResponsePromise.pending() Threading.workQueue.async { - getMessagesWithAuthentication(from: snode, associatedWith: publicKey).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } + getMessagesWithAuthentication(from: snode, associatedWith: publicKey, namespace: configNamespace).done2 { + seal.fulfill($0) + }.catch2 { + seal.reject($0) + } } return promise } - public static func getRawMessagesUnauthenticated(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + public static func getRawMessages(from snode: Snode, associatedWith publicKey: String, authenticated: Bool = true) -> RawResponsePromise { let (promise, seal) = RawResponsePromise.pending() Threading.workQueue.async { - getMessagesInternal(from: snode, associatedWith: publicKey).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } + let retrievePromise = authenticated ? getMessagesWithAuthentication(from: snode, associatedWith: publicKey, namespace: defaultNamespace) : getMessagesUnauthenticated(from: snode, associatedWith: publicKey) + retrievePromise.done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } } return promise } - private static func getMessagesWithAuthentication(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + private static func getMessagesWithAuthentication(from snode: Snode, associatedWith publicKey: String, namespace: Int) -> RawResponsePromise { let storage = SNSnodeKitConfiguration.shared.storage // NOTE: All authentication logic is only apply to 1-1 chats, the reason being that we can't currently support @@ -425,12 +432,13 @@ public final class SnodeAPI : NSObject { // Construct signature let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset) let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() - let verificationData = ("retrieve" + String(timestamp)).data(using: String.Encoding.utf8)! - let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey)! + guard let verificationData = ("retrieve" + String(namespace) + String(timestamp)).data(using: String.Encoding.utf8), + let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) + else { return Promise(error: Error.signingFailed) } // Make the request let parameters: JSON = [ "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey, - "namespace": defaultNamespace, + "namespace": namespace, "lastHash" : lastHash, "timestamp" : timestamp, "pubkey_ed25519" : ed25519PublicKey, @@ -439,7 +447,7 @@ public final class SnodeAPI : NSObject { return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } - private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + private static func getMessagesUnauthenticated(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { let storage = SNSnodeKitConfiguration.shared.storage // Get last message hash @@ -455,12 +463,42 @@ public final class SnodeAPI : NSObject { return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } - public static func sendMessage(_ message: SnodeMessage) -> Promise> { + // MARK: Store + + public static func sendConfigMessage(_ message: SnodeMessage) -> Promise> { + return sendMessageWithAuthentication(message, namespace: configNamespace) + } + + public static func sendMessage(_ message: SnodeMessage, authenticated: Bool, isConfigMessage: Bool) -> Promise> { + if isConfigMessage { + return sendConfigMessage(message) + } + if authenticated { + return sendMessageWithAuthentication(message, namespace: defaultNamespace) + } + return sendMessageUnauthenticated(message) + } + + private static func sendMessageWithAuthentication(_ message: SnodeMessage, namespace: Int) -> Promise> { + let storage = SNSnodeKitConfiguration.shared.storage + + guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) } + // Construct signature + let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset) + let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() + guard let verificationData = ("store" + String(namespace) + String(timestamp)).data(using: String.Encoding.utf8), + let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) + else { return Promise(error: Error.signingFailed) } + // Make the request let (promise, seal) = Promise>.pending() let publicKey = Features.useTestnet ? message.recipient.removing05PrefixIfNeeded() : message.recipient Threading.workQueue.async { getTargetSnodes(for: publicKey).map2 { targetSnodes in - let parameters = message.toJSON() + var parameters = message.toJSON() + parameters["namespace"] = namespace + parameters["sig_timestamp"] = timestamp + parameters["pubkey_ed25519"] = ed25519PublicKey + parameters["signature"] = signature.toBase64() return Set(targetSnodes.map { targetSnode in attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) @@ -471,6 +509,25 @@ public final class SnodeAPI : NSObject { return promise } + private static func sendMessageUnauthenticated(_ message: SnodeMessage) -> Promise> { + let (promise, seal) = Promise>.pending() + let publicKey = Features.useTestnet ? message.recipient.removing05PrefixIfNeeded() : message.recipient + Threading.workQueue.async { + getTargetSnodes(for: publicKey).map2 { targetSnodes in + var parameters = message.toJSON() + parameters["namespace"] = unauthenticatedNamespace + return Set(targetSnodes.map { targetSnode in + attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { + invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) + } + }) + }.done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } + } + return promise + } + + // MARK: Delete + @objc(deleteMessageForPublickKey:serverHashes:) public static func objc_deleteMessage(publicKey: String, serverHashes: [String]) -> AnyPromise { AnyPromise.from(deleteMessage(publicKey: publicKey, serverHashes: serverHashes)) From 9c535dcb1b58e093e11473b07add590fca431c08 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 2 May 2022 16:54:36 +1000 Subject: [PATCH 04/19] update namespace number --- SessionSnodeKit/SnodeAPI.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index d0d7357bc..e031bd1ac 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -24,8 +24,8 @@ public final class SnodeAPI : NSObject { public static var swarmCache: [String:Set] = [:] // MARK: Namespaces - private static let defaultNamespace = 0 - private static let unauthenticatedNamespace = -10 + private static let defaultNamespace = 1 + private static let unauthenticatedNamespace = 10 private static let configNamespace = 5 // MARK: Settings From 38523230cb96752cb4679a3f085d92a704c49438 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 3 May 2022 09:18:53 +1000 Subject: [PATCH 05/19] update namespace --- SessionSnodeKit/SnodeAPI.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index e031bd1ac..d0d7357bc 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -24,8 +24,8 @@ public final class SnodeAPI : NSObject { public static var swarmCache: [String:Set] = [:] // MARK: Namespaces - private static let defaultNamespace = 1 - private static let unauthenticatedNamespace = 10 + private static let defaultNamespace = 0 + private static let unauthenticatedNamespace = -10 private static let configNamespace = 5 // MARK: Settings From 15b954dbd68c4c79ea30449da5d80962b79756c6 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 10:14:15 +1000 Subject: [PATCH 06/19] persist hardfork info --- SessionSnodeKit/SnodeAPI.swift | 30 +++++++++++++++++-- .../General/SNUserDefaults.swift | 2 ++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index d0d7357bc..47e3bca6a 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -27,6 +27,11 @@ public final class SnodeAPI : NSObject { private static let defaultNamespace = 0 private static let unauthenticatedNamespace = -10 private static let configNamespace = 5 + + // MARK: Hardfork version + private static var hardfork = UserDefaults.standard[.hardfork] + private static var softfork = UserDefaults.standard[.softfork] + public static var inHardfork: Bool { hardfork >= 19 && softfork >= 1 } // MARK: Settings private static let maxRetryCount: UInt = 8 @@ -136,7 +141,18 @@ public final class SnodeAPI : NSObject { // MARK: Internal API internal static func invoke(_ method: Snode.Method, on snode: Snode, associatedWith publicKey: String? = nil, parameters: JSON) -> RawResponsePromise { if Features.useOnionRequests { - return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey).map2 { $0 as Any } + return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey) + .map2 { json in + if let hf = json["hf"] as? [Int] { + if hf[0] != hardfork || hf[1] != softfork { + hardfork = hf[0] + softfork = hf[1] + UserDefaults.standard[.hardfork] = hardfork + UserDefaults.standard[.softfork] = softfork + } + } + return json as Any + } } else { let url = "\(snode.address):\(snode.port)/storage_rpc/v1" return HTTP.execute(.post, url, parameters: parameters).map2 { $0 as Any }.recover2 { error -> Promise in @@ -418,6 +434,14 @@ public final class SnodeAPI : NSObject { return promise } + public static func getRawClosedGroupMessagesFromDefaultNamespace(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + let (promise, seal) = RawResponsePromise.pending() + Threading.workQueue.async { + getMessagesUnauthenticated(from: snode, associatedWith: publicKey, namespace: defaultNamespace).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } + } + return promise + } + private static func getMessagesWithAuthentication(from snode: Snode, associatedWith publicKey: String, namespace: Int) -> RawResponsePromise { let storage = SNSnodeKitConfiguration.shared.storage @@ -447,7 +471,7 @@ public final class SnodeAPI : NSObject { return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } - private static func getMessagesUnauthenticated(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { + private static func getMessagesUnauthenticated(from snode: Snode, associatedWith publicKey: String, namespace: Int = unauthenticatedNamespace) -> RawResponsePromise { let storage = SNSnodeKitConfiguration.shared.storage // Get last message hash @@ -457,7 +481,7 @@ public final class SnodeAPI : NSObject { // Make the request let parameters: JSON = [ "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey, - "namespace": unauthenticatedNamespace, + "namespace": namespace, "lastHash" : lastHash, ] return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index ac04172a5..351c64cfc 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -26,6 +26,8 @@ public enum SNUserDefaults { public enum Int : Swift.String { case appMode + case hardfork + case softfork } public enum String : Swift.String { From 4224a1fa3ce84eb43cbb14ce6aaf04feaa51c3a5 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 10:34:35 +1000 Subject: [PATCH 07/19] tweaks for sending message to snode --- .../Sending & Receiving/MessageSender.swift | 2 +- SessionSnodeKit/SnodeAPI.swift | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 1eadc5013..fd280e83c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -205,7 +205,7 @@ public final class MessageSender : NSObject { let timestamp = UInt64(Int64(message.sentTimestamp!) + SnodeAPI.clockOffset) let snodeMessage = SnodeMessage(recipient: message.recipient!, data: base64EncodedData, ttl: message.ttl, timestamp: timestamp) SnodeAPI.sendMessage(snodeMessage, - authenticated: (kind != .closedGroupMessage), + isClosedGroupMessage: (kind == .closedGroupMessage), isConfigMessage: message.isKind(of: ConfigurationMessage.self)) .done(on: DispatchQueue.global(qos: .userInitiated)) { promises in var isSuccess = false diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 47e3bca6a..c7e08b069 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -489,18 +489,12 @@ public final class SnodeAPI : NSObject { // MARK: Store - public static func sendConfigMessage(_ message: SnodeMessage) -> Promise> { - return sendMessageWithAuthentication(message, namespace: configNamespace) - } - - public static func sendMessage(_ message: SnodeMessage, authenticated: Bool, isConfigMessage: Bool) -> Promise> { + public static func sendMessage(_ message: SnodeMessage, isClosedGroupMessage: Bool, isConfigMessage: Bool) -> Promise> { if isConfigMessage { - return sendConfigMessage(message) + return sendMessageWithAuthentication(message, namespace: configNamespace) } - if authenticated { - return sendMessageWithAuthentication(message, namespace: defaultNamespace) - } - return sendMessageUnauthenticated(message) + let namespace = isClosedGroupMessage ? unauthenticatedNamespace : defaultNamespace + return sendMessageUnauthenticated(message, namespace: namespace) } private static func sendMessageWithAuthentication(_ message: SnodeMessage, namespace: Int) -> Promise> { @@ -533,13 +527,13 @@ public final class SnodeAPI : NSObject { return promise } - private static func sendMessageUnauthenticated(_ message: SnodeMessage) -> Promise> { + private static func sendMessageUnauthenticated(_ message: SnodeMessage, namespace: Int) -> Promise> { let (promise, seal) = Promise>.pending() let publicKey = Features.useTestnet ? message.recipient.removing05PrefixIfNeeded() : message.recipient Threading.workQueue.async { getTargetSnodes(for: publicKey).map2 { targetSnodes in var parameters = message.toJSON() - parameters["namespace"] = unauthenticatedNamespace + parameters["namespace"] = namespace return Set(targetSnodes.map { targetSnode in attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) From e540497724c703e7d4deee9aa3ecf6d016e0a5a3 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 11:47:09 +1000 Subject: [PATCH 08/19] update last hash persistence --- Session/Utilities/BackgroundPoller.swift | 12 +++++----- .../Pollers/ClosedGroupPoller.swift | 2 +- .../Sending & Receiving/Pollers/Poller.swift | 2 +- SessionSnodeKit/SnodeAPI.swift | 18 +++++++-------- SessionSnodeKit/Storage+SnodeAPI.swift | 22 +++++++++---------- SessionSnodeKit/Storage.swift | 6 ++--- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 5d84d359c..c7d1b3c28 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -28,19 +28,19 @@ public final class BackgroundPoller : NSObject { private static func pollForMessages() -> Promise { let userPublicKey = getUserHexEncodedPublicKey() - return getMessages(for: userPublicKey) + return getMessages(for: userPublicKey, authenticated: true) } private static func pollForClosedGroupMessages() -> [Promise] { let publicKeys = Storage.shared.getUserClosedGroupPublicKeys() - return publicKeys.map { getMessages(for: $0) } + return publicKeys.map { getMessages(for: $0, authenticated: false) } } - private static func getMessages(for publicKey: String) -> Promise { + private static func getMessages(for publicKey: String, authenticated: Bool) -> Promise { return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { - return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in + return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: authenticated).then(on: DispatchQueue.main) { rawResponse -> Promise in let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) let promises = messages.compactMap { json -> Promise? in // Use a best attempt approach here; we don't want to fail the entire process if one of the @@ -50,9 +50,9 @@ public final class BackgroundPoller : NSObject { let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) return job.execute() } - + let namespace = authenticated ? SnodeAPI.defaultNamespace : SnodeAPI.unauthenticatedNamespace // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: lastRawMessage) + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: namespace, associatedWith: publicKey, from: lastRawMessage) return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 7ea185b16..c2667e1eb 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -128,7 +128,7 @@ public final class ClosedGroupPoller : NSObject { } // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: groupPublicKey, from: lastRawMessage) + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.unauthenticatedNamespace, associatedWith: groupPublicKey, from: lastRawMessage) } promise.catch2 { error in SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).") diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 5c9e66561..7c50725fb 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -112,7 +112,7 @@ public final class Poller : NSObject { } // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: userPublicKey, from: lastRawMessage) + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.defaultNamespace, associatedWith: userPublicKey, from: lastRawMessage) strongSelf.pollCount += 1 if strongSelf.pollCount == Poller.maxPollCount { diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index c7e08b069..dea03b4cc 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -24,9 +24,9 @@ public final class SnodeAPI : NSObject { public static var swarmCache: [String:Set] = [:] // MARK: Namespaces - private static let defaultNamespace = 0 - private static let unauthenticatedNamespace = -10 - private static let configNamespace = 5 + public static let defaultNamespace = 0 + public static let unauthenticatedNamespace = -10 + public static let configNamespace = 5 // MARK: Hardfork version private static var hardfork = UserDefaults.standard[.hardfork] @@ -451,8 +451,8 @@ public final class SnodeAPI : NSObject { guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) } // Get last message hash - storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey) - let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" + storage.pruneLastMessageHashInfoIfExpired(for: snode, namespace: namespace, associatedWith: publicKey) + let lastHash = storage.getLastMessageHash(for: snode, namespace: namespace, associatedWith: publicKey) ?? "" // Construct signature let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset) let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() @@ -475,8 +475,8 @@ public final class SnodeAPI : NSObject { let storage = SNSnodeKitConfiguration.shared.storage // Get last message hash - storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey) - let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" + storage.pruneLastMessageHashInfoIfExpired(for: snode, namespace: namespace, associatedWith: publicKey) + let lastHash = storage.getLastMessageHash(for: snode, namespace: namespace, associatedWith: publicKey) ?? "" // Make the request let parameters: JSON = [ @@ -673,10 +673,10 @@ public final class SnodeAPI : NSObject { ) } - public static func updateLastMessageHashValueIfPossible(for snode: Snode, associatedWith publicKey: String, from lastRawMessage: JSON?) { + public static func updateLastMessageHashValueIfPossible(for snode: Snode, namespace: Int, associatedWith publicKey: String, from lastRawMessage: JSON?) { if let lastMessage = lastRawMessage, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 { SNSnodeKitConfiguration.shared.storage.writeSync { transaction in - SNSnodeKitConfiguration.shared.storage.setLastMessageHashInfo(for: snode, associatedWith: publicKey, + SNSnodeKitConfiguration.shared.storage.setLastMessageHashInfo(for: snode, namespace: namespace, associatedWith: publicKey, to: [ "hash" : lastHash, "expirationDate" : NSNumber(value: expirationDate) ], using: transaction) } } else if (lastRawMessage != nil) { diff --git a/SessionSnodeKit/Storage+SnodeAPI.swift b/SessionSnodeKit/Storage+SnodeAPI.swift index 24b36365f..cbccbc0a6 100644 --- a/SessionSnodeKit/Storage+SnodeAPI.swift +++ b/SessionSnodeKit/Storage+SnodeAPI.swift @@ -80,8 +80,8 @@ extension Storage { private static let lastMessageHashCollection = "LokiLastMessageHashCollection" - public func getLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String) -> JSON? { - let key = "\(snode.address):\(snode.port).\(publicKey)" + public func getLastMessageHashInfo(for snode: Snode, namespace: Int, associatedWith publicKey: String) -> JSON? { + let key = namespace == SnodeAPI.defaultNamespace ? "\(snode.address):\(snode.port).\(publicKey)" : "\(snode.address):\(snode.port).\(publicKey).\(namespace)" var result: JSON? Storage.read { transaction in result = transaction.object(forKey: key, inCollection: Storage.lastMessageHashCollection) as? JSON @@ -93,29 +93,29 @@ extension Storage { return result } - public func getLastMessageHash(for snode: Snode, associatedWith publicKey: String) -> String? { - return getLastMessageHashInfo(for: snode, associatedWith: publicKey)?["hash"] as? String + public func getLastMessageHash(for snode: Snode, namespace: Int, associatedWith publicKey: String) -> String? { + return getLastMessageHashInfo(for: snode, namespace: namespace, associatedWith: publicKey)?["hash"] as? String } - public func setLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String, to lastMessageHashInfo: JSON, using transaction: Any) { - let key = "\(snode.address):\(snode.port).\(publicKey)" + public func setLastMessageHashInfo(for snode: Snode, namespace: Int, associatedWith publicKey: String, to lastMessageHashInfo: JSON, using transaction: Any) { + let key = namespace == SnodeAPI.defaultNamespace ? "\(snode.address):\(snode.port).\(publicKey)" : "\(snode.address):\(snode.port).\(publicKey).\(namespace)" guard lastMessageHashInfo.count == 2 && lastMessageHashInfo["hash"] as? String != nil && lastMessageHashInfo["expirationDate"] as? NSNumber != nil else { return } (transaction as! YapDatabaseReadWriteTransaction).setObject(lastMessageHashInfo, forKey: key, inCollection: Storage.lastMessageHashCollection) } - public func pruneLastMessageHashInfoIfExpired(for snode: Snode, associatedWith publicKey: String) { - guard let lastMessageHashInfo = getLastMessageHashInfo(for: snode, associatedWith: publicKey), + public func pruneLastMessageHashInfoIfExpired(for snode: Snode, namespace: Int, associatedWith publicKey: String) { + guard let lastMessageHashInfo = getLastMessageHashInfo(for: snode, namespace: namespace, associatedWith: publicKey), (lastMessageHashInfo["hash"] as? String) != nil, let expirationDate = (lastMessageHashInfo["expirationDate"] as? NSNumber)?.uint64Value else { return } let now = NSDate.millisecondTimestamp() if now >= expirationDate { Storage.writeSync { transaction in - self.removeLastMessageHashInfo(for: snode, associatedWith: publicKey, using: transaction) + self.removeLastMessageHashInfo(for: snode, namespace: namespace, associatedWith: publicKey, using: transaction) } } } - public func removeLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String, using transaction: Any) { - let key = "\(snode.address):\(snode.port).\(publicKey)" + public func removeLastMessageHashInfo(for snode: Snode, namespace: Int, associatedWith publicKey: String, using transaction: Any) { + let key = namespace == SnodeAPI.defaultNamespace ? "\(snode.address):\(snode.port).\(publicKey)" : "\(snode.address):\(snode.port).\(publicKey).\(namespace)" (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: Storage.lastMessageHashCollection) } diff --git a/SessionSnodeKit/Storage.swift b/SessionSnodeKit/Storage.swift index ce0a90f81..04367a847 100644 --- a/SessionSnodeKit/Storage.swift +++ b/SessionSnodeKit/Storage.swift @@ -20,9 +20,9 @@ public protocol SessionSnodeKitStorageProtocol { func setLastSnodePoolRefreshDate(to date: Date, using transaction: Any) func getSwarm(for publicKey: String) -> Set func setSwarm(to swarm: Set, for publicKey: String, using transaction: Any) - func getLastMessageHash(for snode: Snode, associatedWith publicKey: String) -> String? - func setLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String, to lastMessageHashInfo: JSON, using transaction: Any) - func pruneLastMessageHashInfoIfExpired(for snode: Snode, associatedWith publicKey: String) + func getLastMessageHash(for snode: Snode, namespace: Int, associatedWith publicKey: String) -> String? + func setLastMessageHashInfo(for snode: Snode, namespace: Int, associatedWith publicKey: String, to lastMessageHashInfo: JSON, using transaction: Any) + func pruneLastMessageHashInfoIfExpired(for snode: Snode, namespace: Int, associatedWith publicKey: String) func getReceivedMessages(for publicKey: String) -> Set func setReceivedMessages(to receivedMessages: Set, for publicKey: String, using transaction: Any) } From fb65173b06639686749df3cc952f647fcc441434 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 14:28:29 +1000 Subject: [PATCH 09/19] poll closed group messages in both default namespace and closed group namespace --- .../Pollers/ClosedGroupPoller.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index c2667e1eb..83f3127c1 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -87,7 +87,10 @@ public final class ClosedGroupPoller : NSObject { timers[groupPublicKey] = Timer.scheduledTimerOnMainThread(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in timer.invalidate() Threading.pollerQueue.async { - self?.poll(groupPublicKey).done(on: Threading.pollerQueue) { _ in + var promises: [Promise] = [] + if let promise = self?.poll(groupPublicKey) { promises.append(promise) } + if SnodeAPI.inHardfork, let promise = self?.poll(groupPublicKey, defaultInbox: true) { promises.append(promise) } + when(resolved: promises).done(on: Threading.pollerQueue) { _ in self?.pollRecursively(groupPublicKey) }.catch(on: Threading.pollerQueue) { error in // The error is logged in poll(_:) @@ -97,13 +100,14 @@ public final class ClosedGroupPoller : NSObject { } } - private func poll(_ groupPublicKey: String) -> Promise { + private func poll(_ groupPublicKey: String, defaultInbox: Bool = false) -> Promise { guard isPolling(for: groupPublicKey) else { return Promise.value(()) } let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<(Snode, [JSON], JSON?)> in // randomElement() uses the system's default random generator, which is cryptographically secure guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } guard let self = self, self.isPolling(for: groupPublicKey) else { return Promise(error: Error.pollingCanceled) } - return SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey, authenticated: false).map2 { + let getRawMessagesPromise = defaultInbox ? SnodeAPI.getRawClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: groupPublicKey) : SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey, authenticated: false) + return getRawMessagesPromise.map2 { let (rawMessages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) return (snode, rawMessages, lastRawMessage) From 7cc887267f973b3e17337249aaeee2632ce1d745 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 16:12:45 +1000 Subject: [PATCH 10/19] fix retrieve from default inbox --- SessionSnodeKit/OnionRequestAPI.swift | 3 +++ SessionSnodeKit/SnodeAPI.swift | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index 7a03b16f0..1a8b36ee4 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -395,6 +395,9 @@ public enum OnionRequestAPI { if statusCode == 406 { // Clock out of sync SNLog("The user's clock is out of sync with the service node network.") seal.reject(SnodeAPI.Error.clockOutOfSync) + } else if statusCode == 401 { // Signature verification failed + SNLog("Failed to verify the signature.") + seal.reject(SnodeAPI.Error.signatureVerificationFailed) } else if let bodyAsString = json["body"] as? String { guard let bodyAsData = bodyAsString.data(using: .utf8), let body = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) } diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index dea03b4cc..12b10c708 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -49,6 +49,7 @@ public final class SnodeAPI : NSObject { case inconsistentSnodePools case noKeyPair case signingFailed + case signatureVerificationFailed // ONS case decryptionFailed case hashingFailed @@ -62,6 +63,7 @@ public final class SnodeAPI : NSObject { case .inconsistentSnodePools: return "Received inconsistent Service Node pool information from the Service Node network." case .noKeyPair: return "Missing user key pair." case .signingFailed: return "Couldn't sign message." + case . signatureVerificationFailed: return "Failed to verify the signature." // ONS case .decryptionFailed: return "Couldn't decrypt ONS name." case .hashingFailed: return "Couldn't compute ONS name hash." @@ -456,7 +458,8 @@ public final class SnodeAPI : NSObject { // Construct signature let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset) let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() - guard let verificationData = ("retrieve" + String(namespace) + String(timestamp)).data(using: String.Encoding.utf8), + let namespaceVerificationString = namespace == defaultNamespace ? "" : String(namespace) + guard let verificationData = ("retrieve" + namespaceVerificationString + String(timestamp)).data(using: String.Encoding.utf8), let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { return Promise(error: Error.signingFailed) } // Make the request From 372e87e70f792d12c6f80fd5a7708346cd5e4b2c Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 16:36:15 +1000 Subject: [PATCH 11/19] minor fix --- Session/Utilities/BackgroundPoller.swift | 2 +- .../Pollers/ClosedGroupPoller.swift | 2 +- SessionSnodeKit/SnodeAPI.swift | 12 +++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index c7d1b3c28..2480c16de 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -50,7 +50,7 @@ public final class BackgroundPoller : NSObject { let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) return job.execute() } - let namespace = authenticated ? SnodeAPI.defaultNamespace : SnodeAPI.unauthenticatedNamespace + let namespace = authenticated ? SnodeAPI.defaultNamespace : SnodeAPI.closedGroupNamespace // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: namespace, associatedWith: publicKey, from: lastRawMessage) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 83f3127c1..33a3869ae 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -132,7 +132,7 @@ public final class ClosedGroupPoller : NSObject { } // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.unauthenticatedNamespace, associatedWith: groupPublicKey, from: lastRawMessage) + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.closedGroupNamespace, associatedWith: groupPublicKey, from: lastRawMessage) } promise.catch2 { error in SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).") diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 12b10c708..3f055319a 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -25,7 +25,7 @@ public final class SnodeAPI : NSObject { // MARK: Namespaces public static let defaultNamespace = 0 - public static let unauthenticatedNamespace = -10 + public static let closedGroupNamespace = -10 public static let configNamespace = 5 // MARK: Hardfork version @@ -414,7 +414,7 @@ public final class SnodeAPI : NSObject { } // MARK: Retrieve - + // Not in use until we can batch delete and store config messages public static func getConfigMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { let (promise, seal) = RawResponsePromise.pending() Threading.workQueue.async { @@ -474,7 +474,7 @@ public final class SnodeAPI : NSObject { return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } - private static func getMessagesUnauthenticated(from snode: Snode, associatedWith publicKey: String, namespace: Int = unauthenticatedNamespace) -> RawResponsePromise { + private static func getMessagesUnauthenticated(from snode: Snode, associatedWith publicKey: String, namespace: Int = closedGroupNamespace) -> RawResponsePromise { let storage = SNSnodeKitConfiguration.shared.storage // Get last message hash @@ -493,13 +493,11 @@ public final class SnodeAPI : NSObject { // MARK: Store public static func sendMessage(_ message: SnodeMessage, isClosedGroupMessage: Bool, isConfigMessage: Bool) -> Promise> { - if isConfigMessage { - return sendMessageWithAuthentication(message, namespace: configNamespace) - } - let namespace = isClosedGroupMessage ? unauthenticatedNamespace : defaultNamespace + let namespace = isClosedGroupMessage ? closedGroupNamespace : defaultNamespace return sendMessageUnauthenticated(message, namespace: namespace) } + // Not in use until we can batch delete and store config messages private static func sendMessageWithAuthentication(_ message: SnodeMessage, namespace: Int) -> Promise> { let storage = SNSnodeKitConfiguration.shared.storage From 782d51c1d63248010b06c0b855280d67a98722c5 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 16:43:22 +1000 Subject: [PATCH 12/19] minor change on hardfork version check --- .../Sending & Receiving/Pollers/ClosedGroupPoller.swift | 2 +- SessionSnodeKit/SnodeAPI.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 33a3869ae..8012d971f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -89,7 +89,7 @@ public final class ClosedGroupPoller : NSObject { Threading.pollerQueue.async { var promises: [Promise] = [] if let promise = self?.poll(groupPublicKey) { promises.append(promise) } - if SnodeAPI.inHardfork, let promise = self?.poll(groupPublicKey, defaultInbox: true) { promises.append(promise) } + if SnodeAPI.duringHardforkTransition, let promise = self?.poll(groupPublicKey, defaultInbox: true) { promises.append(promise) } when(resolved: promises).done(on: Threading.pollerQueue) { _ in self?.pollRecursively(groupPublicKey) }.catch(on: Threading.pollerQueue) { error in diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 3f055319a..116f959c2 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -31,7 +31,7 @@ public final class SnodeAPI : NSObject { // MARK: Hardfork version private static var hardfork = UserDefaults.standard[.hardfork] private static var softfork = UserDefaults.standard[.softfork] - public static var inHardfork: Bool { hardfork >= 19 && softfork >= 1 } + public static var duringHardforkTransition: Bool { hardfork == 19 && softfork == 0 } // MARK: Settings private static let maxRetryCount: UInt = 8 From 86605dce0854e79fd97765ae8c13cf35edc895bc Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 17:17:15 +1000 Subject: [PATCH 13/19] update background poller for hardfork closed group message retrieve --- Session/Utilities/BackgroundPoller.swift | 37 ++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 2480c16de..78f26743e 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -28,19 +28,24 @@ public final class BackgroundPoller : NSObject { private static func pollForMessages() -> Promise { let userPublicKey = getUserHexEncodedPublicKey() - return getMessages(for: userPublicKey, authenticated: true) + return getMessages(for: userPublicKey) } private static func pollForClosedGroupMessages() -> [Promise] { let publicKeys = Storage.shared.getUserClosedGroupPublicKeys() - return publicKeys.map { getMessages(for: $0, authenticated: false) } + return publicKeys.map { getMessages(for: $0, isClosedGroup: true) } } - private static func getMessages(for publicKey: String, authenticated: Bool) -> Promise { + private static func getMessages(for publicKey: String, isClosedGroup: Bool = false) -> Promise { + func handleRawMessages(rawResponse: Any) -> [Promise] { + + } + return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { - return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: authenticated).then(on: DispatchQueue.main) { rawResponse -> Promise in + var getMessagesPromises: [Promise] = [] + let promise = SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: !isClosedGroup).then(on: DispatchQueue.main) { rawResponse -> Promise in let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) let promises = messages.compactMap { json -> Promise? in // Use a best attempt approach here; we don't want to fail the entire process if one of the @@ -50,12 +55,34 @@ public final class BackgroundPoller : NSObject { let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) return job.execute() } - let namespace = authenticated ? SnodeAPI.defaultNamespace : SnodeAPI.closedGroupNamespace + let namespace = isClosedGroup ? SnodeAPI.closedGroupNamespace : SnodeAPI.defaultNamespace // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: namespace, associatedWith: publicKey, from: lastRawMessage) return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } + getMessagesPromises.append(promise) + + if isClosedGroup && SnodeAPI.duringHardforkTransition { + let promise1 = SnodeAPI.getRawClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in + let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) + let promises = messages.compactMap { json -> Promise? in + // Use a best attempt approach here; we don't want to fail the entire process if one of the + // messages failed to parse. + guard let envelope = SNProtoEnvelope.from(json), + let data = try? envelope.serializedData() else { return nil } + let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) + return job.execute() + } + // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.defaultNamespace, associatedWith: publicKey, from: lastRawMessage) + + return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects + } + getMessagesPromises.append(promise1) + } + + return when(resolved: getMessagesPromises).map{ _ in } } } } From c46c75e4f0cdce3d181f5aa85ccec9681abc032e Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 4 May 2022 17:18:03 +1000 Subject: [PATCH 14/19] clean --- Session/Utilities/BackgroundPoller.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 78f26743e..7ab31d811 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -37,10 +37,6 @@ public final class BackgroundPoller : NSObject { } private static func getMessages(for publicKey: String, isClosedGroup: Bool = false) -> Promise { - func handleRawMessages(rawResponse: Any) -> [Promise] { - - } - return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { From b8ec4a8c7a02764d7b5d4e42ac74c50cd7883327 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 6 May 2022 09:18:13 +1000 Subject: [PATCH 15/19] exclude namespace in parameters if polling for 0 with no authentication --- SessionSnodeKit/SnodeAPI.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 116f959c2..808eaba1e 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -482,11 +482,14 @@ public final class SnodeAPI : NSObject { let lastHash = storage.getLastMessageHash(for: snode, namespace: namespace, associatedWith: publicKey) ?? "" // Make the request - let parameters: JSON = [ + var parameters: JSON = [ "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey, - "namespace": namespace, "lastHash" : lastHash, ] + // Don't include namespace if polling for 0 with no authentication + if namespace != defaultNamespace { + parameters["namespace"] = namespace + } return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } From dac3f10dd1c5a8e0aba58d18cebd57ea0d85d277 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 6 May 2022 10:37:53 +1000 Subject: [PATCH 16/19] tweaks on closed group polling logic --- Session/Utilities/BackgroundPoller.swift | 48 ++++++++++++++----- .../Pollers/ClosedGroupPoller.swift | 8 +++- SessionSnodeKit/SnodeAPI.swift | 5 +- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 7ab31d811..654c633b8 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -33,15 +33,14 @@ public final class BackgroundPoller : NSObject { private static func pollForClosedGroupMessages() -> [Promise] { let publicKeys = Storage.shared.getUserClosedGroupPublicKeys() - return publicKeys.map { getMessages(for: $0, isClosedGroup: true) } + return publicKeys.map { getClosedGroupMessages(for: $0) } } - private static func getMessages(for publicKey: String, isClosedGroup: Bool = false) -> Promise { + private static func getMessages(for publicKey: String) -> Promise { return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { - var getMessagesPromises: [Promise] = [] - let promise = SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: !isClosedGroup).then(on: DispatchQueue.main) { rawResponse -> Promise in + SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) let promises = messages.compactMap { json -> Promise? in // Use a best attempt approach here; we don't want to fail the entire process if one of the @@ -51,16 +50,40 @@ public final class BackgroundPoller : NSObject { let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) return job.execute() } - let namespace = isClosedGroup ? SnodeAPI.closedGroupNamespace : SnodeAPI.defaultNamespace // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: namespace, associatedWith: publicKey, from: lastRawMessage) + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.defaultNamespace, associatedWith: publicKey, from: lastRawMessage) return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } - getMessagesPromises.append(promise) - - if isClosedGroup && SnodeAPI.duringHardforkTransition { - let promise1 = SnodeAPI.getRawClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in + } + } + } + + private static func getClosedGroupMessages(for publicKey: String) -> Promise { + return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in + guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } + return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { + var promises: [Promise] = [] + if SnodeAPI.hardfork <= 19, SnodeAPI.softfork == 0 { + let promise = SnodeAPI.getRawClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in + let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) + let promises = messages.compactMap { json -> Promise? in + // Use a best attempt approach here; we don't want to fail the entire process if one of the + // messages failed to parse. + guard let envelope = SNProtoEnvelope.from(json), + let data = try? envelope.serializedData() else { return nil } + let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) + return job.execute() + } + // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.defaultNamespace, associatedWith: publicKey, from: lastRawMessage) + + return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects + } + promises.append(promise) + } + if SnodeAPI.hardfork >= 19 && SnodeAPI.softfork >= 0 { + let promise = SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: false).then(on: DispatchQueue.main) { rawResponse -> Promise in let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) let promises = messages.compactMap { json -> Promise? in // Use a best attempt approach here; we don't want to fail the entire process if one of the @@ -75,10 +98,9 @@ public final class BackgroundPoller : NSObject { return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } - getMessagesPromises.append(promise1) + promises.append(promise) } - - return when(resolved: getMessagesPromises).map{ _ in } + return when(resolved: promises).map { _ in } } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 8012d971f..77085c778 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -88,8 +88,12 @@ public final class ClosedGroupPoller : NSObject { timer.invalidate() Threading.pollerQueue.async { var promises: [Promise] = [] - if let promise = self?.poll(groupPublicKey) { promises.append(promise) } - if SnodeAPI.duringHardforkTransition, let promise = self?.poll(groupPublicKey, defaultInbox: true) { promises.append(promise) } + if SnodeAPI.hardfork <= 19, SnodeAPI.softfork == 0, let promise = self?.poll(groupPublicKey, defaultInbox: true) { + promises.append(promise) + } + if SnodeAPI.hardfork >= 19, SnodeAPI.softfork >= 0,let promise = self?.poll(groupPublicKey) { + promises.append(promise) + } when(resolved: promises).done(on: Threading.pollerQueue) { _ in self?.pollRecursively(groupPublicKey) }.catch(on: Threading.pollerQueue) { error in diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 808eaba1e..35dc729b5 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -29,9 +29,8 @@ public final class SnodeAPI : NSObject { public static let configNamespace = 5 // MARK: Hardfork version - private static var hardfork = UserDefaults.standard[.hardfork] - private static var softfork = UserDefaults.standard[.softfork] - public static var duringHardforkTransition: Bool { hardfork == 19 && softfork == 0 } + public static var hardfork = UserDefaults.standard[.hardfork] + public static var softfork = UserDefaults.standard[.softfork] // MARK: Settings private static let maxRetryCount: UInt = 8 From 627aba1650defd5ddd141306969fcb301d4dd406 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 9 May 2022 10:03:54 +1000 Subject: [PATCH 17/19] clean up duplicated code --- Session/Utilities/BackgroundPoller.swift | 34 +++++++++--------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 654c633b8..6c0802ced 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -63,29 +63,21 @@ public final class BackgroundPoller : NSObject { return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { - var promises: [Promise] = [] + var promises: [SnodeAPI.RawResponsePromise] = [] + // We have to poll for both namespace 0 and -10 when hardfork == 19 && softfork == 0 if SnodeAPI.hardfork <= 19, SnodeAPI.softfork == 0 { - let promise = SnodeAPI.getRawClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in - let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) - let promises = messages.compactMap { json -> Promise? in - // Use a best attempt approach here; we don't want to fail the entire process if one of the - // messages failed to parse. - guard let envelope = SNProtoEnvelope.from(json), - let data = try? envelope.serializedData() else { return nil } - let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) - return job.execute() - } - // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.defaultNamespace, associatedWith: publicKey, from: lastRawMessage) - - return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects - } + let promise = SnodeAPI.getRawClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: publicKey) promises.append(promise) } if SnodeAPI.hardfork >= 19 && SnodeAPI.softfork >= 0 { - let promise = SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: false).then(on: DispatchQueue.main) { rawResponse -> Promise in + let promise = SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: false) + promises.append(promise) + } + return when(resolved: promises).then(on: DispatchQueue.main) { rawResponses -> Promise in + var promises: [Promise] = [] + for rawResponse in rawResponses { let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) - let promises = messages.compactMap { json -> Promise? in + let jobPromises = messages.compactMap { json -> Promise? in // Use a best attempt approach here; we don't want to fail the entire process if one of the // messages failed to parse. guard let envelope = SNProtoEnvelope.from(json), @@ -95,12 +87,10 @@ public final class BackgroundPoller : NSObject { } // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.defaultNamespace, associatedWith: publicKey, from: lastRawMessage) - - return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects + promises += jobPromises } - promises.append(promise) + return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } - return when(resolved: promises).map { _ in } } } } From 510d7c22a2c660bfa230dfcac3c1ed8c52665178 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 9 May 2022 14:15:08 +1000 Subject: [PATCH 18/19] fix background poller updating last hash issue. --- Session/Utilities/BackgroundPoller.swift | 33 ++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 6c0802ced..f2f0d5f41 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -64,30 +64,37 @@ public final class BackgroundPoller : NSObject { guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { var promises: [SnodeAPI.RawResponsePromise] = [] + var namespaces: [Int] = [] // We have to poll for both namespace 0 and -10 when hardfork == 19 && softfork == 0 if SnodeAPI.hardfork <= 19, SnodeAPI.softfork == 0 { let promise = SnodeAPI.getRawClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: publicKey) promises.append(promise) + namespaces.append(SnodeAPI.defaultNamespace) } if SnodeAPI.hardfork >= 19 && SnodeAPI.softfork >= 0 { let promise = SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey, authenticated: false) promises.append(promise) + namespaces.append(SnodeAPI.closedGroupNamespace) } - return when(resolved: promises).then(on: DispatchQueue.main) { rawResponses -> Promise in + return when(resolved: promises).then(on: DispatchQueue.main) { results -> Promise in var promises: [Promise] = [] - for rawResponse in rawResponses { - let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) - let jobPromises = messages.compactMap { json -> Promise? in - // Use a best attempt approach here; we don't want to fail the entire process if one of the - // messages failed to parse. - guard let envelope = SNProtoEnvelope.from(json), - let data = try? envelope.serializedData() else { return nil } - let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) - return job.execute() + var index = 0 + for result in results { + if case .fulfilled(let rawResponse) = result { + let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) + let jobPromises = messages.compactMap { json -> Promise? in + // Use a best attempt approach here; we don't want to fail the entire process if one of the + // messages failed to parse. + guard let envelope = SNProtoEnvelope.from(json), + let data = try? envelope.serializedData() else { return nil } + let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) + return job.execute() + } + // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value + SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: namespaces[index], associatedWith: publicKey, from: lastRawMessage) + promises += jobPromises } - // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, namespace: SnodeAPI.defaultNamespace, associatedWith: publicKey, from: lastRawMessage) - promises += jobPromises + index += 1 } return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } From a337123cbbaee168142911cb3fafeb2dc8e9a790 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 10 May 2022 14:25:14 +1000 Subject: [PATCH 19/19] only update the harkfork version when it is increasing --- SessionSnodeKit/SnodeAPI.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 35dc729b5..fd669ea17 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -145,10 +145,14 @@ public final class SnodeAPI : NSObject { return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey) .map2 { json in if let hf = json["hf"] as? [Int] { - if hf[0] != hardfork || hf[1] != softfork { - hardfork = hf[0] + if hf[1] > softfork { softfork = hf[1] + UserDefaults.standard[.softfork] = softfork + } + if hf[0] > hardfork { + hardfork = hf[0] UserDefaults.standard[.hardfork] = hardfork + softfork = hf[1] UserDefaults.standard[.softfork] = softfork } }