diff --git a/Sources/Bluetooth/Extensions/UUID.swift b/Sources/Bluetooth/Extensions/UUID.swift index 88d1e505d..850916f9a 100644 --- a/Sources/Bluetooth/Extensions/UUID.swift +++ b/Sources/Bluetooth/Extensions/UUID.swift @@ -8,6 +8,7 @@ #if canImport(Foundation) import Foundation +#endif internal extension UUID { @@ -16,45 +17,222 @@ internal extension UUID { static var unformattedStringLength: Int { return 32 } } -internal extension UUID { // ByteValue - - typealias ByteValue = uuid_t - - static var bitWidth: Int { return 128 } - - @inline(__always) - init(bytes: uuid_t) { +extension UUID: ByteValue { + public static var bitWidth: Int { return 128 } + + public init(bytes: ByteValue) { self.init(uuid: bytes) } - var bytes: uuid_t { - - @inline(__always) + public var bytes: ByteValue { get { return uuid } - - @inline(__always) set { self = UUID(uuid: newValue) } } } -internal extension UUID { +#if canImport(Foundation) +public extension Foundation.UUID { + + typealias ByteValue = uuid_t +} + +internal extension Foundation.UUID { - init?(data: Data) { + init?(data: Foundation.Data) { guard data.count == UUID.length else { return nil } self.init(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15])) } - var data: Data { + var data: Foundation.Data { return Data([bytes.0, bytes.1, bytes.2, bytes.3, bytes.4, bytes.5, bytes.6, bytes.7, bytes.8, bytes.9, bytes.10, bytes.11, bytes.12, bytes.13, bytes.14, bytes.15]) } } + +/// Internal UUID type. +internal struct _UUID: Sendable { + + public typealias ByteValue = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) + + public let uuid: ByteValue + + /// Create a UUID from a `uuid_t`. + public init(uuid: ByteValue) { + self.uuid = uuid + } +} + +#else + +/// Represents UUID strings, which can be used to uniquely identify types, interfaces, and other items. +public struct UUID: Sendable { + + public typealias ByteValue = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) + + public let uuid: ByteValue + + /// Create a UUID from a `uuid_t`. + public init(uuid: ByteValue) { + self.uuid = uuid + } +} + +internal typealias _UUID = Bluetooth.UUID // Built-in UUID type + #endif -internal extension UInt128 { +extension _UUID { - /// Parse a UUID string. - init?(uuidString string: String) { + /// Create a new UUID with RFC 4122 version 4 random bytes. + public init() { + var uuidBytes: ByteValue = ( + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255), + .random(in: 0...255) + ) + + // Set the version to 4 (random UUID) + uuidBytes.6 = (uuidBytes.6 & 0x0F) | 0x40 + + // Set the variant to RFC 4122 + uuidBytes.8 = (uuidBytes.8 & 0x3F) | 0x80 + + self.init(uuid: uuidBytes) + } + + @inline(__always) + internal func withUUIDBytes(_ work: (UnsafeBufferPointer) throws -> R) rethrows -> R { + return try withExtendedLifetime(self) { + try withUnsafeBytes(of: uuid) { rawBuffer in + return try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in + return try work(buffer) + } + } + } + } + + /// Create a UUID from a string such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F". + /// + /// Returns nil for invalid strings. + public init?(uuidString string: String) { + guard let value = UInt128.bigEndian(uuidString: string) else { + return nil + } + self.init(uuid: value.bytes) + } + + /// Returns a string created from the UUID, such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" + public var uuidString: String { + UInt128(bytes: uuid).bigEndianUUIDString + } +} + +extension _UUID: Equatable { + + public static func ==(lhs: _UUID, rhs: _UUID) -> Bool { + withUnsafeBytes(of: lhs) { lhsPtr in + withUnsafeBytes(of: rhs) { rhsPtr in + let lhsTuple = lhsPtr.loadUnaligned(as: (UInt64, UInt64).self) + let rhsTuple = rhsPtr.loadUnaligned(as: (UInt64, UInt64).self) + return (lhsTuple.0 ^ rhsTuple.0) | (lhsTuple.1 ^ rhsTuple.1) == 0 + } + } + } +} + +extension _UUID: Hashable { + + public func hash(into hasher: inout Hasher) { + withUnsafeBytes(of: uuid) { buffer in + hasher.combine(bytes: buffer) + } + } +} + +extension _UUID: CustomStringConvertible, CustomDebugStringConvertible { + + public var description: String { + return uuidString + } + + public var debugDescription: String { + return description + } +} + +#if !hasFeature(Embedded) +@available(macOS 10.8, iOS 6.0, tvOS 9.0, watchOS 2.0, *) +extension _UUID : CustomReflectable { + public var customMirror: Mirror { + let c : [(label: String?, value: Any)] = [] + let m = Mirror(self, children:c, displayStyle: .struct) + return m + } +} + +@available(macOS 10.8, iOS 6.0, tvOS 9.0, watchOS 2.0, *) +extension _UUID : Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let uuidString = try container.decode(String.self) + + guard let uuid = _UUID(uuidString: uuidString) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, + debugDescription: "Attempted to decode UUID from invalid UUID string.")) + } + + self = uuid + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.uuidString) + } +} +#endif + +extension _UUID : Comparable { + + public static func < (lhs: _UUID, rhs: _UUID) -> Bool { + var leftUUID = lhs.uuid + var rightUUID = rhs.uuid + var result: Int = 0 + var diff: Int = 0 + withUnsafeBytes(of: &leftUUID) { leftPtr in + withUnsafeBytes(of: &rightUUID) { rightPtr in + for offset in (0 ..< MemoryLayout.size).reversed() { + diff = Int(leftPtr.load(fromByteOffset: offset, as: UInt8.self)) - + Int(rightPtr.load(fromByteOffset: offset, as: UInt8.self)) + // Constant time, no branching equivalent of + // if (diff != 0) { + // result = diff; + // } + result = (result & (((diff - 1) & ~diff) >> 8)) | diff + } + } + } + + return result < 0 + } +} + +fileprivate extension UInt128 { + + /// Parse a UUID string and return a value in big endian order. + static func bigEndian(uuidString string: String) -> UInt128? { let separator: Character = "-" guard string.count == 36 else { return nil @@ -72,16 +250,14 @@ internal extension UInt128 { let hexadecimal = (a + b + c + d + e) guard hexadecimal.count == 32, let bytes = [UInt8].init(hexadecimal: hexadecimal), - let value = UInt128(bytes) else { + let value = UInt128.init(bytes) else { return nil } - self.init(bigEndian: value) + return value } - /// Generate UUID string, e.g. `0F4DD6A4-0F71-48EF-98A5-996301B868F9`. - var uuidString: String { - - let bytes = self.bigEndian.bytes + /// Generate UUID string, e.g. `0F4DD6A4-0F71-48EF-98A5-996301B868F9` from a value initialized in its big endian order. + var bigEndianUUIDString: String { let a = (bytes.0.toHexadecimal() + bytes.1.toHexadecimal() @@ -107,3 +283,19 @@ internal extension UInt128 { return a + "-" + b + "-" + c + "-" + d + "-" + e } } + +internal extension UInt128 { + + /// Parse a UUID string. + init?(uuidString string: String) { + guard let value = Self.bigEndian(uuidString: string) else { + return nil + } + self.init(bigEndian: value) + } + + /// Generate UUID string, e.g. `0F4DD6A4-0F71-48EF-98A5-996301B868F9`. + var uuidString: String { + self.bigEndian.bigEndianUUIDString + } +} diff --git a/Sources/Bluetooth/UInt128.swift b/Sources/Bluetooth/UInt128.swift index 9de09caf0..61fcd7aea 100644 --- a/Sources/Bluetooth/UInt128.swift +++ b/Sources/Bluetooth/UInt128.swift @@ -78,9 +78,7 @@ extension UInt128: Hashable { extension UInt128: CustomStringConvertible { public var description: String { - let bytes = self.bigEndian.bytes - return bytes.0.toHexadecimal() + bytes.1.toHexadecimal() + bytes.2.toHexadecimal() @@ -106,11 +104,9 @@ public extension UInt128 { static var length: Int { return 16 } - init?(_ data: C) where C: Collection, C.Element == UInt8, C.Index == Int { - + init?(_ data: C) where C: RandomAccessCollection, C.Element == UInt8, C.Index == Int { guard data.count == UInt128.length else { return nil } - self.init(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15])) } } @@ -155,10 +151,9 @@ extension UInt128: ByteSwap { // MARK: - NSUUID -#if canImport(Foundation) public extension UInt128 { - init(uuid: Foundation.UUID) { + init(uuid: UUID) { /// UUID is always big endian let bigEndian = UInt128(bytes: uuid.uuid) @@ -166,7 +161,7 @@ public extension UInt128 { } } -public extension Foundation.UUID { +public extension UUID { init(_ value: UInt128) { @@ -191,7 +186,6 @@ public extension Foundation.UUID { bytes.15)) } } -#endif // MARK: - ExpressibleByIntegerLiteral diff --git a/Sources/Bluetooth/iBeacon.swift b/Sources/Bluetooth/iBeacon.swift index 47a9b343c..c27513d97 100644 --- a/Sources/Bluetooth/iBeacon.swift +++ b/Sources/Bluetooth/iBeacon.swift @@ -8,6 +8,7 @@ #if canImport(Foundation) import Foundation +#endif /** Apple iBeacon @@ -45,7 +46,7 @@ public struct AppleBeacon: Equatable, Hashable, Sendable { /// The received signal strength indicator (RSSI) value (measured in decibels) for the device. public var rssi: Int8 - public init(uuid: Foundation.UUID, + public init(uuid: UUID, major: UInt16 = 0, minor: UInt16 = 0, rssi: Int8) { @@ -56,5 +57,3 @@ public struct AppleBeacon: Equatable, Hashable, Sendable { self.rssi = rssi } } - -#endif \ No newline at end of file diff --git a/Sources/BluetoothGAP/Extensions/UUID.swift b/Sources/BluetoothGAP/Extensions/UUID.swift index 535bde5ea..ca3d5f825 100644 --- a/Sources/BluetoothGAP/Extensions/UUID.swift +++ b/Sources/BluetoothGAP/Extensions/UUID.swift @@ -8,38 +8,11 @@ import Foundation -internal extension UUID { - - static var length: Int { return 16 } -} - -internal extension UUID { // ByteValue - - typealias ByteValue = uuid_t - - static var bitWidth: Int { return 128 } - - @inline(__always) - init(bytes: uuid_t) { - - self.init(uuid: bytes) - } - - var bytes: uuid_t { - - @inline(__always) - get { return uuid } - - @inline(__always) - set { self = UUID(uuid: newValue) } - } -} - internal extension UUID { init?(data: Data) { - guard data.count == UUID.length else { return nil } + guard data.count == (UUID.bitWidth / 8) else { return nil } self.init(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15])) }