diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 1fc8620a5..a9ccbcdf0 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -206,7 +206,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { return } - let timestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) + let timestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() let message: CallMessage = CallMessage( uuid: self.uuid, kind: .preOffer, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 3f39fd259..335c29b9c 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -461,7 +461,7 @@ extension ConversationVC: // flags appropriately let threadId: String = self.viewModel.threadData.threadId let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true) - let sentTimestampMs: Int64 = Int64(floor((Date().timeIntervalSince1970 * 1000))) + let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() let linkPreviewDraft: LinkPreviewDraft? = snInputView.linkPreviewInfo?.draft let quoteModel: QuotedReplyModel? = snInputView.quoteDraftInfo?.model @@ -580,7 +580,7 @@ extension ConversationVC: // flags appropriately let threadId: String = self.viewModel.threadData.threadId let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true) - let sentTimestampMs: Int64 = Int64(floor((Date().timeIntervalSince1970 * 1000))) + let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() // If this was a message request then approve it approveMessageRequestIfNeeded( @@ -680,7 +680,7 @@ extension ConversationVC: threadVariant: threadVariant, threadIsMessageRequest: threadIsMessageRequest, direction: .outgoing, - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ) if needsToStartTypingIndicator { @@ -1259,7 +1259,7 @@ extension ConversationVC: guard !threadIsMessageRequest else { return } // Perform local rate limiting (don't allow more than 20 reactions within 60 seconds) - let sentTimestamp: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) + let sentTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs() let recentReactionTimestamps: [Int64] = General.cache.wrappedValue.recentReactionTimestamps guard @@ -2084,7 +2084,7 @@ extension ConversationVC: // Create URL let directory: String = OWSTemporaryDirectory() - let fileName: String = "\(Int64(floor(Date().timeIntervalSince1970 * 1000))).m4a" + let fileName: String = "\(SnodeAPI.currentOffsetTimestampMs()).m4a" let url: URL = URL(fileURLWithPath: directory).appendingPathComponent(fileName) // Set up audio session @@ -2325,7 +2325,7 @@ extension ConversationVC { for: self.viewModel.threadData.threadId, threadVariant: self.viewModel.threadData.threadVariant, isNewThread: false, - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ) } diff --git a/Session/Conversations/Settings/OWSMessageTimerView.m b/Session/Conversations/Settings/OWSMessageTimerView.m index dc71b3b06..bfe57d7e3 100644 --- a/Session/Conversations/Settings/OWSMessageTimerView.m +++ b/Session/Conversations/Settings/OWSMessageTimerView.m @@ -8,6 +8,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -75,7 +76,7 @@ const CGFloat kDisappearingMessageIconSize = 12.f; return; } - uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp]; + uint64_t nowTimestamp = [SNSnodeAPI currentOffsetTimestampMs]; CGFloat secondsLeft = (self.expirationTimestamp > nowTimestamp ? (self.expirationTimestamp - nowTimestamp) / 1000.f : 0.f); CGFloat progress = 0.f; diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift index 10005d4a8..87d611648 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift @@ -458,21 +458,21 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier { + if data.count > FileServerAPI.maxFileSize { failure?(HTTP.Error.maxFileSizeExceeded) return } @@ -1114,7 +1115,7 @@ extension Attachment { state: .uploaded, creationTimestamp: ( updatedAttachment?.creationTimestamp ?? - Date().timeIntervalSince1970 + (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ), downloadUrl: "\(FileServerAPI.server)/files/\(fileId)" ) diff --git a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift index 9c1809f2a..c45bf373a 100644 --- a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift +++ b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit +import SessionSnodeKit /// We can rely on the unique constraints within the `Interaction` table to prevent duplicate `VisibleMessage` /// values from being processed, but some control messages don’t have an associated interaction - this table provides @@ -170,7 +171,10 @@ internal extension ControlMessageProcessRecord { self.threadId = threadId self.timestampMs = timestampMs - self.serverExpirationTimestamp = (Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds) + self.serverExpirationTimestamp = ( + (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + + ControlMessageProcessRecord.defaultExpirationSeconds + ) } /// This method should only be used for records created during migration from the legacy @@ -181,7 +185,8 @@ internal extension ControlMessageProcessRecord { /// clean out these excessive entries after `defaultExpirationSeconds`) static func generateLegacyProcessRecords(_ db: Database, receivedMessageTimestamps: [Int64]) throws { let defaultExpirationTimestamp: TimeInterval = ( - Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds + (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + + ControlMessageProcessRecord.defaultExpirationSeconds ) try receivedMessageTimestamps.forEach { timestampMs in diff --git a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift index 2cd6fa9ab..ba4a5c604 100644 --- a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift +++ b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit +import SessionSnodeKit public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { // public static let isNewConfigurationEnabled: Bool = Date().timeIntervalSince1970 > 1671062400 // 15/12/2022 diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index ed74dd624..bffaf4a11 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import Sodium import SessionUtilitiesKit +import SessionSnodeKit public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "interaction" } @@ -311,7 +312,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu self.timestampMs = timestampMs self.receivedAtTimestampMs = { switch variant { - case .standardIncoming, .standardOutgoing: return Int64(Date().timeIntervalSince1970 * 1000) + case .standardIncoming, .standardOutgoing: return SnodeAPI.currentOffsetTimestampMs() /// For TSInteractions which are not `standardIncoming` and `standardOutgoing` use the `timestampMs` value default: return timestampMs @@ -491,7 +492,7 @@ public extension Interaction { job: DisappearingMessagesJob.updateNextRunIfNeeded( db, interactionIds: interactionIds, - startedAtMs: (Date().timeIntervalSince1970 * 1000), + startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs()), threadId: threadId ) ) diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index 21b3de9fa..46b99ff74 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -6,6 +6,7 @@ import PromiseKit import AFNetworking import SignalCoreKit import SessionUtilitiesKit +import SessionSnodeKit public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "linkPreview" } @@ -60,7 +61,7 @@ public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, Persis public init( url: String, timestamp: TimeInterval = LinkPreview.timestampFor( - sentTimestampMs: (Date().timeIntervalSince1970 * 1000) // Default to now + sentTimestampMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs()) // Default to now ), variant: Variant = .standard, title: String?, diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index b19805072..ca08aa182 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import Sodium import SessionUtilitiesKit +import SessionSnodeKit public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "thread" } @@ -104,7 +105,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, public init( id: String, variant: Variant, - creationDateTimestamp: TimeInterval = Date().timeIntervalSince1970, + creationDateTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000), shouldBeVisible: Bool = false, isPinned: Bool = false, messageDraft: String? = nil, diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift index 694bf53be..065a8b94c 100644 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ b/SessionMessagingKit/File Server/FileServerAPI.swift @@ -15,13 +15,9 @@ public final class FileServerAPI: NSObject { @objc public static let server = "http://filev2.getsession.org" public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" public static let maxFileSize = (10 * 1024 * 1024) // 10 MB - /// The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes - /// is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP - /// request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also - /// be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when - /// uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only - /// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds. - public static let fileSizeORMultiplier: Double = 2 + + /// Standard timeout is 10 seconds which is a little too short fir file upload/download with slightly larger files + public static let fileTimeout: TimeInterval = 30 // MARK: - File Storage @@ -77,7 +73,7 @@ public final class FileServerAPI: NSObject { return Promise(error: error) } - return OnionRequestAPI.sendOnionRequest(urlRequest, to: request.server, with: serverPublicKey) + return OnionRequestAPI.sendOnionRequest(urlRequest, to: request.server, with: serverPublicKey, timeout: FileServerAPI.fileTimeout) .map2 { _, response in guard let response: Data = response else { throw HTTP.Error.parsingFailed } diff --git a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift index 6a1d4fc16..a3588e511 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift @@ -145,7 +145,7 @@ public enum AttachmentDownloadJob: JobExecutor { _ = try attachment .with( state: .downloaded, - creationTimestamp: Date().timeIntervalSince1970, + creationTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000), localRelativeFilePath: ( attachment.localRelativeFilePath ?? Attachment.localRelativeFilePath(from: attachment.originalFilePath) diff --git a/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift b/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift index cad536e90..57a8b5a26 100644 --- a/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift +++ b/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift @@ -18,7 +18,7 @@ public enum DisappearingMessagesJob: JobExecutor { deferred: @escaping (Job) -> () ) { // The 'backgroundTask' gets captured and cleared within the 'completion' block - let timestampNowMs: TimeInterval = ceil(Date().timeIntervalSince1970 * 1000) + let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs()) var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function) let updatedJob: Job? = Storage.shared.write { db in @@ -60,10 +60,14 @@ public extension DisappearingMessagesJob { guard let nextExpirationTimestampMs: Double = nextExpirationTimestampMs else { return nil } + /// The `expiresStartedAtMs` timestamp is now based on the `SnodeAPI.currentOffsetTimestampMs()` value + /// so we need to make sure offset the `nextRunTimestamp` accordingly to ensure it runs at the correct local time + let clockOffsetMs: Int64 = SnodeAPI.clockOffsetMs.wrappedValue + return try? Job .filter(Job.Columns.variant == Job.Variant.disappearingMessages) .fetchOne(db)? - .with(nextRunTimestamp: ceil(nextExpirationTimestampMs / 1000)) + .with(nextRunTimestamp: ceil((nextExpirationTimestampMs - Double(clockOffsetMs)) / 1000)) .saved(db) } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 9c473a44c..97eb73e74 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -280,7 +280,10 @@ public extension Message { return try processRawReceivedMessage( db, envelope: envelope, - serverExpirationTimestamp: (Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds), + serverExpirationTimestamp: ( + (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + + ControlMessageProcessRecord.defaultExpirationSeconds + ), serverHash: serverHash, handleClosedGroupKeyUpdateMessages: true ) @@ -296,7 +299,10 @@ public extension Message { let processedMessage: ProcessedMessage? = try processRawReceivedMessage( db, envelope: envelope, - serverExpirationTimestamp: (Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds), + serverExpirationTimestamp: ( + (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + + ControlMessageProcessRecord.defaultExpirationSeconds + ), serverHash: nil, handleClosedGroupKeyUpdateMessages: false ) @@ -428,7 +434,7 @@ public extension Message { let count: Int64 = rawReaction.you ? rawReaction.count - 1 : rawReaction.count - let timestampMs: Int64 = Int64(floor((Date().timeIntervalSince1970 * 1000))) + let timestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() let maxLength: Int = shouldAddSelfReaction ? 4 : 5 let desiredReactorIds: [String] = reactors .filter { $0 != blindedUserPublicKey && $0 != userPublicKey } // Remove current user for now, will add back if needed diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 177c2b5de..19e3a6035 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -871,6 +871,7 @@ public enum OpenGroupAPI { ], body: bytes ), + timeout: FileServerAPI.fileTimeout, using: dependencies ) .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) @@ -890,6 +891,7 @@ public enum OpenGroupAPI { server: server, endpoint: .roomFileIndividual(roomToken, fileId) ), + timeout: FileServerAPI.fileTimeout, using: dependencies ) .map { responseInfo, maybeData in @@ -1410,6 +1412,7 @@ public enum OpenGroupAPI { _ db: Database, request: Request, forceBlinded: Bool = false, + timeout: TimeInterval = HTTP.timeout, using dependencies: SMKDependencies = SMKDependencies() ) -> Promise<(OnionRequestResponseInfoType, Data?)> { let urlRequest: URLRequest @@ -1434,6 +1437,6 @@ public enum OpenGroupAPI { return Promise(error: OpenGroupAPIError.signingFailed) } - return dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey) + return dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey, timeout: timeout) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 8dc60c126..9f8f652db 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import WebRTC import SessionUtilitiesKit +import SessionSnodeKit extension MessageReceiver { public static func handleCallMessage(_ db: Database, message: CallMessage) throws { @@ -189,7 +190,7 @@ extension MessageReceiver { body: String(data: messageInfoData, encoding: .utf8), timestampMs: ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) ) .inserted(db) @@ -235,7 +236,7 @@ extension MessageReceiver { ) let timestampMs: Int64 = ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) guard let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) else { return nil } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 5c5620a81..669e46de0 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import Sodium import SessionUtilitiesKit +import SessionSnodeKit extension MessageReceiver { public static func handleClosedGroupControlMessage(_ db: Database, _ message: ClosedGroupControlMessage) throws { @@ -135,7 +136,7 @@ extension MessageReceiver { threadId: groupPublicKey, publicKey: Data(encryptionKeyPair.publicKey), secretKey: Data(encryptionKeyPair.secretKey), - receivedTimestamp: Date().timeIntervalSince1970 + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ).insert(db) // Start polling @@ -196,7 +197,7 @@ extension MessageReceiver { threadId: groupPublicKey, publicKey: proto.publicKey.removingIdPrefixIfNeeded(), secretKey: proto.privateKey, - receivedTimestamp: Date().timeIntervalSince1970 + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ).insert(db) } catch { @@ -231,7 +232,7 @@ extension MessageReceiver { .infoMessage(db, sender: sender), timestampMs: ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) } @@ -307,7 +308,7 @@ extension MessageReceiver { .infoMessage(db, sender: sender), timestampMs: ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) } @@ -383,7 +384,7 @@ extension MessageReceiver { .infoMessage(db, sender: sender), timestampMs: ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) } @@ -461,7 +462,7 @@ extension MessageReceiver { .infoMessage(db, sender: sender), timestampMs: ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift index fd74915a9..a28346f6d 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift @@ -2,6 +2,7 @@ import Foundation import GRDB +import SessionSnodeKit extension MessageReceiver { internal static func handleDataExtractionNotification(_ db: Database, message: DataExtractionNotification) throws { @@ -24,7 +25,7 @@ extension MessageReceiver { }(), timestampMs: ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index d16adbe95..06ee54eb3 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import SignalCoreKit import SessionUtilitiesKit +import SessionSnodeKit extension MessageReceiver { internal static func handleMessageRequestResponse( @@ -123,7 +124,7 @@ extension MessageReceiver { variant: .infoMessageRequestAccepted, timestampMs: ( message.sentTimestamp.map { Int64($0) } ?? - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index bc8d52a0b..0d219b5b2 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -6,6 +6,7 @@ import Sodium import Curve25519Kit import PromiseKit import SessionUtilitiesKit +import SessionSnodeKit extension MessageSender { public static var distributingKeyPairs: Atomic<[String: [ClosedGroupKeyPair]]> = Atomic([:]) @@ -24,7 +25,7 @@ extension MessageSender { let membersAsData = members.map { Data(hex: $0) } let admins = [ userPublicKey ] let adminsAsData = admins.map { Data(hex: $0) } - let formationTimestamp: TimeInterval = Date().timeIntervalSince1970 + let formationTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: groupPublicKey, variant: .closedGroup) try ClosedGroup( @@ -91,7 +92,7 @@ extension MessageSender { threadId: groupPublicKey, publicKey: encryptionKeyPair.publicKey, secretKey: encryptionKeyPair.privateKey, - receivedTimestamp: Date().timeIntervalSince1970 + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ).insert(db) // Notify the PN server @@ -110,7 +111,7 @@ extension MessageSender { threadId: thread.id, authorId: userPublicKey, variant: .infoClosedGroupCreated, - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ).inserted(db) // Start polling @@ -142,7 +143,7 @@ extension MessageSender { threadId: closedGroup.threadId, publicKey: legacyNewKeyPair.publicKey, secretKey: legacyNewKeyPair.privateKey, - receivedTimestamp: Date().timeIntervalSince1970 + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ) // Distribute it @@ -230,7 +231,7 @@ extension MessageSender { body: ClosedGroupControlMessage.Kind .nameChange(name: name) .infoMessage(db, sender: userPublicKey), - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ).inserted(db) guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } @@ -330,7 +331,7 @@ extension MessageSender { body: ClosedGroupControlMessage.Kind .membersAdded(members: addedMembers.map { Data(hex: $0) }) .infoMessage(db, sender: userPublicKey), - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ).inserted(db) guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } @@ -431,7 +432,7 @@ extension MessageSender { body: ClosedGroupControlMessage.Kind .membersRemoved(members: removedMembers.map { Data(hex: $0) }) .infoMessage(db, sender: userPublicKey), - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ).inserted(db) guard let newInteractionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } @@ -496,7 +497,7 @@ extension MessageSender { body: ClosedGroupControlMessage.Kind .memberLeft .infoMessage(db, sender: userPublicKey), - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ).inserted(db) guard let interactionId: Int64 = interaction.id else { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 44b5bc413..02cb2f226 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -5,6 +5,7 @@ import GRDB import Sodium import SignalCoreKit import SessionUtilitiesKit +import SessionSnodeKit public enum MessageReceiver { private static var lastEncryptionKeyPairRequest: [String: Date] = [:] @@ -144,7 +145,7 @@ public enum MessageReceiver { message.sender = sender message.recipient = userPublicKey message.sentTimestamp = envelope.timestamp - message.receivedTimestamp = UInt64((Date().timeIntervalSince1970) * 1000) + message.receivedTimestamp = UInt64(SnodeAPI.currentOffsetTimestampMs()) message.groupPublicKey = groupPublicKey message.openGroupServerMessageId = openGroupMessageServerId.map { UInt64($0) } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index ade3ed9ff..234929f67 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -67,7 +67,7 @@ public final class MessageSender { let (promise, seal) = Promise.pending() let userPublicKey: String = getUserHexEncodedPublicKey(db) let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) - let messageSendTimestamp: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) + let messageSendTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs() // Set the timestamp, sender and recipient message.sentTimestamp = ( @@ -202,7 +202,7 @@ public final class MessageSender { recipient: message.recipient!, data: base64EncodedData, ttl: getSpecifiedTTL(db, message: message, isSyncMessage: isSyncMessage) ?? message.ttl, - timestampMs: UInt64(messageSendTimestamp + SnodeAPI.clockOffset.wrappedValue) + timestampMs: UInt64(messageSendTimestamp) ) SnodeAPI @@ -322,7 +322,7 @@ public final class MessageSender { // Set the timestamp, sender and recipient if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set - message.sentTimestamp = UInt64(floor(Date().timeIntervalSince1970 * 1000)) + message.sentTimestamp = UInt64(SnodeAPI.currentOffsetTimestampMs()) } switch destination { @@ -472,7 +472,7 @@ public final class MessageSender { // Set the timestamp, sender and recipient if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set - message.sentTimestamp = UInt64(floor(Date().timeIntervalSince1970 * 1000)) + message.sentTimestamp = UInt64(SnodeAPI.currentOffsetTimestampMs()) } message.sender = userPublicKey @@ -610,6 +610,19 @@ public final class MessageSender { // Mark the message as sent try interaction.recipientStates .updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.sent)) +<<<<<<< HEAD +======= + + // Start the disappearing messages timer if needed + JobRunner.upsert( + db, + job: DisappearingMessagesJob.updateNextRunIfNeeded( + db, + interaction: interaction, + startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs()) + ) + ) +>>>>>>> dev } } @@ -626,7 +639,10 @@ public final class MessageSender { } }(), message: message, - serverExpirationTimestamp: (Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds) + serverExpirationTimestamp: ( + (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + + ControlMessageProcessRecord.defaultExpirationSeconds + ) )?.insert(db) // Sync the message if: diff --git a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift index 9e50e80b8..afebe2c10 100644 --- a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit +import SessionSnodeKit public class TypingIndicators { // MARK: - Direction @@ -41,7 +42,7 @@ public class TypingIndicators { self.threadId = threadId self.direction = direction - self.timestampMs = (timestampMs ?? Int64(floor(Date().timeIntervalSince1970 * 1000))) + self.timestampMs = (timestampMs ?? SnodeAPI.currentOffsetTimestampMs()) } fileprivate func start(_ db: Database) { diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index b3c4bf796..c60395f1d 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -196,7 +196,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView authorId: getUserHexEncodedPublicKey(db), variant: .standardOutgoing, body: body, - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)), + timestampMs: SnodeAPI.currentOffsetTimestampMs(), hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body), linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil) ).inserted(db) diff --git a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift index 7addb56e5..336a19de8 100644 --- a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift +++ b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift @@ -93,7 +93,7 @@ public extension SnodeReceivedMessageInfo { return try SnodeReceivedMessageInfo .select(Column.rowID) .filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) - .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs <= (Date().timeIntervalSince1970 * 1000)) + .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs <= SnodeAPI.currentOffsetTimestampMs()) .asRequest(of: Int64.self) .fetchAll(db) } @@ -122,7 +122,7 @@ public extension SnodeReceivedMessageInfo { SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false ) .filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) - .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs > (Date().timeIntervalSince1970 * 1000)) + .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs > SnodeAPI.currentOffsetTimestampMs()) .order(SnodeReceivedMessageInfo.Columns.id.desc) .fetchOne(db) diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index d2a71b66c..eedb629aa 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -7,13 +7,17 @@ import PromiseKit import SessionUtilitiesKit public protocol OnionRequestAPIType { - static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise - static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> + static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?, timeout: TimeInterval) -> Promise + static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String, timeout: TimeInterval) -> Promise<(OnionRequestResponseInfoType, Data?)> } public extension OnionRequestAPIType { - static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> { - sendOnionRequest(request, to: server, using: .v4, with: x25519PublicKey) + static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise { + sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey, timeout: HTTP.timeout) + } + + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval = HTTP.timeout) -> Promise<(OnionRequestResponseInfoType, Data?)> { + sendOnionRequest(request, to: server, using: .v4, with: x25519PublicKey, timeout: timeout) } } @@ -369,7 +373,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { // MARK: - Public API /// Sends an onion request to `snode`. Builds new paths as needed. - public static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String? = nil) -> Promise { + public static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String? = nil, timeout: TimeInterval = HTTP.timeout) -> Promise { let payloadJson: JSON = [ "method" : method.rawValue, "params" : parameters ] guard let payload: Data = try? JSONSerialization.data(withJSONObject: payloadJson, options: []) else { @@ -377,7 +381,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { } /// **Note:** Currently the service nodes only support V3 Onion Requests - return sendOnionRequest(with: payload, to: OnionRequestAPIDestination.snode(snode), version: .v3) + return sendOnionRequest(with: payload, to: OnionRequestAPIDestination.snode(snode), version: .v3, timeout: timeout) .map { _, maybeData in guard let data: Data = maybeData else { throw HTTP.Error.invalidResponse } @@ -393,7 +397,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { } /// Sends an onion request to `server`. Builds new paths as needed. - public static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion = .v4, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> { + public static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion = .v4, with x25519PublicKey: String, timeout: TimeInterval = HTTP.timeout) -> Promise<(OnionRequestResponseInfoType, Data?)> { guard let url = request.url, let host = request.url?.host else { return Promise(error: OnionRequestAPIError.invalidURL) } @@ -412,14 +416,14 @@ public enum OnionRequestAPI: OnionRequestAPIType { scheme: scheme, port: port ) - let promise = sendOnionRequest(with: payload, to: destination, version: version) + let promise = sendOnionRequest(with: payload, to: destination, version: version, timeout: timeout) promise.catch2 { error in SNLog("Couldn't reach server: \(url) due to error: \(error).") } return promise } - public static func sendOnionRequest(with payload: Data, to destination: OnionRequestAPIDestination, version: OnionRequestAPIVersion) -> Promise<(OnionRequestResponseInfoType, Data?)> { + public static func sendOnionRequest(with payload: Data, to destination: OnionRequestAPIDestination, version: OnionRequestAPIVersion, timeout: TimeInterval = HTTP.timeout) -> Promise<(OnionRequestResponseInfoType, Data?)> { let (promise, seal) = Promise<(OnionRequestResponseInfoType, Data?)>.pending() var guardSnode: Snode? @@ -444,7 +448,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { } let destinationSymmetricKey = intermediate.destinationSymmetricKey - HTTP.execute(.post, url, body: body) + HTTP.execute(.post, url, body: body, timeout: timeout) .done2 { responseData in handleResponse( responseData: responseData, @@ -672,7 +676,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { if let timestamp = body["t"] as? Int64 { let offset = timestamp - Int64(floor(Date().timeIntervalSince1970 * 1000)) - SnodeAPI.clockOffset.mutate { $0 = offset } + SnodeAPI.clockOffsetMs.mutate { $0 = offset } } guard 200...299 ~= statusCode else { diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 6e523b294..d1f8994b5 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -19,10 +19,16 @@ public final class SnodeAPI { internal static var snodePool: Atomic> = Atomic([]) /// The offset between the user's clock and the Service Node's clock. Used in cases where the - /// user's clock is incorrect. - /// - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - public static var clockOffset: Atomic = Atomic(0) + /// user's clock is incorrect + public static var clockOffsetMs: Atomic = Atomic(0) + + public static func currentOffsetTimestampMs() -> Int64 { + return ( + Int64(floor(Date().timeIntervalSince1970 * 1000)) + + SnodeAPI.clockOffsetMs.wrappedValue + ) + } + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. public static var swarmCache: Atomic<[String: Set]> = Atomic([:]) @@ -546,7 +552,7 @@ public final class SnodeAPI { let lastHash = SnodeReceivedMessageInfo.fetchLastNotExpired(for: snode, namespace: namespace, associatedWith: publicKey)?.hash ?? "" // Construct signature - let timestamp = UInt64(Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.clockOffset.wrappedValue) + let timestamp = UInt64(SnodeAPI.currentOffsetTimestampMs()) let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() let namespaceVerificationString = (namespace == defaultNamespace ? "" : String(namespace)) @@ -647,7 +653,7 @@ public final class SnodeAPI { } // Construct signature - let timestamp = UInt64(Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.clockOffset.wrappedValue) + let timestamp = UInt64(SnodeAPI.currentOffsetTimestampMs()) let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() guard @@ -1107,3 +1113,11 @@ public final class SnodeAPI { return nil } } + +@objc(SNSnodeAPI) +public final class SNSnodeAPI: NSObject { + @objc(currentOffsetTimestampMs) + public static func currentOffsetTimestampMs() -> UInt64 { + return UInt64(SnodeAPI.currentOffsetTimestampMs()) + } +} diff --git a/SessionUtilitiesKit/Database/Models/Identity.swift b/SessionUtilitiesKit/Database/Models/Identity.swift index 9d08731f5..4f3f82ee8 100644 --- a/SessionUtilitiesKit/Database/Models/Identity.swift +++ b/SessionUtilitiesKit/Database/Models/Identity.swift @@ -53,7 +53,7 @@ extension ECKeyPair { public extension Identity { static func generate(from seed: Data) throws -> (ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { - assert(seed.count == 16) + guard (seed.count == 16) else { throw GeneralError.invalidSeed } let padding = Data(repeating: 0, count: 16) guard diff --git a/SessionUtilitiesKit/General/General.swift b/SessionUtilitiesKit/General/General.swift index 714184ee3..72576fb56 100644 --- a/SessionUtilitiesKit/General/General.swift +++ b/SessionUtilitiesKit/General/General.swift @@ -19,6 +19,7 @@ public enum General { } public enum GeneralError: Error { + case invalidSeed case keyGenerationFailed } diff --git a/SignalUtilitiesKit/Configuration.swift b/SignalUtilitiesKit/Configuration.swift index 2c0414db2..4ab7e2d50 100644 --- a/SignalUtilitiesKit/Configuration.swift +++ b/SignalUtilitiesKit/Configuration.swift @@ -8,10 +8,7 @@ import SessionMessagingKit public enum Configuration { public static func performMainSetup() { // Need to do this first to ensure the legacy database exists - SNUtilitiesKit.configure( - maxFileSize: UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier) - ) - + SNUtilitiesKit.configure(maxFileSize: UInt(FileServerAPI.maxFileSize)) SNMessagingKit.configure() SNSnodeKit.configure() SNUIKit.configure()