diff --git a/Pods b/Pods index 960a820c7..8b30c2d91 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 960a820c76a95a64cfb1c6ad721c68f73a8f27b9 +Subproject commit 8b30c2d91fe7f9743350dd30521b5ca74e78766c diff --git a/Signal/src/AppDelegate.h b/Signal/src/AppDelegate.h index 0730d665f..762c9df57 100644 --- a/Signal/src/AppDelegate.h +++ b/Signal/src/AppDelegate.h @@ -8,6 +8,7 @@ extern NSString *const AppDelegateStoryboardMain; @interface AppDelegate : UIResponder +- (void)stopLongPollerIfNeeded; - (void)createGroupChatsIfNeeded; - (void)createRSSFeedsIfNeeded; - (void)startGroupChatPollersIfNeeded; diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 2d28aebda..49c6150a7 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -63,7 +63,10 @@ static NSTimeInterval launchStartedAt; @property (nonatomic) BOOL hasInitialRootViewController; @property (nonatomic) BOOL areVersionMigrationsComplete; @property (nonatomic) BOOL didAppLaunchFail; + +// Loki @property (nonatomic) LKP2PServer *lokiP2PServer; +@property (nonatomic) LKLongPoller *lokiLongPoller; @property (nonatomic) LKGroupChatPoller *lokiPublicChatPoller; @property (nonatomic) LKRSSFeedPoller *lokiNewsFeedPoller; @property (nonatomic) LKRSSFeedPoller *lokiMessengerUpdatesFeedPoller; @@ -175,7 +178,7 @@ static NSTimeInterval launchStartedAt; [DDLog flushLog]; - [LKAPI stopLongPolling]; + [self stopLongPollerIfNeeded]; } - (void)applicationWillEnterForeground:(UIApplication *)application @@ -194,7 +197,8 @@ static NSTimeInterval launchStartedAt; [DDLog flushLog]; - [LKAPI stopLongPolling]; + [self stopLongPollerIfNeeded]; + if (self.lokiP2PServer) { [self.lokiP2PServer stop]; } } @@ -761,7 +765,7 @@ static NSTimeInterval launchStartedAt; [Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized]; // Loki: Start long polling - [LKAPI startLongPollingIfNeeded]; + [self startLongPollerIfNeeded]; // Loki: Tell our friends that we are online [LKP2PAPI broadcastOnlineStatus]; @@ -1359,8 +1363,8 @@ static NSTimeInterval launchStartedAt; // For non-legacy users, read receipts are on by default. [self.readReceiptManager setAreReadReceiptsEnabled:YES]; - // Start long polling - [LKAPI startLongPollingIfNeeded]; + // Loki: Start long polling + [self startLongPollerIfNeeded]; } } @@ -1406,23 +1410,6 @@ static NSTimeInterval launchStartedAt; [UIViewController attemptRotationToDeviceOrientation]; } -#pragma mark - Long polling - -- (void)handleNewMessagesReceived:(NSNotification *)notification -{ - NSArray *messages = (NSArray *)notification.userInfo[@"messages"]; - NSLog(@"[Loki] Received %lu messages through long polling.", messages.count); - - for (SSKProtoEnvelope *envelope in messages) { - NSData *envelopeData = [envelope serializedDataAndReturnError:nil]; - if (envelopeData != nil) { - [SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:envelopeData]; - } else { - OWSFailDebug(@"Failed to deserialize envelope."); - } - } -} - #pragma mark - status bar touches - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event @@ -1487,6 +1474,34 @@ static NSTimeInterval launchStartedAt; #pragma mark - Loki +- (void)setUpLongPollerIfNeeded +{ + if (self.lokiLongPoller != nil) { return; } + NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; + if (userHexEncodedPublicKey == nil) { return; } + self.lokiLongPoller = [[LKLongPoller alloc] initOnMessagesReceived:^(NSArray *messages) { + for (SSKProtoEnvelope *message in messages) { + NSData *data = [message serializedDataAndReturnError:nil]; + if (data != nil) { + [SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:data]; + } else { + NSLog(@"[Loki] Failed to deserialize envelope."); + } + } + }]; +} + +- (void)startLongPollerIfNeeded +{ + [self setUpLongPollerIfNeeded]; + [self.lokiLongPoller startIfNeeded]; +} + +- (void)stopLongPollerIfNeeded +{ + [self.lokiLongPoller stopIfNeeded]; +} + - (LKGroupChat *)lokiPublicChat { return [[LKGroupChat alloc] initWithServerID:@(LKGroupChatAPI.publicChatServerID).unsignedIntegerValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true]; diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 080644cde..5ff7add30 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -7,7 +7,7 @@ public final class LokiGroupChatPoller : NSObject { private var hasStarted = false private let pollForNewMessagesInterval: TimeInterval = 4 - private let pollForDeletedMessagesInterval: TimeInterval = 32 * 60 + private let pollForDeletedMessagesInterval: TimeInterval = 20 @objc(initForGroup:) public init(for group: LokiGroupChat) { @@ -44,6 +44,10 @@ public final class LokiGroupChatPoller : NSObject { x2.setTimestamp(message.timestamp) x2.setGroup(try! x1.build()) x2.setBody(message.body) + let messageServerID = message.serverID! + let publicChatInfo = SSKProtoPublicChatInfo.builder() + publicChatInfo.setServerID(messageServerID) + x2.setPublicChatInfo(try! publicChatInfo.build()) let x3 = SSKProtoContent.builder() x3.setDataMessage(try! x2.build()) let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp) diff --git a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m index 893354032..c1119fd2c 100644 --- a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m @@ -2,6 +2,7 @@ // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // +#import "AppDelegate.h" #import "AppSettingsViewController.h" #import "AboutTableViewController.h" #import "AdvancedSettingsTableViewController.h" @@ -533,7 +534,8 @@ [ThreadUtil deleteAllContent]; [SSKEnvironment.shared.identityManager clearIdentityKey]; [LKAPI clearRandomSnodePool]; - [LKAPI stopLongPolling]; + AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate; + [appDelegate stopLongPollerIfNeeded]; [SSKEnvironment.shared.tsAccountManager resetForReregistration]; UIViewController *rootViewController = [[OnboardingController new] initialViewController]; OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:rootViewController]; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index a43bb6a5e..d92171b60 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -680,7 +680,8 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { [ThreadUtil deleteAllContent]; [SSKEnvironment.shared.identityManager clearIdentityKey]; [LKAPI clearRandomSnodePool]; - [LKAPI stopLongPolling]; + AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate; + [appDelegate stopLongPollerIfNeeded]; [SSKEnvironment.shared.tsAccountManager resetForReregistration]; UIViewController *rootViewController = [[OnboardingController new] initialViewController]; OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:rootViewController]; diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto index 7c92ad5f7..483fd5aad 100644 --- a/SignalServiceKit/protobuf/SignalService.proto +++ b/SignalServiceKit/protobuf/SignalService.proto @@ -253,6 +253,7 @@ message DataMessage { repeated Contact contact = 9; repeated Preview preview = 10; optional LokiProfile profile = 101; // Loki: The current user's profile + optional PublicChatInfo publicChatInfo = 999; // Loki: Internal public chat info } message NullMessage { @@ -421,3 +422,8 @@ message GroupDetails { optional string color = 7; optional bool blocked = 8; } + +// Internal - DO NOT SEND +message PublicChatInfo { + optional uint64 serverID = 1; +} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Convenience.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Convenience.swift deleted file mode 100644 index 0ebca88e4..000000000 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Convenience.swift +++ /dev/null @@ -1,49 +0,0 @@ -import PromiseKit - -internal extension LokiAPI { - - private static let receivedMessageHashValuesKey = "receivedMessageHashValuesKey" - private static let receivedMessageHashValuesCollection = "receivedMessageHashValuesCollection" - - internal static func getLastMessageHashValue(for target: LokiAPITarget) -> String? { - var result: String? = nil - // Uses a read/write connection because getting the last message hash value also removes expired messages as needed - // TODO: This shouldn't be the case; a getter shouldn't have an unexpected side effect - storage.dbReadWriteConnection.readWrite { transaction in - result = storage.getLastMessageHash(forServiceNode: target.address, transaction: transaction) - } - return result - } - - internal static func setLastMessageHashValue(for target: LokiAPITarget, hashValue: String, expirationDate: UInt64) { - storage.dbReadWriteConnection.readWrite { transaction in - storage.setLastMessageHash(forServiceNode: target.address, hash: hashValue, expiresAt: expirationDate, transaction: transaction) - } - } - - internal static func getReceivedMessageHashValues() -> Set? { - var result: Set? = nil - storage.dbReadConnection.read { transaction in - result = transaction.object(forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) as! Set? - } - return result - } - - internal static func setReceivedMessageHashValues(to receivedMessageHashValues: Set) { - storage.dbReadWriteConnection.readWrite { transaction in - transaction.setObject(receivedMessageHashValues, forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) - } - } -} - -internal extension Promise { - - internal func recoveringNetworkErrorsIfNeeded() -> Promise { - return recover() { error -> Promise in - switch error { - case NetworkManagerError.taskError(_, let underlyingError): throw underlyingError - default: throw error - } - } - } -} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift b/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift deleted file mode 100644 index 70554fd51..000000000 --- a/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift +++ /dev/null @@ -1,116 +0,0 @@ -import PromiseKit - -private typealias Callback = () -> Void - -public extension LokiAPI { - private static var isLongPolling = false - private static var shouldStopPolling = false - private static var usedSnodes = [LokiAPITarget]() - private static var cancels = [Callback]() - - /// Start long polling. - /// This will send a notification if new messages were received - @objc public static func startLongPollingIfNeeded() { - guard !isLongPolling else { return } - isLongPolling = true - shouldStopPolling = false - - print("[Loki] Started long polling.") - - longPoll() - } - - /// Stop long polling - @objc public static func stopLongPolling() { - shouldStopPolling = true - isLongPolling = false - usedSnodes.removeAll() - cancelAllPromises() - - print("[Loki] Stopped long polling.") - } - - /// The long polling loop - private static func longPoll() { - // This is here so we can stop the infinite loop - guard !shouldStopPolling else { return } - - getSwarm(for: userHexEncodedPublicKey).then { _ -> Guarantee<[Result]> in - var promises = [Promise]() - let connections = 3 - for i in 0.. [LokiAPITarget] { - let snodes = LokiAPI.swarmCache[userHexEncodedPublicKey] ?? [] - return snodes.filter { !usedSnodes.contains($0) } - } - - /// Open a connection to an unused snode and get messages from it - private static func openConnection() -> (Promise, cancel: Callback) { - var isCancelled = false - - let cancel = { - isCancelled = true - } - - func connectToNextSnode() -> Promise { - guard let nextSnode = getUnusedSnodes().first else { - // We don't have anymore unused snodes - return Promise.value(()) - } - - // Add the snode to the used array - usedSnodes.append(nextSnode) - - func getMessagesInfinitely(from target: LokiAPITarget) -> Promise { - // The only way to exit the infinite loop is to throw an error 3 times or cancel - return getRawMessages(from: target, usingLongPolling: true).then { rawResponse -> Promise in - // Check if we need to abort - guard !isCancelled else { throw PMKError.cancelled } - - // Process the messages - let messages = parseRawMessagesResponse(rawResponse, from: target) - - // Send our messages as a notification - NotificationCenter.default.post(name: .newMessagesReceived, object: nil, userInfo: ["messages": messages]) - - // Continue fetching if we haven't cancelled - return getMessagesInfinitely(from: target) - }.retryingIfNeeded(maxRetryCount: 3) - } - - // Keep getting messages for this snode - // If we errored out then connect to the next snode - return getMessagesInfinitely(from: nextSnode).recover { _ -> Promise in - // Cancelled, so just return successfully - guard !isCancelled else { return Promise.value(()) } - - // Connect to the next snode if we haven't cancelled - // We also need to remove the cached snode so we don't contact it again - dropIfNeeded(nextSnode, hexEncodedPublicKey: userHexEncodedPublicKey) - return connectToNextSnode() - } - } - - // Keep connecting to snodes - return (connectToNextSnode(), cancel) - } -} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 04e74f481..c81a39589 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -173,4 +173,51 @@ public final class LokiAPI : NSObject { return envelope } } + + // MARK: Caching + private static let receivedMessageHashValuesKey = "receivedMessageHashValuesKey" + private static let receivedMessageHashValuesCollection = "receivedMessageHashValuesCollection" + + private static func getLastMessageHashValue(for target: LokiAPITarget) -> String? { + var result: String? = nil + // Uses a read/write connection because getting the last message hash value also removes expired messages as needed + // TODO: This shouldn't be the case; a getter shouldn't have an unexpected side effect + storage.dbReadWriteConnection.readWrite { transaction in + result = storage.getLastMessageHash(forServiceNode: target.address, transaction: transaction) + } + return result + } + + private static func setLastMessageHashValue(for target: LokiAPITarget, hashValue: String, expirationDate: UInt64) { + storage.dbReadWriteConnection.readWrite { transaction in + storage.setLastMessageHash(forServiceNode: target.address, hash: hashValue, expiresAt: expirationDate, transaction: transaction) + } + } + + private static func getReceivedMessageHashValues() -> Set? { + var result: Set? = nil + storage.dbReadConnection.read { transaction in + result = transaction.object(forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) as! Set? + } + return result + } + + private static func setReceivedMessageHashValues(to receivedMessageHashValues: Set) { + storage.dbReadWriteConnection.readWrite { transaction in + transaction.setObject(receivedMessageHashValues, forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) + } + } +} + +// MARK: Error Handling +private extension Promise { + + fileprivate func recoveringNetworkErrorsIfNeeded() -> Promise { + return recover() { error -> Promise in + switch error { + case NetworkManagerError.taskError(_, let underlyingError): throw underlyingError + default: throw error + } + } + } } diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift index bd7ca5a9c..9b24ea586 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift @@ -28,13 +28,13 @@ public final class LokiGroupChatAPI : NSObject { // MARK: Error public enum Error : Swift.Error { - case tokenParsingFailed, tokenDecryptionFailed, messageParsingFailed + case tokenParsingFailed, tokenDecryptionFailed, messageParsingFailed, deletionParsingFailed } // MARK: Database private static let authTokenCollection = "LokiGroupChatAuthTokenCollection" private static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection" - private static let firstMessageServerIDCollection = "LokiGroupChatFirstMessageServerIDCollection" + private static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection" private static func getAuthTokenFromDatabase(for server: String) -> String? { var result: String? = nil @@ -64,17 +64,17 @@ public final class LokiGroupChatAPI : NSObject { } } - private static func getFirstMessageServerID(for group: UInt64, on server: String) -> UInt? { + private static func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt? { var result: UInt? = nil storage.dbReadConnection.read { transaction in - result = transaction.object(forKey: "\(server).\(group)", inCollection: firstMessageServerIDCollection) as! UInt? + result = transaction.object(forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection) as! UInt? } return result } - private static func setFirstMessageServerID(for group: UInt64, on server: String, to newValue: UInt64) { + private static func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64) { storage.dbReadWriteConnection.readWrite { transaction in - transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: firstMessageServerIDCollection) + transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection) } } @@ -147,9 +147,7 @@ public final class LokiGroupChatAPI : NSObject { } guard hexEncodedPublicKey != userHexEncodedPublicKey else { return nil } let lastMessageServerID = getLastMessageServerID(for: group, on: server) - let firstMessageServerID = getFirstMessageServerID(for: group, on: server) if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: group, on: server, to: serverID) } - if serverID < (firstMessageServerID ?? UInt.max) { setFirstMessageServerID(for: group, on: server, to: serverID) } return LokiGroupMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp) } } @@ -186,22 +184,27 @@ public final class LokiGroupChatAPI : NSObject { public static func getDeletedMessageServerIDs(for group: UInt64, on server: String) -> Promise<[UInt64]> { print("[Loki] Getting deleted messages for group chat with ID: \(group) on server: \(server).") - let firstMessageServerID = getFirstMessageServerID(for: group, on: server) ?? 0 - let queryParameters = "is_deleted=true&since_id=\(firstMessageServerID)" - let url = URL(string: "\(server)/channels/\(group)/messages?\(queryParameters)")! + let queryParameters: String + if let lastDeletionServerID = getLastDeletionServerID(for: group, on: server) { + queryParameters = "since_id=\(lastDeletionServerID)" + } else { + queryParameters = "count=\(fallbackBatchCount)" + } + let url = URL(string: "\(server)/loki/v1/channel/\(group)/deletes?\(queryParameters)")! let request = TSRequest(url: url) return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in - guard let json = rawResponse as? JSON, let rawMessages = json["data"] as? [JSON] else { + guard let json = rawResponse as? JSON, let deletions = json["data"] as? [JSON] else { print("[Loki] Couldn't parse deleted messages for group chat with ID: \(group) on server: \(server) from: \(rawResponse).") - throw Error.messageParsingFailed + throw Error.deletionParsingFailed } - return rawMessages.flatMap { message in - guard let serverID = message["id"] as? UInt64 else { - print("[Loki] Couldn't parse deleted message for group chat with ID: \(group) on server: \(server) from: \(message).") + return deletions.flatMap { deletion in + guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else { + print("[Loki] Couldn't parse deleted message for group chat with ID: \(group) on server: \(server) from: \(deletion).") return nil } - let isDeleted = (message["is_deleted"] as? Bool ?? false) - return isDeleted ? serverID : nil + let lastDeletionServerID = getLastDeletionServerID(for: group, on: server) + if serverID > (lastDeletionServerID ?? 0) { setLastDeletionServerID(for: group, on: server, to: serverID) } + return messageServerID } } } diff --git a/SignalServiceKit/src/Loki/API/LokiLongPoller.swift b/SignalServiceKit/src/Loki/API/LokiLongPoller.swift new file mode 100644 index 000000000..1acb6f7db --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiLongPoller.swift @@ -0,0 +1,90 @@ +import PromiseKit + +@objc(LKLongPoller) +public final class LokiLongPoller : NSObject { + private let onMessagesReceived: ([SSKProtoEnvelope]) -> Void + private let storage = OWSPrimaryStorage.shared() + private var hasStarted = false + private var hasStopped = false + private var connections = Set>() + private var usedSnodes = Set() + + // MARK: Settings + private let connectionCount = 3 + private let retryInterval: TimeInterval = 4 + + // MARK: Convenience + private var userHexEncodedPublicKey: String { return OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey } + + // MARK: Initialization + @objc public init(onMessagesReceived: @escaping ([SSKProtoEnvelope]) -> Void) { + self.onMessagesReceived = onMessagesReceived + super.init() + } + + // MARK: Public API + @objc public func startIfNeeded() { + guard !hasStarted else { return } + print("[Loki] Started long polling.") + hasStarted = true + hasStopped = false + openConnections() + } + + @objc public func stopIfNeeded() { + guard !hasStopped else { return } + print("[Loki] Stopped long polling.") + hasStarted = false + hasStopped = true + usedSnodes.removeAll() + } + + // MARK: Private API + private func openConnections() { + guard !hasStopped else { return } + LokiAPI.getSwarm(for: userHexEncodedPublicKey).then { [weak self] _ -> Guarantee<[Result]> in + guard let strongSelf = self else { return Guarantee.value([Result]()) } + strongSelf.usedSnodes.removeAll() + let connections: [Promise] = (0...pending() + strongSelf.openConnectionToNextSnode(seal: seal) + return promise + } + strongSelf.connections = Set(connections) + return when(resolved: connections) + }.ensure { [weak self] in + guard let strongSelf = self else { return } + Timer.scheduledTimer(withTimeInterval: strongSelf.retryInterval, repeats: false) { _ in + guard let strongSelf = self else { return } + strongSelf.openConnections() + } + } + } + + private func openConnectionToNextSnode(seal: Resolver) { + let swarm = LokiAPI.swarmCache[userHexEncodedPublicKey] ?? [] + let userHexEncodedPublicKey = self.userHexEncodedPublicKey + let unusedSnodes = Set(swarm).subtracting(usedSnodes) + if !unusedSnodes.isEmpty { + let nextSnode = unusedSnodes.randomElement()! + usedSnodes.insert(nextSnode) + print("[Loki] Opening long polling connection to \(nextSnode).") + longPoll(nextSnode, seal: seal).catch { [weak self] error in + print("[Loki] Long polling connection to \(nextSnode) failed; dropping it and switching to next snode.") + LokiAPI.dropIfNeeded(nextSnode, hexEncodedPublicKey: userHexEncodedPublicKey) + self?.openConnectionToNextSnode(seal: seal) + } + } else { + seal.fulfill(()) + } + } + + private func longPoll(_ target: LokiAPITarget, seal: Resolver) -> Promise { + return LokiAPI.getRawMessages(from: target, usingLongPolling: true).then { [weak self] rawResponse -> Promise in + guard let strongSelf = self, !strongSelf.hasStopped else { return Promise.value(()) } + let messages = LokiAPI.parseRawMessagesResponse(rawResponse, from: target) + strongSelf.onMessagesReceived(messages) + return strongSelf.longPoll(target, seal: seal) + } + } +} diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 394535ec9..1350a1af5 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1401,6 +1401,11 @@ NS_ASSUME_NONNULL_BEGIN thread:oldGroupThread envelope:envelope transaction:transaction]; + + if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) { + [self.primaryStorage setIDForMessageWithServerID:dataMessage.publicChatInfo.serverID to:incomingMessage.uniqueId in:transaction]; + } + return incomingMessage; } default: { diff --git a/SignalServiceKit/src/Protos/Generated/SSKProto.swift b/SignalServiceKit/src/Protos/Generated/SSKProto.swift index c1e3c4657..a54585ac9 100644 --- a/SignalServiceKit/src/Protos/Generated/SSKProto.swift +++ b/SignalServiceKit/src/Protos/Generated/SSKProto.swift @@ -3308,6 +3308,9 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { if let _value = profile { builder.setProfile(_value) } + if let _value = publicChatInfo { + builder.setPublicChatInfo(_value) + } return builder } @@ -3379,6 +3382,10 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { proto.profile = valueParam.proto } + @objc public func setPublicChatInfo(_ valueParam: SSKProtoPublicChatInfo) { + proto.publicChatInfo = valueParam.proto + } + @objc public func build() throws -> SSKProtoDataMessage { return try SSKProtoDataMessage.parseProto(proto) } @@ -3402,6 +3409,8 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { @objc public let profile: SSKProtoDataMessageLokiProfile? + @objc public let publicChatInfo: SSKProtoPublicChatInfo? + @objc public var body: String? { guard proto.hasBody else { return nil @@ -3449,7 +3458,8 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { quote: SSKProtoDataMessageQuote?, contact: [SSKProtoDataMessageContact], preview: [SSKProtoDataMessagePreview], - profile: SSKProtoDataMessageLokiProfile?) { + profile: SSKProtoDataMessageLokiProfile?, + publicChatInfo: SSKProtoPublicChatInfo?) { self.proto = proto self.attachments = attachments self.group = group @@ -3457,6 +3467,7 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { self.contact = contact self.preview = preview self.profile = profile + self.publicChatInfo = publicChatInfo } @objc @@ -3494,6 +3505,11 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { profile = try SSKProtoDataMessageLokiProfile.parseProto(proto.profile) } + var publicChatInfo: SSKProtoPublicChatInfo? = nil + if proto.hasPublicChatInfo { + publicChatInfo = try SSKProtoPublicChatInfo.parseProto(proto.publicChatInfo) + } + // MARK: - Begin Validation Logic for SSKProtoDataMessage - // MARK: - End Validation Logic for SSKProtoDataMessage - @@ -3504,7 +3520,8 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { quote: quote, contact: contact, preview: preview, - profile: profile) + profile: profile, + publicChatInfo: publicChatInfo) return result } @@ -6215,3 +6232,94 @@ extension SSKProtoGroupDetails.SSKProtoGroupDetailsBuilder { } #endif + +// MARK: - SSKProtoPublicChatInfo + +@objc public class SSKProtoPublicChatInfo: NSObject { + + // MARK: - SSKProtoPublicChatInfoBuilder + + @objc public class func builder() -> SSKProtoPublicChatInfoBuilder { + return SSKProtoPublicChatInfoBuilder() + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SSKProtoPublicChatInfoBuilder { + let builder = SSKProtoPublicChatInfoBuilder() + if hasServerID { + builder.setServerID(serverID) + } + return builder + } + + @objc public class SSKProtoPublicChatInfoBuilder: NSObject { + + private var proto = SignalServiceProtos_PublicChatInfo() + + @objc fileprivate override init() {} + + @objc public func setServerID(_ valueParam: UInt64) { + proto.serverID = valueParam + } + + @objc public func build() throws -> SSKProtoPublicChatInfo { + return try SSKProtoPublicChatInfo.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SSKProtoPublicChatInfo.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SignalServiceProtos_PublicChatInfo + + @objc public var serverID: UInt64 { + return proto.serverID + } + @objc public var hasServerID: Bool { + return proto.hasServerID + } + + private init(proto: SignalServiceProtos_PublicChatInfo) { + self.proto = proto + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoPublicChatInfo { + let proto = try SignalServiceProtos_PublicChatInfo(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SignalServiceProtos_PublicChatInfo) throws -> SSKProtoPublicChatInfo { + // MARK: - Begin Validation Logic for SSKProtoPublicChatInfo - + + // MARK: - End Validation Logic for SSKProtoPublicChatInfo - + + let result = SSKProtoPublicChatInfo(proto: proto) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SSKProtoPublicChatInfo { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SSKProtoPublicChatInfo.SSKProtoPublicChatInfoBuilder { + @objc public func buildIgnoringErrors() -> SSKProtoPublicChatInfo? { + return try! self.build() + } +} + +#endif diff --git a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift index 8f2176d04..bf0fea786 100644 --- a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift +++ b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift @@ -802,6 +802,16 @@ struct SignalServiceProtos_DataMessage { /// Clears the value of `profile`. Subsequent reads from it will return its default value. mutating func clearProfile() {_uniqueStorage()._profile = nil} + /// Loki: Internal public chat info + var publicChatInfo: SignalServiceProtos_PublicChatInfo { + get {return _storage._publicChatInfo ?? SignalServiceProtos_PublicChatInfo()} + set {_uniqueStorage()._publicChatInfo = newValue} + } + /// Returns true if `publicChatInfo` has been explicitly set. + var hasPublicChatInfo: Bool {return _storage._publicChatInfo != nil} + /// Clears the value of `publicChatInfo`. Subsequent reads from it will return its default value. + mutating func clearPublicChatInfo() {_uniqueStorage()._publicChatInfo = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum Flags: SwiftProtobuf.Enum { @@ -2492,6 +2502,28 @@ struct SignalServiceProtos_GroupDetails { fileprivate var _storage = _StorageClass.defaultInstance } +/// Internal - DO NOT SEND +struct SignalServiceProtos_PublicChatInfo { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var serverID: UInt64 { + get {return _serverID ?? 0} + set {_serverID = newValue} + } + /// Returns true if `serverID` has been explicitly set. + var hasServerID: Bool {return self._serverID != nil} + /// Clears the value of `serverID`. Subsequent reads from it will return its default value. + mutating func clearServerID() {self._serverID = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _serverID: UInt64? = nil +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "SignalServiceProtos" @@ -3146,6 +3178,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf. 9: .same(proto: "contact"), 10: .same(proto: "preview"), 101: .same(proto: "profile"), + 999: .same(proto: "publicChatInfo"), ] fileprivate class _StorageClass { @@ -3160,6 +3193,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf. var _contact: [SignalServiceProtos_DataMessage.Contact] = [] var _preview: [SignalServiceProtos_DataMessage.Preview] = [] var _profile: SignalServiceProtos_DataMessage.LokiProfile? = nil + var _publicChatInfo: SignalServiceProtos_PublicChatInfo? = nil static let defaultInstance = _StorageClass() @@ -3177,6 +3211,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf. _contact = source._contact _preview = source._preview _profile = source._profile + _publicChatInfo = source._publicChatInfo } } @@ -3203,6 +3238,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf. case 9: try decoder.decodeRepeatedMessageField(value: &_storage._contact) case 10: try decoder.decodeRepeatedMessageField(value: &_storage._preview) case 101: try decoder.decodeSingularMessageField(value: &_storage._profile) + case 999: try decoder.decodeSingularMessageField(value: &_storage._publicChatInfo) default: break } } @@ -3244,6 +3280,9 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf. if let v = _storage._profile { try visitor.visitSingularMessageField(value: v, fieldNumber: 101) } + if let v = _storage._publicChatInfo { + try visitor.visitSingularMessageField(value: v, fieldNumber: 999) + } } try unknownFields.traverse(visitor: &visitor) } @@ -3264,6 +3303,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf. if _storage._contact != rhs_storage._contact {return false} if _storage._preview != rhs_storage._preview {return false} if _storage._profile != rhs_storage._profile {return false} + if _storage._publicChatInfo != rhs_storage._publicChatInfo {return false} return true } if !storagesAreEqual {return false} @@ -5113,3 +5153,32 @@ extension SignalServiceProtos_GroupDetails.Avatar: SwiftProtobuf.Message, SwiftP return true } } + +extension SignalServiceProtos_PublicChatInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PublicChatInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "serverID"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularUInt64Field(value: &self._serverID) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._serverID { + try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SignalServiceProtos_PublicChatInfo, rhs: SignalServiceProtos_PublicChatInfo) -> Bool { + if lhs._serverID != rhs._serverID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +}