diff --git a/Pods b/Pods index c8f9b28f5..7005b4be3 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit c8f9b28f577a03f9f03a0b2e38e125811785ee29 +Subproject commit 7005b4be3cd1596fcf644356aea526663cd8ac67 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 933952dd7..2de5c7df3 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -320,11 +320,13 @@ static NSTimeInterval launchStartedAt; if (self.lokiP2PServer.isRunning) { break; } BOOL isStarted = [self.lokiP2PServer startOnPort:port.unsignedIntegerValue]; if (isStarted) { - NSString *serverURL = self.lokiP2PServer.serverURL.absoluteString; - if ([serverURL hasSuffix:@"/"]) { - serverURL = [serverURL substringToIndex:serverURL.length - 1]; + NSURL *serverURL = self.lokiP2PServer.serverURL; + [LokiP2PManager setOurP2PAddressWithUrl:self.lokiP2PServer.serverURL]; + NSString *serverURLDescription = serverURL.absoluteString; + if ([serverURLDescription hasSuffix:@"/"]) { + serverURLDescription = [serverURLDescription substringToIndex:serverURLDescription.length - 1]; } - OWSLogInfo(@"[Loki] Started server at %@.", serverURL); + OWSLogInfo(@"[Loki] Started server at %@.", serverURLDescription); break; } } @@ -755,7 +757,8 @@ static NSTimeInterval launchStartedAt; // Loki: Start poller [Poller.shared startIfNeeded]; - // TODO: Ping friends to let them know we're online + // Loki: Tell our friends that we are online + [LokiP2PManager broadcastOnlineStatus]; if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) { OWSLogInfo(@"Retrying to register for remote notifications since user hasn't registered yet."); diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift index 2d280d2d2..9a5fbf748 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift @@ -9,49 +9,58 @@ public extension LokiAPI { let data: LosslessStringConvertible /// The time to live for the message in milliseconds. let ttl: UInt64 - /// When the proof of work was calculated, if applicable. + /// Whether this message is a ping. + /// + /// - Note: The concept of pinging only applies to P2P messaging. + let isPing: Bool + /// When the proof of work was calculated, if applicable (P2P messages don't require proof of work). /// /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - let timestamp: UInt64? - /// The base 64 encoded proof of work, if applicable. - let nonce: String? + private(set) var timestamp: UInt64? = nil + /// The base 64 encoded proof of work, if applicable (P2P messages don't require proof of work). + private(set) var nonce: String? = nil - public init(destination: String, data: LosslessStringConvertible, ttl: UInt64, timestamp: UInt64?, nonce: String?) { + private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool) { self.destination = destination self.data = data self.ttl = ttl - self.timestamp = timestamp - self.nonce = nonce + self.isPing = isPing } /// Construct a `LokiMessage` from a `SignalMessage`. /// /// - Note: `timestamp` is the original message timestamp (i.e. `TSOutgoingMessage.timestamp`). - public static func from(signalMessage: SignalMessage, timestamp: UInt64, requiringPoW isPoWRequired: Bool) -> Promise { + public static func from(signalMessage: SignalMessage, with timestamp: UInt64) -> Message? { // To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object + do { + let wrappedMessage = try LokiMessageWrapper.wrap(message: signalMessage, timestamp: timestamp) + let data = wrappedMessage.base64EncodedString() + let destination = signalMessage["destination"] as! String + var ttl = LokiAPI.defaultMessageTTL + if let messageTTL = signalMessage["ttl"] as! UInt?, messageTTL > 0 { ttl = UInt64(messageTTL) } + let isPing = signalMessage["isPing"] as! Bool + return Message(destination: destination, data: data, ttl: ttl, isPing: isPing) + } catch let error { + Logger.debug("[Loki] Failed to convert Signal message to Loki message: \(signalMessage).") + return nil + } + } + + /// Calculate the proof of work for this message. + /// + /// - Returns: The promise of a new message with its `timestamp` and `nonce` set. + public func calculatePoW() -> Promise { return Promise { seal in DispatchQueue.global(qos: .default).async { - do { - let wrappedMessage = try LokiMessageWrapper.wrap(message: signalMessage, timestamp: timestamp) - let data = wrappedMessage.base64EncodedString() - let destination = signalMessage["destination"] as! String - var ttl = LokiAPI.defaultMessageTTL - if let messageTTL = signalMessage["ttl"] as? UInt, messageTTL > 0 { ttl = UInt64(messageTTL) } - if isPoWRequired { - // The storage server takes a time interval in milliseconds - let now = NSDate.ows_millisecondTimeStamp() - if let nonce = ProofOfWork.calculate(data: data, pubKey: destination, timestamp: now, ttl: ttl) { - let result = Message(destination: destination, data: data, ttl: ttl, timestamp: now, nonce: nonce) - seal.fulfill(result) - } else { - seal.reject(Error.proofOfWorkCalculationFailed) - } - } else { - let result = Message(destination: destination, data: data, ttl: ttl, timestamp: nil, nonce: nil) - seal.fulfill(result) - } - } catch let error { - seal.reject(error) + let now = NSDate.ows_millisecondTimeStamp() + let dataAsString = self.data as! String // Safe because of how from(signalMessage:with:) is implemented + if let nonce = ProofOfWork.calculate(data: dataAsString, pubKey: self.destination, timestamp: now, ttl: self.ttl) { + var result = self + result.timestamp = now + result.nonce = nonce + seal.fulfill(result) + } else { + seal.reject(Error.proofOfWorkCalculationFailed) } } } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 906d800ec..c08212d23 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -5,8 +5,8 @@ import PromiseKit // MARK: Settings private static let version = "v1" - public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 * 1000 private static let maxRetryCount: UInt = 3 + public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 * 1000 // MARK: Types public typealias RawResponse = Any @@ -14,15 +14,18 @@ import PromiseKit public enum Error : LocalizedError { /// Only applicable to snode targets as proof of work isn't required for P2P messaging. case proofOfWorkCalculationFailed + case messageConversionFailed public var errorDescription: String? { switch self { case .proofOfWorkCalculationFailed: return NSLocalizedString("Failed to calculate proof of work.", comment: "") + case .messageConversionFailed: return "Failed to convert Signal message to Loki message." } } } - public typealias MessagePromise = Promise<[SSKProtoEnvelope]> // To keep the return type of getMessages() readable + public typealias MessagePromise = Promise<[SSKProtoEnvelope]> + public typealias RawResponsePromise = Promise // MARK: Lifecycle override private init() { } @@ -47,39 +50,47 @@ import PromiseKit let newRawMessages = removeDuplicates(from: rawMessages) return parseProtoEnvelopes(from: newRawMessages) } - }.retryingIfNeeded(maxRetryCount: maxRetryCount).map { Set($0) } + }.map { Set($0) }.retryingIfNeeded(maxRetryCount: maxRetryCount) } - public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, timestamp: UInt64) -> Promise>> { - let isP2PMessagingPossible = false - return Message.from(signalMessage: signalMessage, timestamp: timestamp, requiringPoW: !isP2PMessagingPossible).then(sendMessage) - } - - public static func sendMessage(_ lokiMessage: Message) -> Promise>> { - let isP2PMessagingPossible = false - if isP2PMessagingPossible { - // TODO: Send using P2P protocol - } else { + public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> Promise> { + guard let lokiMessage = Message.from(signalMessage: signalMessage, with: timestamp) else { return Promise(error: Error.messageConversionFailed) } + let destination = lokiMessage.destination + func sendLokiMessage(_ lokiMessage: Message, to target: Target) -> RawResponsePromise { let parameters = lokiMessage.toJSON() - return getTargetSnodes(for: lokiMessage.destination).mapValues { invoke(.sendMessage, on: $0, associatedWith: lokiMessage.destination, parameters: parameters) } - .retryingIfNeeded(maxRetryCount: maxRetryCount).map { Set($0) } + return invoke(.sendMessage, on: target, associatedWith: destination, parameters: parameters) } - } - - public static func ping(_ hexEncodedPublicKey: String) -> Promise>> { - let isP2PMessagingPossible = false - if isP2PMessagingPossible { - // TODO: Send using P2P protocol + func sendLokiMessageUsingSwarmAPI() -> Promise> { + let powPromise = lokiMessage.calculatePoW() + let swarmPromise = getTargetSnodes(for: destination) + return when(fulfilled: powPromise, swarmPromise).map { lokiMessageWithPoW, swarm in + return Set(swarm.map { sendLokiMessage(lokiMessageWithPoW, to: $0) }) + } + } + if let p2pDetails = LokiP2PManager.getDetails(forContact: destination), (lokiMessage.isPing || p2pDetails.isOnline) { + return Promise.value([ p2pDetails.target ]).mapValues { sendLokiMessage(lokiMessage, to: $0) }.map { Set($0) }.retryingIfNeeded(maxRetryCount: maxRetryCount).get { _ in + LokiP2PManager.setOnline(true, forContact: destination) + }.recover { error -> Promise> in + LokiP2PManager.setOnline(false, forContact: destination) + if lokiMessage.isPing { + Logger.warn("[Loki] Failed to ping \(destination); marking contact as offline.") + if let nsError = error as? NSError { + nsError.isRetryable = false + throw nsError + } else { + throw error + } + } + return sendLokiMessageUsingSwarmAPI().retryingIfNeeded(maxRetryCount: maxRetryCount) + } } else { - let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey ] // TODO: Figure out correct parameters - return getTargetSnodes(for: hexEncodedPublicKey).mapValues { invoke(.sendMessage, on: $0, associatedWith: hexEncodedPublicKey, parameters: parameters) } - .retryingIfNeeded(maxRetryCount: maxRetryCount).map { Set($0) } + return sendLokiMessageUsingSwarmAPI().retryingIfNeeded(maxRetryCount: maxRetryCount) } } // MARK: Public API (Obj-C) @objc public static func objc_sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> AnyPromise { - let promise = sendSignalMessage(signalMessage, to: destination, timestamp: timestamp).mapValues { AnyPromise.from($0) }.map { Set($0) } + let promise = sendSignalMessage(signalMessage, to: destination, with: timestamp).mapValues { AnyPromise.from($0) }.map { Set($0) } return AnyPromise.from(promise) } diff --git a/SignalServiceKit/src/Loki/API/LokiP2PManager.swift b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift new file mode 100644 index 000000000..a244f025d --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift @@ -0,0 +1,201 @@ + +@objc public class LokiP2PManager : NSObject { + private static let storage = OWSPrimaryStorage.shared() + private static let messageSender = SSKEnvironment.shared.messageSender + private static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + + /// The amount of time before pinging when a user is set to offline + private static let offlinePingTime = 2 * kMinuteInterval + + /// A p2p state struct + internal struct P2PDetails { + var address: String + var port: UInt16 + var isOnline: Bool + var timerDuration: Double + var pingTimer: Timer? = nil + + var target: LokiAPI.Target { + return LokiAPI.Target(address: address, port: port) + } + } + + /// Our p2p address + private static var ourP2PAddress: LokiAPI.Target? = nil + + /// This is where we store the p2p details of our contacts + private static var contactP2PDetails = [String: P2PDetails]() + + // MARK: - Public functions + + /// Set our local P2P address + /// + /// - Parameter url: The url to our local server + @objc public static func setOurP2PAddress(url: URL) { + guard let scheme = url.scheme, let host = url.host, let port = url.port else { return } + let target = LokiAPI.Target(address: "\(scheme)://\(host)", port: UInt16(port)) + ourP2PAddress = target + } + + /// Ping a contact + /// + /// - Parameter pubKey: The contact hex pubkey + @objc(pingContact:) + public static func ping(contact pubKey: String) { + // Dispatch on the main queue so we escape any transaction blocks + DispatchQueue.main.async { + var contactThread: TSThread? = nil + storage.dbReadConnection.read { transaction in + contactThread = TSContactThread.getWithContactId(pubKey, transaction: transaction) + } + + guard let thread = contactThread else { + Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey).") + return + } + + guard let message = lokiAddressMessage(for: thread, isPing: true) else { + Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey).") + return + } + + messageSender.sendPromise(message: message).retainUntilComplete() + } + } + + /// Broadcast an online message to all our friends. + /// This shouldn't be called inside a transaction. + @objc public static func broadcastOnlineStatus() { + // Escape any transaction blocks + DispatchQueue.main.async { + let friendThreads = getAllFriendThreads() + for thread in friendThreads { + sendOnlineBroadcastMessage(forThread: thread) + } + } + } + + // MARK: - Internal functions + + /// Get the P2P details for the given contact. + /// + /// - Parameter pubKey: The contact hex pubkey + /// - Returns: The P2P Details or nil if they don't exist + internal static func getDetails(forContact pubKey: String) -> P2PDetails? { + return contactP2PDetails[pubKey] + } + + /// Get the `LokiAddressMessage` for the given thread. + /// + /// - Parameter thread: The contact thread. + /// - Returns: The `LokiAddressMessage` for that thread. + @objc public static func onlineBroadcastMessage(forThread thread: TSThread) -> LokiAddressMessage? { + return lokiAddressMessage(for: thread, isPing: false) + } + + /// Handle P2P logic when we receive a `LokiAddressMessage` + /// + /// - Parameters: + /// - pubKey: The other users pubKey + /// - address: The pther users p2p address + /// - port: The other users p2p port + /// - receivedThroughP2P: Wether we received the message through p2p + @objc internal static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt16, receivedThroughP2P: Bool) { + // Stagger the ping timers so that contacts don't ping each other at the same time + let timerDuration = pubKey < ourHexEncodedPubKey ? 1 * kMinuteInterval : 2 * kMinuteInterval + + // Get out current contact details + let oldContactDetails = contactP2PDetails[pubKey] + + // Set the new contact details + // A contact is always assumed to be offline unless the specific conditions below are met + let details = P2PDetails(address: address, port: port, isOnline: false, timerDuration: timerDuration, pingTimer: nil) + contactP2PDetails[pubKey] = details + + // Set up our checks + let oldContactExists = oldContactDetails != nil + let wasOnline = oldContactDetails?.isOnline ?? false + let p2pDetailsMatch = oldContactDetails?.address == address && oldContactDetails?.port == port + + /* + We need to check if we should ping the user. + We don't ping the user IF: + - We had old contact details + - We got a P2P message + - The old contact was set as `Online` + - The new p2p details match the old one + */ + if oldContactExists && receivedThroughP2P && wasOnline && p2pDetailsMatch { + setOnline(true, forContact: pubKey) + return + } + + /* + Ping the contact. + This happens in the following scenarios: + 1. We didn't have the contact, we need to ping them to let them know our details. + 2. wasP2PMessage = false, so we assume the contact doesn't have our details. + 3. We had the contact marked as offline, we need to make sure that we can reach their server. + 4. The other contact details have changed, we need to make sure that we can reach their new server. + */ + ping(contact: pubKey) + } + + /// Mark a contact as online or offline. + /// + /// - Parameters: + /// - isOnline: Whether to set the contact to online or offline. + /// - pubKey: The contact hexh pubKey + @objc internal static func setOnline(_ isOnline: Bool, forContact pubKey: String) { + // Make sure we are on the main thread + DispatchQueue.main.async { + guard var details = contactP2PDetails[pubKey] else { return } + + let interval = isOnline ? details.timerDuration : offlinePingTime + + // Setup a new timer + details.pingTimer?.invalidate() + details.pingTimer = WeakTimer.scheduledTimer(timeInterval: interval, target: self, userInfo: nil, repeats: true) { _ in ping(contact: pubKey) } + details.isOnline = isOnline + + contactP2PDetails[pubKey] = details + } + } + + // MARK: - Private functions + + private static func sendOnlineBroadcastMessage(forThread thread: TSContactThread) { + AssertIsOnMainThread() + + guard let message = onlineBroadcastMessage(forThread: thread) else { + owsFailDebug("P2P Address not set") + return + } + + messageSender.sendPromise(message: message).catch { error in + Logger.warn("Failed to send online status to \(thread.contactIdentifier())") + }.retainUntilComplete() + } + + private static func getAllFriendThreads() -> [TSContactThread] { + var friendThreadIds = [String]() + TSContactThread.enumerateCollectionObjects { (object, _) in + guard let thread = object as? TSContactThread, let uniqueId = thread.uniqueId else { return } + + if thread.friendRequestStatus == .friends && thread.contactIdentifier() != ourHexEncodedPubKey { + friendThreadIds.append(thread.uniqueId!) + } + } + + return friendThreadIds.compactMap { TSContactThread.fetch(uniqueId: $0) } + } + + private static func lokiAddressMessage(for thread: TSThread, isPing: Bool) -> LokiAddressMessage? { + guard let ourAddress = ourP2PAddress else { + Logger.error("P2P Address not set") + return nil + } + + return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: isPing) + } +} diff --git a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift index 4e69db3f7..84aa6abd4 100644 --- a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift +++ b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift @@ -63,7 +63,7 @@ public enum ProofOfWork { /// - data: The message data /// - pubKey: The message recipient /// - timestamp: The timestamp - /// - ttl: The message time to live + /// - ttl: The message time to live in milliseconds /// - Returns: A nonce string or `nil` if it failed public static func calculate(data: String, pubKey: String, timestamp: UInt64, ttl: UInt64) -> String? { let payload = createPayload(pubKey: pubKey, data: data, timestamp: timestamp, ttl: ttl) diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h new file mode 100644 index 000000000..99d334d13 --- /dev/null +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h @@ -0,0 +1,16 @@ +#import "LKEphemeralMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(LokiAddressMessage) +@interface LKAddressMessage : LKEphemeralMessage + +@property (nonatomic, readonly) NSString *address; +@property (nonatomic, readonly) uint16_t port; +@property (nonatomic, readonly) BOOL isPing; + +- (instancetype)initInThread:(nullable TSThread *)thread address:(NSString *)address port:(uint16_t)port isPing:(BOOL)isPing; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m new file mode 100644 index 000000000..e82abd15d --- /dev/null +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m @@ -0,0 +1,51 @@ +#import "LKAddressMessage.h" +#import "NSDate+OWS.h" +#import "SignalRecipient.h" +#import + +@interface LKAddressMessage () + +@property (nonatomic) NSString *address; +@property (nonatomic) uint16_t port; +@property (nonatomic) BOOL isPing; + +@end + +@implementation LKAddressMessage + +- (instancetype)initInThread:(nullable TSThread *)thread address:(NSString *)address port:(uint16_t)port isPing:(bool)isPing +{ + self = [LKEphemeralMessage createEmptyOutgoingMessageInThread:thread]; + if (self) { + _address = address; + _port = port; + _isPing = isPing; + } + return self; +} + +- (SSKProtoContentBuilder *)contentBuilder:(SignalRecipient *)recipient { + SSKProtoContentBuilder *contentBuilder = [super contentBuilder:recipient]; + + SSKProtoLokiAddressMessageBuilder *addressBuilder = [SSKProtoLokiAddressMessage builder]; + [addressBuilder setPtpAddress:self.address]; + uint32_t portAsUInt32 = self.port; + [addressBuilder setPtpPort:portAsUInt32]; + + NSError *error; + SSKProtoLokiAddressMessage *addressMessage = [addressBuilder buildAndReturnError:&error]; + if (error || !addressMessage) { + OWSFailDebug(@"Failed to build LokiAddressMessage for %@: %@.", recipient.recipientId, error); + } else { + [contentBuilder setLokiAddressMessage:addressMessage]; + } + + return contentBuilder; +} + +- (uint)ttl { + // Address messages should only last 1 minute + return 1 * kMinuteInMs; +} + +@end diff --git a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h index 16e318f75..a60c7b1b1 100644 --- a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h +++ b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h @@ -5,7 +5,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(EphemeralMessage) @interface LKEphemeralMessage : TSOutgoingMessage -/// Used to establish sessions. +/// Used for e.g. session initialization. + (LKEphemeralMessage *)createEmptyOutgoingMessageInThread:(TSThread *)thread; @end diff --git a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m index 658e9b795..f74ba80c8 100644 --- a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m @@ -8,6 +8,7 @@ expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; } +- (BOOL)shouldSyncTranscript { return NO; } - (BOOL)shouldBeSaved { return NO; } @end diff --git a/SignalServiceKit/src/Messages/OWSMessageHandler.m b/SignalServiceKit/src/Messages/OWSMessageHandler.m index e1de268a3..3caee3738 100644 --- a/SignalServiceKit/src/Messages/OWSMessageHandler.m +++ b/SignalServiceKit/src/Messages/OWSMessageHandler.m @@ -78,7 +78,10 @@ NSString *envelopeAddress(SSKProtoEnvelope *envelope) } else { // Don't fire an analytics event; if we ever add a new content type, we'd generate a ton of // analytics traffic. - OWSFailDebug(@"Unknown content type."); + // Loki: Original code + // ======== +// OWSFailDebug(@"Unknown content type."); + // ======== return @"UnknownContent"; } } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 647ce456d..7006544b2 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -432,6 +432,13 @@ NS_ASSUME_NONNULL_BEGIN } [self.primaryStorage setPreKeyBundle:bundle forContact:envelope.source transaction:transaction]; } + + // Loki: Check if we got p2p address + if (contentProto.lokiAddressMessage) { + NSString *address = contentProto.lokiAddressMessage.ptpAddress; + uint32_t port = contentProto.lokiAddressMessage.ptpPort; + [LokiP2PManager didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage]; + } if (contentProto.syncMessage) { [self throws_handleIncomingEnvelope:envelope @@ -1452,6 +1459,16 @@ NS_ASSUME_NONNULL_BEGIN (unsigned long)timestamp); return nil; } + + // Loki + // If we received a message from a contact in the last 2 minues that was not p2p, then we need to ping them. + // We assume this occurred because they don't have our p2p details. + if (!envelope.isPtpMessage && envelope.source != nil) { + uint64_t timestamp = envelope.timestamp; + uint64_t now = NSDate.ows_millisecondTimeStamp; + uint64_t ageInSeconds = (now - timestamp) / 1000; + if (ageInSeconds <= 120) { [LokiP2PManager pingContact:envelope.source]; } + } [self finalizeIncomingMessage:incomingMessage thread:thread @@ -1500,6 +1517,12 @@ NS_ASSUME_NONNULL_BEGIN if (existingFriendRequestMessage != nil && existingFriendRequestMessage.isFriendRequest) { [existingFriendRequestMessage saveFriendRequestStatus:LKMessageFriendRequestStatusAccepted withTransaction:transaction]; } + + // Send our p2p details to the other user + LKAddressMessage *_Nullable onlineMessage = [LokiP2PManager onlineBroadcastMessageForThread:thread]; + if (onlineMessage != nil) { + [self.messageSenderJobQueue addMessage:onlineMessage transaction:transaction]; + } } } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 99b47ca01..b355b19ef 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -44,6 +44,7 @@ #import "TSSocketManager.h" #import "TSThread.h" #import "LKFriendRequestMessage.h" +#import "LKAddressMessage.h" #import #import #import @@ -1802,7 +1803,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; isSilent:false isOnline:false registrationId:0 - ttl:message.ttl]; + ttl:message.ttl + isPing:false]; NSError *error; NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; @@ -1889,6 +1891,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; BOOL isSilent = message.isSilent; BOOL isOnline = message.isOnline; + + LKAddressMessage *_Nullable addressMessage = [message as:[LKAddressMessage class]]; + BOOL isPing = addressMessage != nil && addressMessage.isPing; OWSMessageServiceParams *messageParams = [[OWSMessageServiceParams alloc] initWithType:messageType recipientId:recipientId @@ -1897,7 +1902,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; isSilent:isSilent isOnline:isOnline registrationId:[cipher throws_remoteRegistrationId:transaction] - ttl:message.ttl]; + ttl:message.ttl + isPing:isPing]; NSError *error; NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; diff --git a/SignalServiceKit/src/Messages/OWSMessageServiceParams.h b/SignalServiceKit/src/Messages/OWSMessageServiceParams.h index 3a8dc25c9..83f00ce3f 100644 --- a/SignalServiceKit/src/Messages/OWSMessageServiceParams.h +++ b/SignalServiceKit/src/Messages/OWSMessageServiceParams.h @@ -27,6 +27,9 @@ NS_ASSUME_NONNULL_BEGIN // Loki: Message ttl @property (nonatomic, readonly) uint ttl; +// Loki: Wether this message is a p2p ping +@property (nonatomic, readonly) BOOL isPing; + - (instancetype)initWithType:(TSWhisperMessageType)type recipientId:(NSString *)destination device:(int)deviceId @@ -34,7 +37,8 @@ NS_ASSUME_NONNULL_BEGIN isSilent:(BOOL)isSilent isOnline:(BOOL)isOnline registrationId:(int)registrationId - ttl:(uint)ttl; + ttl:(uint)ttl + isPing:(BOOL)isPing; @end diff --git a/SignalServiceKit/src/Messages/OWSMessageServiceParams.m b/SignalServiceKit/src/Messages/OWSMessageServiceParams.m index cfd88bd3e..d7a0cbf12 100644 --- a/SignalServiceKit/src/Messages/OWSMessageServiceParams.m +++ b/SignalServiceKit/src/Messages/OWSMessageServiceParams.m @@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN isOnline:(BOOL)isOnline registrationId:(int)registrationId ttl:(uint)ttl + isPing:(BOOL)isPing { self = [super init]; @@ -38,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN _silent = isSilent; _online = isOnline; _ttl = ttl; + _isPing = isPing; return self; } diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.m b/SignalServiceKit/src/Network/API/TSNetworkManager.m index eb69e8be0..57f0b08a4 100644 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.m +++ b/SignalServiceKit/src/Network/API/TSNetworkManager.m @@ -496,6 +496,10 @@ dispatch_queue_t NetworkManagerQueue() + (void)deregisterAfterAuthErrorIfNecessary:(NSURLSessionDataTask *)task request:(TSRequest *)request statusCode:(NSInteger)statusCode { + /* Loki: Original code + * We don't really care about invalid auth + * ======== + OWSLogVerbose(@"Invalid auth: %@", task.originalRequest.allHTTPHeaderFields); // We only want to de-register for: @@ -522,6 +526,9 @@ dispatch_queue_t NetworkManagerQueue() } else { OWSLogWarn(@"Ignoring %d for URL: %@", (int)statusCode, task.originalRequest.URL.absoluteString); } + + * ======== + */ } + (NSError *)errorWithHTTPCode:(NSInteger)code