Skip to content

Commit

Permalink
Merge pull request #159 from PureSwift/feature/uuid
Browse files Browse the repository at this point in the history
Add `UUID` implementation
  • Loading branch information
colemancda authored Nov 4, 2024
2 parents 1c0fdbc + 116db3b commit 8f75e18
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 65 deletions.
242 changes: 217 additions & 25 deletions Sources/Bluetooth/Extensions/UUID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#if canImport(Foundation)
import Foundation
#endif

internal extension UUID {

Expand All @@ -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
}
}

public 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<R>(_ work: (UnsafeBufferPointer<UInt8>) 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<ByteValue>.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
Expand All @@ -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()
Expand All @@ -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
}
}
12 changes: 3 additions & 9 deletions Sources/Bluetooth/UInt128.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -106,11 +104,9 @@ public extension UInt128 {

static var length: Int { return 16 }

init?<C>(_ data: C) where C: Collection, C.Element == UInt8, C.Index == Int {

init?<C>(_ 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]))
}
}
Expand Down Expand Up @@ -155,18 +151,17 @@ 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)
self.init(bigEndian: bigEndian)
}
}

public extension Foundation.UUID {
public extension UUID {

init(_ value: UInt128) {

Expand All @@ -191,7 +186,6 @@ public extension Foundation.UUID {
bytes.15))
}
}
#endif

// MARK: - ExpressibleByIntegerLiteral

Expand Down
5 changes: 2 additions & 3 deletions Sources/Bluetooth/iBeacon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#if canImport(Foundation)
import Foundation
#endif

/**
Apple iBeacon
Expand Down Expand Up @@ -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) {
Expand All @@ -56,5 +57,3 @@ public struct AppleBeacon: Equatable, Hashable, Sendable {
self.rssi = rssi
}
}

#endif
Loading

0 comments on commit 8f75e18

Please sign in to comment.