Merge branch 'dev' into disappearing-message-redesign

pull/941/head
ryanzhao 2 years ago
commit 9954cddc2a

@ -206,7 +206,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId)
else { return } else { return }
let timestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) let timestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs()
let message: CallMessage = CallMessage( let message: CallMessage = CallMessage(
uuid: self.uuid, uuid: self.uuid,
kind: .preOffer, kind: .preOffer,

@ -461,7 +461,7 @@ extension ConversationVC:
// flags appropriately // flags appropriately
let threadId: String = self.viewModel.threadData.threadId let threadId: String = self.viewModel.threadData.threadId
let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true) 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 linkPreviewDraft: LinkPreviewDraft? = snInputView.linkPreviewInfo?.draft
let quoteModel: QuotedReplyModel? = snInputView.quoteDraftInfo?.model let quoteModel: QuotedReplyModel? = snInputView.quoteDraftInfo?.model
@ -580,7 +580,7 @@ extension ConversationVC:
// flags appropriately // flags appropriately
let threadId: String = self.viewModel.threadData.threadId let threadId: String = self.viewModel.threadData.threadId
let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true) 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 // If this was a message request then approve it
approveMessageRequestIfNeeded( approveMessageRequestIfNeeded(
@ -680,7 +680,7 @@ extension ConversationVC:
threadVariant: threadVariant, threadVariant: threadVariant,
threadIsMessageRequest: threadIsMessageRequest, threadIsMessageRequest: threadIsMessageRequest,
direction: .outgoing, direction: .outgoing,
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) timestampMs: SnodeAPI.currentOffsetTimestampMs()
) )
if needsToStartTypingIndicator { if needsToStartTypingIndicator {
@ -1259,7 +1259,7 @@ extension ConversationVC:
guard !threadIsMessageRequest else { return } guard !threadIsMessageRequest else { return }
// Perform local rate limiting (don't allow more than 20 reactions within 60 seconds) // 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 let recentReactionTimestamps: [Int64] = General.cache.wrappedValue.recentReactionTimestamps
guard guard
@ -2084,7 +2084,7 @@ extension ConversationVC:
// Create URL // Create URL
let directory: String = OWSTemporaryDirectory() 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) let url: URL = URL(fileURLWithPath: directory).appendingPathComponent(fileName)
// Set up audio session // Set up audio session
@ -2325,7 +2325,7 @@ extension ConversationVC {
for: self.viewModel.threadData.threadId, for: self.viewModel.threadData.threadId,
threadVariant: self.viewModel.threadData.threadVariant, threadVariant: self.viewModel.threadData.threadVariant,
isNewThread: false, isNewThread: false,
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) timestampMs: SnodeAPI.currentOffsetTimestampMs()
) )
} }

@ -8,6 +8,7 @@
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#import <SignalCoreKit/NSDate+OWS.h> #import <SignalCoreKit/NSDate+OWS.h>
#import <SessionUtilitiesKit/NSTimer+Proxying.h> #import <SessionUtilitiesKit/NSTimer+Proxying.h>
#import <SessionSnodeKit/SessionSnodeKit.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -75,7 +76,7 @@ const CGFloat kDisappearingMessageIconSize = 12.f;
return; return;
} }
uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp]; uint64_t nowTimestamp = [SNSnodeAPI currentOffsetTimestampMs];
CGFloat secondsLeft CGFloat secondsLeft
= (self.expirationTimestamp > nowTimestamp ? (self.expirationTimestamp - nowTimestamp) / 1000.f : 0.f); = (self.expirationTimestamp > nowTimestamp ? (self.expirationTimestamp - nowTimestamp) / 1000.f : 0.f);
CGFloat progress = 0.f; CGFloat progress = 0.f;

@ -458,21 +458,21 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
_ = try updatedConfig.saved(db) _ = try updatedConfig.saved(db)
let nowInMs: Double = floor(Date().timeIntervalSince1970 * 1000)
_ = try Interaction _ = try Interaction
.filter(Interaction.Columns.threadId == threadId) .filter(Interaction.Columns.threadId == threadId)
.filter(Interaction.Columns.variant == Interaction.Variant.infoDisappearingMessagesUpdate) .filter(Interaction.Columns.variant == Interaction.Variant.infoDisappearingMessagesUpdate)
.deleteAll(db) .deleteAll(db)
let currentOffsetTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs()
let interaction: Interaction = try Interaction( let interaction: Interaction = try Interaction(
threadId: threadId, threadId: threadId,
authorId: getUserHexEncodedPublicKey(db), authorId: getUserHexEncodedPublicKey(db),
variant: .infoDisappearingMessagesUpdate, variant: .infoDisappearingMessagesUpdate,
body: updatedConfig.messageInfoString(with: nil, isPreviousOff: !self.config.isEnabled), body: updatedConfig.messageInfoString(with: nil, isPreviousOff: !self.config.isEnabled),
timestampMs: Int64(nowInMs), timestampMs: currentOffsetTimestampMs,
expiresInSeconds: updatedConfig.isEnabled ? nil : self.config.durationSeconds, expiresInSeconds: updatedConfig.isEnabled ? nil : self.config.durationSeconds,
expiresStartedAtMs: (!updatedConfig.isEnabled && self.config.type == .disappearAfterSend) ? nowInMs : nil expiresStartedAtMs: (!updatedConfig.isEnabled && self.config.type == .disappearAfterSend) ? Double(currentOffsetTimestampMs) : nil
) )
.inserted(db) .inserted(db)

@ -620,7 +620,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
threadId: thread.id, threadId: thread.id,
authorId: userId, authorId: userId,
variant: .standardOutgoing, variant: .standardOutgoing,
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)), timestampMs: SnodeAPI.currentOffsetTimestampMs(),
expiresInSeconds: try? DisappearingMessagesConfiguration expiresInSeconds: try? DisappearingMessagesConfiguration
.select(.durationSeconds) .select(.durationSeconds)
.filter(id: userId) .filter(id: userId)

@ -6,6 +6,7 @@ import Foundation
import AVFoundation import AVFoundation
import PromiseKit import PromiseKit
import CoreServices import CoreServices
import SessionMessagingKit
protocol PhotoCaptureDelegate: AnyObject { protocol PhotoCaptureDelegate: AnyObject {
func photoCapture(_ photoCapture: PhotoCapture, didFinishProcessingAttachment attachment: SignalAttachment) func photoCapture(_ photoCapture: PhotoCapture, didFinishProcessingAttachment attachment: SignalAttachment)
@ -463,6 +464,9 @@ class CaptureOutput {
// leaving it enabled causes all audio to be lost on videos longer // leaving it enabled causes all audio to be lost on videos longer
// than the default length (10s). // than the default length (10s).
movieOutput.movieFragmentInterval = CMTime.invalid movieOutput.movieFragmentInterval = CMTime.invalid
// Ensure the recorded movie can't go over the maximum file server size
movieOutput.maxRecordedFileSize = Int64(FileServerAPI.maxFileSize)
} }
var photoOutput: AVCaptureOutput? { var photoOutput: AVCaptureOutput? {

@ -532,7 +532,7 @@ class NotificationActionHandler {
authorId: getUserHexEncodedPublicKey(db), authorId: getUserHexEncodedPublicKey(db),
variant: .standardOutgoing, variant: .standardOutgoing,
body: replyText, body: replyText,
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)), timestampMs: SnodeAPI.currentOffsetTimestampMs(),
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText) hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText)
).inserted(db) ).inserted(db)

@ -198,7 +198,7 @@ final class RestoreVC: BaseVC {
do { do {
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic) let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
let seed = Data(hex: hexEncodedSeed) let seed = Data(hex: hexEncodedSeed)
let (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) let (ed25519KeyPair, x25519KeyPair) = try Identity.generate(from: seed)
Onboarding.Flow.recover.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) Onboarding.Flow.recover.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
mnemonicTextView.resignFirstResponder() mnemonicTextView.resignFirstResponder()

@ -5,6 +5,7 @@ import GRDB
import PromiseKit import PromiseKit
import WebRTC import WebRTC
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
public protocol WebRTCSessionDelegate: AnyObject { public protocol WebRTCSessionDelegate: AnyObject {
var videoCapturer: RTCVideoCapturer { get } var videoCapturer: RTCVideoCapturer { get }
@ -179,7 +180,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
uuid: uuid, uuid: uuid,
kind: .offer, kind: .offer,
sdps: [ sdp.sdp ], sdps: [ sdp.sdp ],
sentTimestampMs: UInt64(floor(Date().timeIntervalSince1970 * 1000)) sentTimestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs())
), ),
interactionId: nil, interactionId: nil,
in: thread in: thread

@ -1289,7 +1289,7 @@ enum _003_YDBToGRDBMigration: Migration {
// so we can reverse-engineer an approximate timestamp by extracting it from // so we can reverse-engineer an approximate timestamp by extracting it from
// the id (this value is unlikely to match exactly though) // the id (this value is unlikely to match exactly though)
let fallbackTimestamp: UInt64 = legacyJob.id let fallbackTimestamp: UInt64 = legacyJob.id
.map { UInt64($0.prefix("\(Int(Date().timeIntervalSince1970 * 1000))".count)) } .map { UInt64($0.prefix("\(SnodeAPI.currentOffsetTimestampMs())".count)) }
.defaulting(to: 0) .defaulting(to: 0)
let legacyIdentifier: String = identifier( let legacyIdentifier: String = identifier(
for: threadId, for: threadId,
@ -1660,7 +1660,7 @@ enum _003_YDBToGRDBMigration: Migration {
state: .invalid, state: .invalid,
contentType: "", contentType: "",
byteCount: 0, byteCount: 0,
creationTimestamp: Date().timeIntervalSince1970, creationTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000),
sourceFilename: nil, sourceFilename: nil,
downloadUrl: nil, downloadUrl: nil,
localRelativeFilePath: nil, localRelativeFilePath: nil,

@ -1,12 +1,13 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import AVFAudio
import AVFoundation
import GRDB import GRDB
import PromiseKit import PromiseKit
import SignalCoreKit import SignalCoreKit
import SessionUtilitiesKit import SessionUtilitiesKit
import AVFAudio import SessionSnodeKit
import AVFoundation
public struct Attachment: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public struct Attachment: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "attachment" } public static var databaseTableName: String { "attachment" }
@ -1062,7 +1063,7 @@ extension Attachment {
// Check the file size // Check the file size
SNLog("File size: \(data.count) bytes.") SNLog("File size: \(data.count) bytes.")
if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier { if data.count > FileServerAPI.maxFileSize {
failure?(HTTP.Error.maxFileSizeExceeded) failure?(HTTP.Error.maxFileSizeExceeded)
return return
} }
@ -1114,7 +1115,7 @@ extension Attachment {
state: .uploaded, state: .uploaded,
creationTimestamp: ( creationTimestamp: (
updatedAttachment?.creationTimestamp ?? updatedAttachment?.creationTimestamp ??
Date().timeIntervalSince1970 (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
), ),
downloadUrl: "\(FileServerAPI.server)/files/\(fileId)" downloadUrl: "\(FileServerAPI.server)/files/\(fileId)"
) )

@ -3,6 +3,7 @@
import Foundation import Foundation
import GRDB import GRDB
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
/// We can rely on the unique constraints within the `Interaction` table to prevent duplicate `VisibleMessage` /// We can rely on the unique constraints within the `Interaction` table to prevent duplicate `VisibleMessage`
/// values from being processed, but some control messages dont have an associated interaction - this table provides /// values from being processed, but some control messages dont have an associated interaction - this table provides
@ -170,7 +171,10 @@ internal extension ControlMessageProcessRecord {
self.threadId = threadId self.threadId = threadId
self.timestampMs = timestampMs 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 /// 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`) /// clean out these excessive entries after `defaultExpirationSeconds`)
static func generateLegacyProcessRecords(_ db: Database, receivedMessageTimestamps: [Int64]) throws { static func generateLegacyProcessRecords(_ db: Database, receivedMessageTimestamps: [Int64]) throws {
let defaultExpirationTimestamp: TimeInterval = ( let defaultExpirationTimestamp: TimeInterval = (
Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) +
ControlMessageProcessRecord.defaultExpirationSeconds
) )
try receivedMessageTimestamps.forEach { timestampMs in try receivedMessageTimestamps.forEach { timestampMs in

@ -3,6 +3,7 @@
import Foundation import Foundation
import GRDB import GRDB
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
// public static let isNewConfigurationEnabled: Bool = Date().timeIntervalSince1970 > 1671062400 // 15/12/2022 // public static let isNewConfigurationEnabled: Bool = Date().timeIntervalSince1970 > 1671062400 // 15/12/2022

@ -4,6 +4,7 @@ import Foundation
import GRDB import GRDB
import Sodium import Sodium
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "interaction" } public static var databaseTableName: String { "interaction" }
@ -311,7 +312,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
self.timestampMs = timestampMs self.timestampMs = timestampMs
self.receivedAtTimestampMs = { self.receivedAtTimestampMs = {
switch variant { 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 /// For TSInteractions which are not `standardIncoming` and `standardOutgoing` use the `timestampMs` value
default: return timestampMs default: return timestampMs
@ -491,7 +492,7 @@ public extension Interaction {
job: DisappearingMessagesJob.updateNextRunIfNeeded( job: DisappearingMessagesJob.updateNextRunIfNeeded(
db, db,
interactionIds: interactionIds, interactionIds: interactionIds,
startedAtMs: (Date().timeIntervalSince1970 * 1000), startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs()),
threadId: threadId threadId: threadId
) )
) )

@ -6,6 +6,7 @@ import PromiseKit
import AFNetworking import AFNetworking
import SignalCoreKit import SignalCoreKit
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "linkPreview" } public static var databaseTableName: String { "linkPreview" }
@ -60,7 +61,7 @@ public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, Persis
public init( public init(
url: String, url: String,
timestamp: TimeInterval = LinkPreview.timestampFor( timestamp: TimeInterval = LinkPreview.timestampFor(
sentTimestampMs: (Date().timeIntervalSince1970 * 1000) // Default to now sentTimestampMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs()) // Default to now
), ),
variant: Variant = .standard, variant: Variant = .standard,
title: String?, title: String?,

@ -4,6 +4,7 @@ import Foundation
import GRDB import GRDB
import Sodium import Sodium
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "thread" } public static var databaseTableName: String { "thread" }
@ -104,7 +105,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord,
public init( public init(
id: String, id: String,
variant: Variant, variant: Variant,
creationDateTimestamp: TimeInterval = Date().timeIntervalSince1970, creationDateTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000),
shouldBeVisible: Bool = false, shouldBeVisible: Bool = false,
isPinned: Bool = false, isPinned: Bool = false,
messageDraft: String? = nil, messageDraft: String? = nil,

@ -15,13 +15,9 @@ public final class FileServerAPI: NSObject {
@objc public static let server = "http://filev2.getsession.org" @objc public static let server = "http://filev2.getsession.org"
public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59"
public static let maxFileSize = (10 * 1024 * 1024) // 10 MB 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 /// Standard timeout is 10 seconds which is a little too short fir file upload/download with slightly larger files
/// 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 public static let fileTimeout: TimeInterval = 30
/// 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
// MARK: - File Storage // MARK: - File Storage
@ -77,7 +73,7 @@ public final class FileServerAPI: NSObject {
return Promise(error: error) 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 .map2 { _, response in
guard let response: Data = response else { throw HTTP.Error.parsingFailed } guard let response: Data = response else { throw HTTP.Error.parsingFailed }

@ -145,7 +145,7 @@ public enum AttachmentDownloadJob: JobExecutor {
_ = try attachment _ = try attachment
.with( .with(
state: .downloaded, state: .downloaded,
creationTimestamp: Date().timeIntervalSince1970, creationTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000),
localRelativeFilePath: ( localRelativeFilePath: (
attachment.localRelativeFilePath ?? attachment.localRelativeFilePath ??
Attachment.localRelativeFilePath(from: attachment.originalFilePath) Attachment.localRelativeFilePath(from: attachment.originalFilePath)

@ -18,7 +18,7 @@ public enum DisappearingMessagesJob: JobExecutor {
deferred: @escaping (Job) -> () deferred: @escaping (Job) -> ()
) { ) {
// The 'backgroundTask' gets captured and cleared within the 'completion' block // 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) var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function)
let updatedJob: Job? = Storage.shared.write { db in let updatedJob: Job? = Storage.shared.write { db in
@ -60,10 +60,14 @@ public extension DisappearingMessagesJob {
guard let nextExpirationTimestampMs: Double = nextExpirationTimestampMs else { return nil } 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 return try? Job
.filter(Job.Columns.variant == Job.Variant.disappearingMessages) .filter(Job.Columns.variant == Job.Variant.disappearingMessages)
.fetchOne(db)? .fetchOne(db)?
.with(nextRunTimestamp: ceil(nextExpirationTimestampMs / 1000)) .with(nextRunTimestamp: ceil((nextExpirationTimestampMs - Double(clockOffsetMs)) / 1000))
.saved(db) .saved(db)
} }

@ -280,7 +280,10 @@ public extension Message {
return try processRawReceivedMessage( return try processRawReceivedMessage(
db, db,
envelope: envelope, envelope: envelope,
serverExpirationTimestamp: (Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds), serverExpirationTimestamp: (
(TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) +
ControlMessageProcessRecord.defaultExpirationSeconds
),
serverHash: serverHash, serverHash: serverHash,
handleClosedGroupKeyUpdateMessages: true handleClosedGroupKeyUpdateMessages: true
) )
@ -296,7 +299,10 @@ public extension Message {
let processedMessage: ProcessedMessage? = try processRawReceivedMessage( let processedMessage: ProcessedMessage? = try processRawReceivedMessage(
db, db,
envelope: envelope, envelope: envelope,
serverExpirationTimestamp: (Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds), serverExpirationTimestamp: (
(TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) +
ControlMessageProcessRecord.defaultExpirationSeconds
),
serverHash: nil, serverHash: nil,
handleClosedGroupKeyUpdateMessages: false handleClosedGroupKeyUpdateMessages: false
) )
@ -428,7 +434,7 @@ public extension Message {
let count: Int64 = rawReaction.you ? rawReaction.count - 1 : rawReaction.count 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 maxLength: Int = shouldAddSelfReaction ? 4 : 5
let desiredReactorIds: [String] = reactors let desiredReactorIds: [String] = reactors
.filter { $0 != blindedUserPublicKey && $0 != userPublicKey } // Remove current user for now, will add back if needed .filter { $0 != blindedUserPublicKey && $0 != userPublicKey } // Remove current user for now, will add back if needed

@ -871,6 +871,7 @@ public enum OpenGroupAPI {
], ],
body: bytes body: bytes
), ),
timeout: FileServerAPI.fileTimeout,
using: dependencies using: dependencies
) )
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies)
@ -890,6 +891,7 @@ public enum OpenGroupAPI {
server: server, server: server,
endpoint: .roomFileIndividual(roomToken, fileId) endpoint: .roomFileIndividual(roomToken, fileId)
), ),
timeout: FileServerAPI.fileTimeout,
using: dependencies using: dependencies
) )
.map { responseInfo, maybeData in .map { responseInfo, maybeData in
@ -1410,6 +1412,7 @@ public enum OpenGroupAPI {
_ db: Database, _ db: Database,
request: Request<T, Endpoint>, request: Request<T, Endpoint>,
forceBlinded: Bool = false, forceBlinded: Bool = false,
timeout: TimeInterval = HTTP.timeout,
using dependencies: SMKDependencies = SMKDependencies() using dependencies: SMKDependencies = SMKDependencies()
) -> Promise<(OnionRequestResponseInfoType, Data?)> { ) -> Promise<(OnionRequestResponseInfoType, Data?)> {
let urlRequest: URLRequest let urlRequest: URLRequest
@ -1434,6 +1437,6 @@ public enum OpenGroupAPI {
return Promise(error: OpenGroupAPIError.signingFailed) 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)
} }
} }

@ -4,6 +4,7 @@ import Foundation
import GRDB import GRDB
import WebRTC import WebRTC
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
extension MessageReceiver { extension MessageReceiver {
public static func handleCallMessage(_ db: Database, message: CallMessage) throws { public static func handleCallMessage(_ db: Database, message: CallMessage) throws {
@ -189,7 +190,7 @@ extension MessageReceiver {
body: String(data: messageInfoData, encoding: .utf8), body: String(data: messageInfoData, encoding: .utf8),
timestampMs: ( timestampMs: (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
) )
.inserted(db) .inserted(db)
@ -235,7 +236,7 @@ extension MessageReceiver {
) )
let timestampMs: Int64 = ( let timestampMs: Int64 = (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
guard let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) else { return nil } guard let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) else { return nil }

@ -4,6 +4,7 @@ import Foundation
import GRDB import GRDB
import Sodium import Sodium
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
extension MessageReceiver { extension MessageReceiver {
public static func handleClosedGroupControlMessage(_ db: Database, _ message: ClosedGroupControlMessage) throws { public static func handleClosedGroupControlMessage(_ db: Database, _ message: ClosedGroupControlMessage) throws {
@ -135,7 +136,7 @@ extension MessageReceiver {
threadId: groupPublicKey, threadId: groupPublicKey,
publicKey: Data(encryptionKeyPair.publicKey), publicKey: Data(encryptionKeyPair.publicKey),
secretKey: Data(encryptionKeyPair.secretKey), secretKey: Data(encryptionKeyPair.secretKey),
receivedTimestamp: Date().timeIntervalSince1970 receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
).insert(db) ).insert(db)
// Start polling // Start polling
@ -196,7 +197,7 @@ extension MessageReceiver {
threadId: groupPublicKey, threadId: groupPublicKey,
publicKey: proto.publicKey.removingIdPrefixIfNeeded(), publicKey: proto.publicKey.removingIdPrefixIfNeeded(),
secretKey: proto.privateKey, secretKey: proto.privateKey,
receivedTimestamp: Date().timeIntervalSince1970 receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
).insert(db) ).insert(db)
} }
catch { catch {
@ -231,7 +232,7 @@ extension MessageReceiver {
.infoMessage(db, sender: sender), .infoMessage(db, sender: sender),
timestampMs: ( timestampMs: (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
).inserted(db) ).inserted(db)
} }
@ -307,7 +308,7 @@ extension MessageReceiver {
.infoMessage(db, sender: sender), .infoMessage(db, sender: sender),
timestampMs: ( timestampMs: (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
).inserted(db) ).inserted(db)
} }
@ -383,7 +384,7 @@ extension MessageReceiver {
.infoMessage(db, sender: sender), .infoMessage(db, sender: sender),
timestampMs: ( timestampMs: (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
).inserted(db) ).inserted(db)
} }
@ -461,7 +462,7 @@ extension MessageReceiver {
.infoMessage(db, sender: sender), .infoMessage(db, sender: sender),
timestampMs: ( timestampMs: (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
).inserted(db) ).inserted(db)
} }

@ -2,6 +2,7 @@
import Foundation import Foundation
import GRDB import GRDB
import SessionSnodeKit
extension MessageReceiver { extension MessageReceiver {
internal static func handleDataExtractionNotification(_ db: Database, message: DataExtractionNotification) throws { internal static func handleDataExtractionNotification(_ db: Database, message: DataExtractionNotification) throws {
@ -24,7 +25,7 @@ extension MessageReceiver {
}(), }(),
timestampMs: ( timestampMs: (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
).inserted(db) ).inserted(db)
} }

@ -4,6 +4,7 @@ import Foundation
import GRDB import GRDB
import SignalCoreKit import SignalCoreKit
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
extension MessageReceiver { extension MessageReceiver {
internal static func handleMessageRequestResponse( internal static func handleMessageRequestResponse(
@ -123,7 +124,7 @@ extension MessageReceiver {
variant: .infoMessageRequestAccepted, variant: .infoMessageRequestAccepted,
timestampMs: ( timestampMs: (
message.sentTimestamp.map { Int64($0) } ?? message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000)) SnodeAPI.currentOffsetTimestampMs()
) )
).inserted(db) ).inserted(db)
} }

@ -6,6 +6,7 @@ import Sodium
import Curve25519Kit import Curve25519Kit
import PromiseKit import PromiseKit
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
extension MessageSender { extension MessageSender {
public static var distributingKeyPairs: Atomic<[String: [ClosedGroupKeyPair]]> = Atomic([:]) public static var distributingKeyPairs: Atomic<[String: [ClosedGroupKeyPair]]> = Atomic([:])
@ -24,7 +25,7 @@ extension MessageSender {
let membersAsData = members.map { Data(hex: $0) } let membersAsData = members.map { Data(hex: $0) }
let admins = [ userPublicKey ] let admins = [ userPublicKey ]
let adminsAsData = admins.map { Data(hex: $0) } let adminsAsData = admins.map { Data(hex: $0) }
let formationTimestamp: TimeInterval = Date().timeIntervalSince1970 let formationTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
let thread: SessionThread = try SessionThread let thread: SessionThread = try SessionThread
.fetchOrCreate(db, id: groupPublicKey, variant: .closedGroup) .fetchOrCreate(db, id: groupPublicKey, variant: .closedGroup)
try ClosedGroup( try ClosedGroup(
@ -91,7 +92,7 @@ extension MessageSender {
threadId: groupPublicKey, threadId: groupPublicKey,
publicKey: encryptionKeyPair.publicKey, publicKey: encryptionKeyPair.publicKey,
secretKey: encryptionKeyPair.privateKey, secretKey: encryptionKeyPair.privateKey,
receivedTimestamp: Date().timeIntervalSince1970 receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
).insert(db) ).insert(db)
// Notify the PN server // Notify the PN server
@ -110,7 +111,7 @@ extension MessageSender {
threadId: thread.id, threadId: thread.id,
authorId: userPublicKey, authorId: userPublicKey,
variant: .infoClosedGroupCreated, variant: .infoClosedGroupCreated,
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) timestampMs: SnodeAPI.currentOffsetTimestampMs()
).inserted(db) ).inserted(db)
// Start polling // Start polling
@ -142,7 +143,7 @@ extension MessageSender {
threadId: closedGroup.threadId, threadId: closedGroup.threadId,
publicKey: legacyNewKeyPair.publicKey, publicKey: legacyNewKeyPair.publicKey,
secretKey: legacyNewKeyPair.privateKey, secretKey: legacyNewKeyPair.privateKey,
receivedTimestamp: Date().timeIntervalSince1970 receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
) )
// Distribute it // Distribute it
@ -230,7 +231,7 @@ extension MessageSender {
body: ClosedGroupControlMessage.Kind body: ClosedGroupControlMessage.Kind
.nameChange(name: name) .nameChange(name: name)
.infoMessage(db, sender: userPublicKey), .infoMessage(db, sender: userPublicKey),
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) timestampMs: SnodeAPI.currentOffsetTimestampMs()
).inserted(db) ).inserted(db)
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
@ -330,7 +331,7 @@ extension MessageSender {
body: ClosedGroupControlMessage.Kind body: ClosedGroupControlMessage.Kind
.membersAdded(members: addedMembers.map { Data(hex: $0) }) .membersAdded(members: addedMembers.map { Data(hex: $0) })
.infoMessage(db, sender: userPublicKey), .infoMessage(db, sender: userPublicKey),
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) timestampMs: SnodeAPI.currentOffsetTimestampMs()
).inserted(db) ).inserted(db)
guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
@ -431,7 +432,7 @@ extension MessageSender {
body: ClosedGroupControlMessage.Kind body: ClosedGroupControlMessage.Kind
.membersRemoved(members: removedMembers.map { Data(hex: $0) }) .membersRemoved(members: removedMembers.map { Data(hex: $0) })
.infoMessage(db, sender: userPublicKey), .infoMessage(db, sender: userPublicKey),
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) timestampMs: SnodeAPI.currentOffsetTimestampMs()
).inserted(db) ).inserted(db)
guard let newInteractionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } guard let newInteractionId: Int64 = interaction.id else { throw StorageError.objectNotSaved }
@ -496,7 +497,7 @@ extension MessageSender {
body: ClosedGroupControlMessage.Kind body: ClosedGroupControlMessage.Kind
.memberLeft .memberLeft
.infoMessage(db, sender: userPublicKey), .infoMessage(db, sender: userPublicKey),
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) timestampMs: SnodeAPI.currentOffsetTimestampMs()
).inserted(db) ).inserted(db)
guard let interactionId: Int64 = interaction.id else { guard let interactionId: Int64 = interaction.id else {

@ -5,6 +5,7 @@ import GRDB
import Sodium import Sodium
import SignalCoreKit import SignalCoreKit
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
public enum MessageReceiver { public enum MessageReceiver {
private static var lastEncryptionKeyPairRequest: [String: Date] = [:] private static var lastEncryptionKeyPairRequest: [String: Date] = [:]
@ -144,7 +145,7 @@ public enum MessageReceiver {
message.sender = sender message.sender = sender
message.recipient = userPublicKey message.recipient = userPublicKey
message.sentTimestamp = envelope.timestamp message.sentTimestamp = envelope.timestamp
message.receivedTimestamp = UInt64((Date().timeIntervalSince1970) * 1000) message.receivedTimestamp = UInt64(SnodeAPI.currentOffsetTimestampMs())
message.groupPublicKey = groupPublicKey message.groupPublicKey = groupPublicKey
message.openGroupServerMessageId = openGroupMessageServerId.map { UInt64($0) } message.openGroupServerMessageId = openGroupMessageServerId.map { UInt64($0) }

@ -67,7 +67,7 @@ public final class MessageSender {
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<Void>.pending()
let userPublicKey: String = getUserHexEncodedPublicKey(db) let userPublicKey: String = getUserHexEncodedPublicKey(db)
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) 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 // Set the timestamp, sender and recipient
message.sentTimestamp = ( message.sentTimestamp = (
@ -202,7 +202,7 @@ public final class MessageSender {
recipient: message.recipient!, recipient: message.recipient!,
data: base64EncodedData, data: base64EncodedData,
ttl: getSpecifiedTTL(db, message: message, isSyncMessage: isSyncMessage) ?? message.ttl, ttl: getSpecifiedTTL(db, message: message, isSyncMessage: isSyncMessage) ?? message.ttl,
timestampMs: UInt64(messageSendTimestamp + SnodeAPI.clockOffset.wrappedValue) timestampMs: UInt64(messageSendTimestamp)
) )
SnodeAPI SnodeAPI
@ -322,7 +322,7 @@ public final class MessageSender {
// Set the timestamp, sender and recipient // Set the timestamp, sender and recipient
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set 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 { switch destination {
@ -472,7 +472,7 @@ public final class MessageSender {
// Set the timestamp, sender and recipient // Set the timestamp, sender and recipient
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set 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 message.sender = userPublicKey
@ -610,6 +610,19 @@ public final class MessageSender {
// Mark the message as sent // Mark the message as sent
try interaction.recipientStates try interaction.recipientStates
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.sent)) .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, message: message,
serverExpirationTimestamp: (Date().timeIntervalSince1970 + ControlMessageProcessRecord.defaultExpirationSeconds) serverExpirationTimestamp: (
(TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) +
ControlMessageProcessRecord.defaultExpirationSeconds
)
)?.insert(db) )?.insert(db)
// Sync the message if: // Sync the message if:

@ -3,6 +3,7 @@
import Foundation import Foundation
import GRDB import GRDB
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionSnodeKit
public class TypingIndicators { public class TypingIndicators {
// MARK: - Direction // MARK: - Direction
@ -41,7 +42,7 @@ public class TypingIndicators {
self.threadId = threadId self.threadId = threadId
self.direction = direction self.direction = direction
self.timestampMs = (timestampMs ?? Int64(floor(Date().timeIntervalSince1970 * 1000))) self.timestampMs = (timestampMs ?? SnodeAPI.currentOffsetTimestampMs())
} }
fileprivate func start(_ db: Database) { fileprivate func start(_ db: Database) {

@ -196,7 +196,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
authorId: getUserHexEncodedPublicKey(db), authorId: getUserHexEncodedPublicKey(db),
variant: .standardOutgoing, variant: .standardOutgoing,
body: body, body: body,
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)), timestampMs: SnodeAPI.currentOffsetTimestampMs(),
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body), hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body),
linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil) linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil)
).inserted(db) ).inserted(db)

@ -93,7 +93,7 @@ public extension SnodeReceivedMessageInfo {
return try SnodeReceivedMessageInfo return try SnodeReceivedMessageInfo
.select(Column.rowID) .select(Column.rowID)
.filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) .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) .asRequest(of: Int64.self)
.fetchAll(db) .fetchAll(db)
} }
@ -122,7 +122,7 @@ public extension SnodeReceivedMessageInfo {
SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false
) )
.filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) .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) .order(SnodeReceivedMessageInfo.Columns.id.desc)
.fetchOne(db) .fetchOne(db)

@ -7,13 +7,17 @@ import PromiseKit
import SessionUtilitiesKit import SessionUtilitiesKit
public protocol OnionRequestAPIType { public protocol OnionRequestAPIType {
static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise<Data> static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?, timeout: TimeInterval) -> Promise<Data>
static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String, timeout: TimeInterval) -> Promise<(OnionRequestResponseInfoType, Data?)>
} }
public extension OnionRequestAPIType { public extension OnionRequestAPIType {
static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> { static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise<Data> {
sendOnionRequest(request, to: server, using: .v4, with: x25519PublicKey) 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 // MARK: - Public API
/// Sends an onion request to `snode`. Builds new paths as needed. /// 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<Data> { public static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String? = nil, timeout: TimeInterval = HTTP.timeout) -> Promise<Data> {
let payloadJson: JSON = [ "method" : method.rawValue, "params" : parameters ] let payloadJson: JSON = [ "method" : method.rawValue, "params" : parameters ]
guard let payload: Data = try? JSONSerialization.data(withJSONObject: payloadJson, options: []) else { 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 /// **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 .map { _, maybeData in
guard let data: Data = maybeData else { throw HTTP.Error.invalidResponse } 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. /// 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 { guard let url = request.url, let host = request.url?.host else {
return Promise(error: OnionRequestAPIError.invalidURL) return Promise(error: OnionRequestAPIError.invalidURL)
} }
@ -412,14 +416,14 @@ public enum OnionRequestAPI: OnionRequestAPIType {
scheme: scheme, scheme: scheme,
port: port 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 promise.catch2 { error in
SNLog("Couldn't reach server: \(url) due to error: \(error).") SNLog("Couldn't reach server: \(url) due to error: \(error).")
} }
return promise 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() let (promise, seal) = Promise<(OnionRequestResponseInfoType, Data?)>.pending()
var guardSnode: Snode? var guardSnode: Snode?
@ -444,7 +448,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
} }
let destinationSymmetricKey = intermediate.destinationSymmetricKey let destinationSymmetricKey = intermediate.destinationSymmetricKey
HTTP.execute(.post, url, body: body) HTTP.execute(.post, url, body: body, timeout: timeout)
.done2 { responseData in .done2 { responseData in
handleResponse( handleResponse(
responseData: responseData, responseData: responseData,
@ -672,7 +676,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
if let timestamp = body["t"] as? Int64 { if let timestamp = body["t"] as? Int64 {
let offset = timestamp - Int64(floor(Date().timeIntervalSince1970 * 1000)) let offset = timestamp - Int64(floor(Date().timeIntervalSince1970 * 1000))
SnodeAPI.clockOffset.mutate { $0 = offset } SnodeAPI.clockOffsetMs.mutate { $0 = offset }
} }
guard 200...299 ~= statusCode else { guard 200...299 ~= statusCode else {

@ -19,10 +19,16 @@ public final class SnodeAPI {
internal static var snodePool: Atomic<Set<Snode>> = Atomic([]) internal static var snodePool: Atomic<Set<Snode>> = Atomic([])
/// The offset between the user's clock and the Service Node's clock. Used in cases where the /// The offset between the user's clock and the Service Node's clock. Used in cases where the
/// user's clock is incorrect. /// user's clock is incorrect
/// public static var clockOffsetMs: Atomic<Int64> = Atomic(0)
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var clockOffset: Atomic<Int64> = 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. /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var swarmCache: Atomic<[String: Set<Snode>]> = Atomic([:]) public static var swarmCache: Atomic<[String: Set<Snode>]> = Atomic([:])
@ -546,7 +552,7 @@ public final class SnodeAPI {
let lastHash = SnodeReceivedMessageInfo.fetchLastNotExpired(for: snode, namespace: namespace, associatedWith: publicKey)?.hash ?? "" let lastHash = SnodeReceivedMessageInfo.fetchLastNotExpired(for: snode, namespace: namespace, associatedWith: publicKey)?.hash ?? ""
// Construct signature // Construct signature
let timestamp = UInt64(Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.clockOffset.wrappedValue) let timestamp = UInt64(SnodeAPI.currentOffsetTimestampMs())
let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString()
let namespaceVerificationString = (namespace == defaultNamespace ? "" : String(namespace)) let namespaceVerificationString = (namespace == defaultNamespace ? "" : String(namespace))
@ -647,7 +653,7 @@ public final class SnodeAPI {
} }
// Construct signature // Construct signature
let timestamp = UInt64(Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.clockOffset.wrappedValue) let timestamp = UInt64(SnodeAPI.currentOffsetTimestampMs())
let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString()
guard guard
@ -1107,3 +1113,11 @@ public final class SnodeAPI {
return nil return nil
} }
} }
@objc(SNSnodeAPI)
public final class SNSnodeAPI: NSObject {
@objc(currentOffsetTimestampMs)
public static func currentOffsetTimestampMs() -> UInt64 {
return UInt64(SnodeAPI.currentOffsetTimestampMs())
}
}

@ -53,7 +53,7 @@ extension ECKeyPair {
public extension Identity { public extension Identity {
static func generate(from seed: Data) throws -> (ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { 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) let padding = Data(repeating: 0, count: 16)
guard guard

@ -19,6 +19,7 @@ public enum General {
} }
public enum GeneralError: Error { public enum GeneralError: Error {
case invalidSeed
case keyGenerationFailed case keyGenerationFailed
} }

@ -8,10 +8,7 @@ import SessionMessagingKit
public enum Configuration { public enum Configuration {
public static func performMainSetup() { public static func performMainSetup() {
// Need to do this first to ensure the legacy database exists // Need to do this first to ensure the legacy database exists
SNUtilitiesKit.configure( SNUtilitiesKit.configure(maxFileSize: UInt(FileServerAPI.maxFileSize))
maxFileSize: UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier)
)
SNMessagingKit.configure() SNMessagingKit.configure()
SNSnodeKit.configure() SNSnodeKit.configure()
SNUIKit.configure() SNUIKit.configure()

Loading…
Cancel
Save