// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation /// Generates timestamps for messages/envelopes. public class MessageTimestampGenerator { private let rangeToAvoid = AtomicValue?>(nil, lock: .init()) private let nowMs: () -> UInt64 public static let sharedInstance = MessageTimestampGenerator() public init(nowMs: @escaping () -> UInt64 = NSDate.ows_millisecondTimeStamp) { self.nowMs = nowMs } /// Generates a new timestamp from the device's local clock. /// /// Performs a few heuristics to try and avoid generating the same timestamp /// repeatedly when called in a tight loop. public func generateTimestamp() -> UInt64 { let generatedTimestamp = max(nowMs(), 1) return rangeToAvoid.update { rangeToAvoid in let newRangeToAvoid = Self.avoidAndExtendRange(rangeToAvoid, proposedValue: generatedTimestamp) rangeToAvoid = newRangeToAvoid return newRangeToAvoid.upperBound } } private static func avoidAndExtendRange( _ oldRange: ClosedRange?, proposedValue: UInt64, ) -> ClosedRange { if let oldRange, oldRange.contains(proposedValue) { // If we have a range from the last value, ensure that the new one is // higher. We track the range to handle cases where `generateTimestamp()` // is called twice for `t1` and then once for `t1 + 1`. return oldRange.lowerBound...(oldRange.upperBound + 1) } else { // Otherwise, we assume there's no conflict and return `proposedValue`. return proposedValue...proposedValue } } }