Signal-iOS/SignalServiceKit/Util/UUIDv7.swift
2026-03-10 09:33:44 -05:00

68 lines
2.6 KiB
Swift

//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
extension UUID {
/// Returns a UUIDv7, as defined in [the RFC][RFC]. Notably, v7 UUIDs embed
/// a timestamp in the first 6 most-significant bytes, thereby allowing
/// callers who pass sequential timestamps to construct UUIDs that are
/// lexicographically sequential.
///
/// This sequential property can be a useful optimization when the UUIDs are
/// part of a database row and that row is indexed by the UUID. When the
/// UUIDs are ordered in the same sequence as the row insertions the
/// corresponding insertion into the index is always at the end of the
/// index (`O(1)`), whereas a random UUID might be inserted anywhere in the
/// index (`O(log n)`).
///
/// [RFC]: https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7
static func v7(timestamp: UInt64) -> UUID {
return UUID(uuid: (
// Assign the lower six bytes of the timestamp (the bytes that change
// the most) to the first six bytes of the UUID, in big-endian order
// (most-significant byte first).
// Assign timestamp to the first 6 bytes (big-endian)
// For each of the first six bytes of the
UInt8((timestamp >> 40) & 0xFF),
UInt8((timestamp >> 32) & 0xFF),
UInt8((timestamp >> 24) & 0xFF),
UInt8((timestamp >> 16) & 0xFF),
UInt8((timestamp >> 8) & 0xFF),
UInt8(timestamp & 0xFF),
// Set the next four bits to 0b0111, the required "version" for UUIDv7.
// Set the rest of that byte to random.
((0b0111 as UInt8) << 4) | UInt8.random(in: 0...((1 << 4) - 1)),
// Add a full byte of random.
UInt8.random(in: 0...0xFF),
// Set the next two bits to 0b10, the required "variant" for UUIDv7. Set
// the rest of that byte to random.
((0b10 as UInt8) << 6) | UInt8.random(in: 0...((1 << 6) - 1)),
// Set the remaining bytes to random.
UInt8.random(in: 0...0xFF),
UInt8.random(in: 0...0xFF),
UInt8.random(in: 0...0xFF),
UInt8.random(in: 0...0xFF),
UInt8.random(in: 0...0xFF),
UInt8.random(in: 0...0xFF),
UInt8.random(in: 0...0xFF),
))
}
}
extension NSUUID {
private static var sequentialCounter: UInt64 = Date().ows_millisecondsSince1970
@objc
static func sequential() -> NSUUID {
let uuid: UUID = .v7(timestamp: sequentialCounter)
sequentialCounter += 1
return uuid as NSUUID
}
}