From 836db2f4e4ec01d22f6fcb4db36989b6f8791cc6 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 13:58:08 -0500 Subject: [PATCH 01/12] #158 Add Swift StdLib `UInt128` support --- Sources/Bluetooth/UInt128.swift | 360 ++++++++++++++++++++++---------- 1 file changed, 253 insertions(+), 107 deletions(-) diff --git a/Sources/Bluetooth/UInt128.swift b/Sources/Bluetooth/UInt128.swift index 61fcd7aea..0b3039beb 100644 --- a/Sources/Bluetooth/UInt128.swift +++ b/Sources/Bluetooth/UInt128.swift @@ -9,92 +9,26 @@ import Foundation #endif -/// A 128 bit number stored according to host endianness. -/// -/// Unlike `NSUUID` which is always stored in big endian. -@frozen -public struct UInt128: ByteValue, Sendable { +// MARK: - ByteValue + +extension UInt128: ByteValue { public typealias ByteValue = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) - - public static var bitWidth: Int { return 128 } - - public var bytes: ByteValue - - public init(bytes: ByteValue = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) { - self.bytes = bytes - } -} - -public extension UInt128 { - - /// The minimum representable value in this type. - static var min: UInt128 { return UInt128(bytes: (.min, .min, .min, .min, .min, .min, .min, .min, .min, .min, .min, .min, .min, .min, .min, .min)) } + public var bytes: ByteValue { + @_transparent + get { + unsafeBitCast(self, to: ByteValue.self) + } - /// The maximum representable value in this type. - static var max: UInt128 { return UInt128(bytes: (.max, .max, .max, .max, .max, .max, .max, .max, .max, .max, .max, .max, .max, .max, .max, .max)) } - - /// The value with all bits set to zero. - static var zero: UInt128 { return .min } -} - -// MARK: - Equatable - -extension UInt128: Equatable { - - public static func == (lhs: UInt128, rhs: UInt128) -> Bool { - - return lhs.bytes.0 == rhs.bytes.0 && - lhs.bytes.1 == rhs.bytes.1 && - lhs.bytes.2 == rhs.bytes.2 && - lhs.bytes.3 == rhs.bytes.3 && - lhs.bytes.4 == rhs.bytes.4 && - lhs.bytes.5 == rhs.bytes.5 && - lhs.bytes.6 == rhs.bytes.6 && - lhs.bytes.7 == rhs.bytes.7 && - lhs.bytes.8 == rhs.bytes.8 && - lhs.bytes.9 == rhs.bytes.9 && - lhs.bytes.10 == rhs.bytes.10 && - lhs.bytes.11 == rhs.bytes.11 && - lhs.bytes.12 == rhs.bytes.12 && - lhs.bytes.13 == rhs.bytes.13 && - lhs.bytes.14 == rhs.bytes.14 && - lhs.bytes.15 == rhs.bytes.15 + @_transparent + set { + self = .init(bytes: newValue) + } } -} - -// MARK: - Hashable - -extension UInt128: Hashable { - public func hash(into hasher: inout Hasher) { - withUnsafeBytes(of: bytes) { hasher.combine(bytes: $0) } - } -} - -// MARK: - CustomStringConvertible - -extension UInt128: CustomStringConvertible { - - public var description: String { - let bytes = self.bigEndian.bytes - return bytes.0.toHexadecimal() - + bytes.1.toHexadecimal() - + bytes.2.toHexadecimal() - + bytes.3.toHexadecimal() - + bytes.4.toHexadecimal() - + bytes.5.toHexadecimal() - + bytes.6.toHexadecimal() - + bytes.7.toHexadecimal() - + bytes.8.toHexadecimal() - + bytes.9.toHexadecimal() - + bytes.10.toHexadecimal() - + bytes.11.toHexadecimal() - + bytes.12.toHexadecimal() - + bytes.13.toHexadecimal() - + bytes.14.toHexadecimal() - + bytes.15.toHexadecimal() + public init(bytes: ByteValue) { + self = unsafeBitCast(bytes, to: Self.self) } } @@ -126,30 +60,9 @@ public extension UInt128 { // MARK: - Byte Swap -extension UInt128: ByteSwap { - - /// A representation of this integer with the byte order swapped. - public var byteSwapped: UInt128 { - return UInt128(bytes: (bytes.15, - bytes.14, - bytes.13, - bytes.12, - bytes.11, - bytes.10, - bytes.9, - bytes.8, - bytes.7, - bytes.6, - bytes.5, - bytes.4, - bytes.3, - bytes.2, - bytes.1, - bytes.0)) - } -} +extension UInt128: ByteSwap { } -// MARK: - NSUUID +// MARK: - UUID public extension UInt128 { @@ -187,12 +100,245 @@ public extension UUID { } } +// MARK: - Backwards compatibility + +#if canImport(Darwin) +/// A 128-bit signed integer value type. +@frozen +public struct UInt128: Sendable { + +#if _endian(little) + public var _low: UInt64 + public var _high: UInt64 +#else + public var _high: UInt64 + public var _low: UInt64 +#endif + + @_transparent + public init(_low: UInt64, _high: UInt64) { + self._low = _low + self._high = _high + } + + // Allow conversion if type is available. + @available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) + public var _value: Int128 { + @_transparent + get { + unsafeBitCast(self, to: Int128.self) + } + + @_transparent + set { + self = Self(newValue) + } + } + + @available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) + @_transparent + public init(_ _value: Int128) { + self = unsafeBitCast(_value, to: Self.self) + } + + /// Creates a new instance with the same memory representation as the given + /// value. + /// + /// This initializer does not perform any range or overflow checking. The + /// resulting instance may not have the same numeric value as + /// `bitPattern`---it is only guaranteed to use the same pattern of bits in + /// its binary representation. + /// + /// - Parameter bitPattern: A value to use as the source of the new instance's + /// binary representation. + @available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) + @_transparent + public init(bitPattern: Int128) { + self.init(bitPattern) + } +} + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +public extension Bluetooth.UInt128 { + + init(_ value: Swift.UInt128) { + self.init(_low: value._low, _high: value._high) + } +} + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +public extension Swift.UInt128 { + + init(_ value: Bluetooth.UInt128) { + self.init(bitPattern: value._value) + } +} + +// MARK: - Constants + +extension Bluetooth.UInt128 { + @_transparent + public static var zero: Self { + Self(_low: .min, _high: .min) + } + + @_transparent + public static var min: Self { + zero + } + + @_transparent + public static var max: Self { + Self(_low: .max, _high: .max) + } +} + // MARK: - ExpressibleByIntegerLiteral -extension UInt128: ExpressibleByIntegerLiteral { +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension Bluetooth.UInt128: ExpressibleByIntegerLiteral { - public init(integerLiteral value: UInt64) { - let bytes = value.bigEndian.bytes - self = UInt128(bigEndian: UInt128(bytes: (0, 0, 0, 0, 0, 0, 0, 0, bytes.0, bytes.1, bytes.2, bytes.3, bytes.4, bytes.5, bytes.6, bytes.7))) + public typealias IntegerLiteralType = Swift.UInt128 + + public init(integerLiteral value: Swift.UInt128) { + self.init(_low: value._low, _high: value._high) + } +} + +// MARK: - Conversions + +extension Bluetooth.UInt128 { + + @inlinable + public init?(exactly source: T) where T: BinaryInteger { + guard let high = UInt64(exactly: source >> 64) else { return nil } + let low = UInt64(truncatingIfNeeded: source) + self.init(_low: low, _high: high) + } + + @inlinable + public init(_ source: T) where T: BinaryInteger { + guard let value = Self(exactly: source) else { + fatalError("value cannot be converted to UInt128 because it is outside the representable range") + } + self = value + } + + @inlinable + public init(clamping source: T) where T: BinaryInteger { + guard let value = Self(exactly: source) else { + self = source < .zero ? .zero : .max + return + } + self = value + } + + @inlinable + public init(truncatingIfNeeded source: T) where T: BinaryInteger { + let high = UInt64(truncatingIfNeeded: source >> 64) + let low = UInt64(truncatingIfNeeded: source) + self.init(_low: low, _high: high) + } + + @_transparent + public init(_truncatingBits source: UInt) { + self.init(_low: UInt64(source), _high: .zero) + } +} + +extension UInt128 { + @inlinable + public init?(exactly source: T) where T: BinaryFloatingPoint { + let highAsFloat = (source * 0x1.0p-64).rounded(.towardZero) + guard let high = UInt64(exactly: highAsFloat) else { return nil } + guard let low = UInt64( + exactly: high == 0 ? source : source - 0x1.0p64*highAsFloat + ) else { return nil } + self.init(_low: low, _high: high) + } + + @inlinable + public init(_ source: T) where T: BinaryFloatingPoint { + guard let value = Self(exactly: source.rounded(.towardZero)) else { + fatalError("value cannot be converted to UInt128 because it is outside the representable range") + } + self = value + } +} + +// MARK: - Equatable + +extension UInt128: Equatable { + + public static func == (lhs: UInt128, rhs: UInt128) -> Bool { + return lhs._low == rhs._low + && lhs._high == rhs._high + } +} + +// MARK: - Hashable + +extension UInt128: Hashable { + + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(_low) + hasher.combine(_high) + } +} + +// MARK: - Comparable + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension UInt128: Comparable { + + @_transparent + public static func < (lhs: Self, rhs: Self) -> Bool { + lhs._value < rhs._value + } +} + +// MARK: - CustomStringConvertible + +extension UInt128: CustomStringConvertible { + + public var description: String { + if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { + return Swift.UInt128(self).description + } else { + let bytes = self.bigEndian.bytes + return bytes.0.toHexadecimal() + + bytes.1.toHexadecimal() + + bytes.2.toHexadecimal() + + bytes.3.toHexadecimal() + + bytes.4.toHexadecimal() + + bytes.5.toHexadecimal() + + bytes.6.toHexadecimal() + + bytes.7.toHexadecimal() + + bytes.8.toHexadecimal() + + bytes.9.toHexadecimal() + + bytes.10.toHexadecimal() + + bytes.11.toHexadecimal() + + bytes.12.toHexadecimal() + + bytes.13.toHexadecimal() + + bytes.14.toHexadecimal() + + bytes.15.toHexadecimal() + } + } +} + +// TODO: Implement Integer protocols +extension Bluetooth.UInt128 { + + @_transparent + public static var bitWidth: Int { 128 } + + public var byteSwapped: Self { + return Self(_low: _high.byteSwapped, _high: _low.byteSwapped) } } + + +#else +public typealias UInt128 = Swift.UInt128 +#endif From 094efc62d98ac1e336095d83af564bf789bbb89d Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 13:58:15 -0500 Subject: [PATCH 02/12] Updated unit tests --- Tests/BluetoothTests/BluetoothUUIDTests.swift | 8 ++++-- Tests/BluetoothTests/HCITests.swift | 4 +-- Tests/BluetoothTests/UInt128Tests.swift | 25 ++++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Tests/BluetoothTests/BluetoothUUIDTests.swift b/Tests/BluetoothTests/BluetoothUUIDTests.swift index 7a6b4b0a3..b1e1278f8 100644 --- a/Tests/BluetoothTests/BluetoothUUIDTests.swift +++ b/Tests/BluetoothTests/BluetoothUUIDTests.swift @@ -144,7 +144,9 @@ final class BluetoothUUIDTests: XCTestCase { XCTAssertNotEqual(uuid.hashValue, 0) XCTAssertEqual(uuid, .bit16(uuidValue)) XCTAssertNotEqual(uuid, .bit16(0xFEAA)) - XCTAssertNotEqual(uuid, .bit128(UInt128())) + if #available(macOS 15, iOS 18, *) { + XCTAssertNotEqual(uuid, .bit128(0x00)) + } XCTAssertNotEqual(uuid, .bit32(0x12345678)) XCTAssertEqual(uuid.littleEndian.data, Data([uuidValue.littleEndian.bytes.0, @@ -173,7 +175,9 @@ final class BluetoothUUIDTests: XCTestCase { XCTAssertEqual(uuid, .bit32(uuidValue)) XCTAssertNotEqual(uuid, .bit32(0x1234)) XCTAssertNotEqual(uuid, .bit16(0xFEA9)) - XCTAssertNotEqual(uuid, .bit128(UInt128())) + if #available(macOS 15, iOS 18, *) { + XCTAssertNotEqual(uuid, .bit128(0x00)) + } XCTAssertEqual(uuid.littleEndian.data, Data([uuidValue.littleEndian.bytes.0, uuidValue.littleEndian.bytes.1, diff --git a/Tests/BluetoothTests/HCITests.swift b/Tests/BluetoothTests/HCITests.swift index 92a238117..49baac15f 100644 --- a/Tests/BluetoothTests/HCITests.swift +++ b/Tests/BluetoothTests/HCITests.swift @@ -1264,11 +1264,11 @@ final class HCITests: XCTestCase { XCTAssertEqual(HCILEEncryptReturn(data: Data([/* 0x02, 0x17, 0x20, 0x00, */ 0x66, 0xc6, 0xc2, 0x27, 0x8e, 0x3b, 0x8e, 0x05, 0x3e, 0x7e, 0xa3, 0x26, 0x52, 0x1b, 0xad, 0x99]))?.encryptedData.description, "99AD1B5226A37E3E058E3B8E27C2C666") - var encryptedData: UInt128 = 0 + var encryptedData: UInt128 = .zero (encryptedData = try await hostController.lowEnergyEncrypt(key: key, data: plainTextData)) XCTAssert(hostController.queue.isEmpty) - XCTAssertNotEqual(encryptedData, 0) + XCTAssertNotEqual(encryptedData, .zero) XCTAssertEqual(encryptedData.description, "99AD1B5226A37E3E058E3B8E27C2C666") } diff --git a/Tests/BluetoothTests/UInt128Tests.swift b/Tests/BluetoothTests/UInt128Tests.swift index 896e6953a..1d8167aa7 100644 --- a/Tests/BluetoothTests/UInt128Tests.swift +++ b/Tests/BluetoothTests/UInt128Tests.swift @@ -14,14 +14,14 @@ final class UInt128Tests: XCTestCase { func testBitWidth() { - XCTAssertEqual(UInt128.bitWidth, MemoryLayout.size * 8) - XCTAssertEqual(UInt128.bitWidth, 128) + XCTAssertEqual(Bluetooth.UInt128.bitWidth, MemoryLayout.size * 8) + XCTAssertEqual(Bluetooth.UInt128.bitWidth, 128) } func testUUID() { let uuid = UUID(uuidString: "60F14FE2-F972-11E5-B84F-23E070D5A8C7")! - let value = UInt128(uuid: uuid) + let value = Bluetooth.UInt128(uuid: uuid) XCTAssertEqual(UUID(value), uuid) XCTAssertEqual(value.description, "60F14FE2F97211E5B84F23E070D5A8C7") @@ -29,21 +29,28 @@ final class UInt128Tests: XCTestCase { func testHashable() { - XCTAssertNotEqual(UInt128.max.hashValue, 0) + XCTAssertNotEqual(Bluetooth.UInt128.max.hashValue, 0) } func testExpressibleByIntegerLiteral() { + guard #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) else { + print("Skipping \(#function), requires macOS 15") + return + } + let values: [(UInt128, String)] = [ - (UInt128.zero, "00000000000000000000000000000000"), - (0x00000000000000000000000000000000, "00000000000000000000000000000000"), - (0x00000000000000000000000000000001, "00000000000000000000000000000001"), - (0x00000000000000000000000000000020, "00000000000000000000000000000020"), - (0x0000000000000000DCBABEBAAFDE0001, "0000000000000000DCBABEBAAFDE0001") + (UInt128.zero, "00000000000000000000000000000000"), + (0x00000000000000000000000000000000, "00000000000000000000000000000000"), + (0x00000000000000000000000000000001, "00000000000000000000000000000001"), + (100000000000000000000000000000, "10000000000000000000000000000000"), + (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") ] values.forEach { XCTAssertEqual($0.description, $1) } XCTAssertEqual(UInt128.zero, 0) + XCTAssertEqual(UInt128.min, 0) + XCTAssertEqual(UInt128.max, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) } } From ab73bdd1ddc0a891f3446fbba9dce63510f6dd76 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 14:12:02 -0500 Subject: [PATCH 03/12] #154 Fix `UInt128` Embedded Swift support --- Sources/Bluetooth/UInt128.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/Bluetooth/UInt128.swift b/Sources/Bluetooth/UInt128.swift index 0b3039beb..8d76c3c37 100644 --- a/Sources/Bluetooth/UInt128.swift +++ b/Sources/Bluetooth/UInt128.swift @@ -60,7 +60,8 @@ public extension UInt128 { // MARK: - Byte Swap -extension UInt128: ByteSwap { } +// TODO: Conflicts with FixedWidthInteger +//extension UInt128: ByteSwap { } // MARK: - UUID @@ -268,7 +269,7 @@ extension UInt128 { // MARK: - Equatable -extension UInt128: Equatable { +extension Bluetooth.UInt128: Equatable { public static func == (lhs: UInt128, rhs: UInt128) -> Bool { return lhs._low == rhs._low @@ -278,7 +279,7 @@ extension UInt128: Equatable { // MARK: - Hashable -extension UInt128: Hashable { +extension Bluetooth.UInt128: Hashable { @inlinable public func hash(into hasher: inout Hasher) { @@ -290,7 +291,7 @@ extension UInt128: Hashable { // MARK: - Comparable @available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) -extension UInt128: Comparable { +extension Bluetooth.UInt128: Comparable { @_transparent public static func < (lhs: Self, rhs: Self) -> Bool { @@ -300,7 +301,7 @@ extension UInt128: Comparable { // MARK: - CustomStringConvertible -extension UInt128: CustomStringConvertible { +extension Bluetooth.UInt128: CustomStringConvertible { public var description: String { if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { From 17334619b5a2c51828211f3b803edce944ce5ba4 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 19:45:48 +0000 Subject: [PATCH 04/12] Updated VS Code container --- .devcontainer/Dockerfile | 10 ++-------- .devcontainer/devcontainer.json | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index aae22520e..fdbc31e17 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,5 @@ -# [Choice] Swift version: 5.6-focal, 5.5, 5.4, 5.3, 5.2, 5.1, 4.2 -ARG VARIANT=5.7.1-jammy +# [Choice] Swift version: +ARG VARIANT=6.0.2-jammy FROM swift:${VARIANT} # [Option] Install zsh @@ -25,9 +25,3 @@ COPY library-scripts/node-debian.sh /tmp/library-scripts/ RUN bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}" \ && rm -rf /var/lib/apt/lists/* /tmp/library-scripts -# [Optional] Uncomment this section to install additional OS packages you may want. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3216a0ac4..a7adc9ca2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "dockerfile": "Dockerfile", "args": { // Update the VARIANT arg to pick a Swift version - "VARIANT": "5.7.1-jammy", + "VARIANT": "6.0.2-jammy", // Options "NODE_VERSION": "lts/*", "UPGRADE_PACKAGES": "true" From 927c606a1f3eff47b2d0f2ee1196b351abb1b038 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 14:52:57 -0500 Subject: [PATCH 05/12] Update GitHub CI --- .github/workflows/swift.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 4afb8ada2..15a57b123 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -9,7 +9,7 @@ jobs: - name: Install Swift uses: slashmo/install-swift@v0.3.0 with: - version: 6.0 + version: 6.0.2 - name: Checkout uses: actions/checkout@v2 - name: Swift Version @@ -25,7 +25,7 @@ jobs: name: Linux strategy: matrix: - swift: [5.9, 6.0] + swift: [6.0.2] runs-on: ubuntu-20.04 steps: - name: Install Swift From fcd9d0a8b68e20e29b3ad17b364a92e06cb6819a Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 14:55:56 -0500 Subject: [PATCH 06/12] Updated GitHub CI --- .github/workflows/swift-wasm.yml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/workflows/swift-wasm.yml diff --git a/.github/workflows/swift-wasm.yml b/.github/workflows/swift-wasm.yml deleted file mode 100644 index 8ea3dc86f..000000000 --- a/.github/workflows/swift-wasm.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Swift WASM -on: [push] -jobs: - test: - name: Test - runs-on: ubuntu-latest - container: ghcr.io/swiftwasm/carton:latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Swift Version - run: swift --version - - name: Test - run: carton test From 7e742acb534c59cd7528a88c0253018f13775993 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 14:59:22 -0500 Subject: [PATCH 07/12] Improve `BluetoothUUID` Embedded Swift support --- Sources/Bluetooth/BluetoothUUID.swift | 24 +++++++++++------------- Sources/Bluetooth/UInt128.swift | 6 +++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Sources/Bluetooth/BluetoothUUID.swift b/Sources/Bluetooth/BluetoothUUID.swift index f8bb00f6f..f91bc0f07 100644 --- a/Sources/Bluetooth/BluetoothUUID.swift +++ b/Sources/Bluetooth/BluetoothUUID.swift @@ -19,6 +19,14 @@ public enum BluetoothUUID: Equatable, Hashable, Sendable { case bit128(UInt128) } +public extension BluetoothUUID { + + /// Creates a random 128-bit Bluetooth UUID. + init() { + self.init(uuid: UUID()) + } +} + // MARK: - CustomStringConvertible extension BluetoothUUID: CustomStringConvertible { @@ -93,14 +101,6 @@ extension BluetoothUUID: RawRepresentable { #if canImport(Foundation) -public extension BluetoothUUID { - - /// Creates a random 128 bit Bluetooth UUID. - init() { - self.init(uuid: UUID()) - } -} - public extension BluetoothUUID { init?(data: Data) { @@ -212,7 +212,6 @@ public extension BluetoothUUID { } } -#if canImport(Foundation) internal extension UUID { @inline(__always) @@ -242,7 +241,7 @@ internal extension UUID { public extension UInt16 { /// Attempt to extract Bluetooth 16-bit UUID from standard 128-bit UUID. - init?(bluetooth uuid: Foundation.UUID) { + init?(bluetooth uuid: UUID) { guard let prefixBytes = uuid.bluetoothPrefix(), prefixBytes.0 == 0, @@ -256,7 +255,7 @@ public extension UInt16 { public extension UInt32 { /// Attempt to extract Bluetooth 32-bit UUID from standard 128-bit UUID. - init?(bluetooth uuid: Foundation.UUID) { + init?(bluetooth uuid: UUID) { guard let prefixBytes = uuid.bluetoothPrefix() else { return nil } @@ -275,7 +274,7 @@ public extension BluetoothUUID { } } -public extension Foundation.UUID { +public extension UUID { /// Initialize and convert from a Bluetooth UUID. init(bluetooth uuid: BluetoothUUID) { @@ -308,4 +307,3 @@ public extension CBUUID { } #endif -#endif diff --git a/Sources/Bluetooth/UInt128.swift b/Sources/Bluetooth/UInt128.swift index 8d76c3c37..ed4592e5c 100644 --- a/Sources/Bluetooth/UInt128.swift +++ b/Sources/Bluetooth/UInt128.swift @@ -61,14 +61,15 @@ public extension UInt128 { // MARK: - Byte Swap // TODO: Conflicts with FixedWidthInteger -//extension UInt128: ByteSwap { } +#if canImport(Darwin) +extension UInt128: ByteSwap { } +#endif // MARK: - UUID public extension UInt128 { init(uuid: UUID) { - /// UUID is always big endian let bigEndian = UInt128(bytes: uuid.uuid) self.init(bigEndian: bigEndian) @@ -339,7 +340,6 @@ extension Bluetooth.UInt128 { } } - #else public typealias UInt128 = Swift.UInt128 #endif From 9b28daea4aa7f1076f21cd42ef21906b0475b644 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 15:07:03 -0500 Subject: [PATCH 08/12] #160 Fixed `GATTClient` Swift 6 support --- Sources/BluetoothGATT/GATTClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BluetoothGATT/GATTClient.swift b/Sources/BluetoothGATT/GATTClient.swift index 2cc51f1a2..d3cd6e6e5 100755 --- a/Sources/BluetoothGATT/GATTClient.swift +++ b/Sources/BluetoothGATT/GATTClient.swift @@ -328,10 +328,10 @@ public actor GATTClient { private func registerATTHandlers() async { // value notifications / indications - await connection.register { [unowned self] in + await connection.register { self.notification($0) } - await connection.register { [unowned self] in + await connection.register { await self.indication($0) } } From e12aadf631d01ed2e3a08de2dd2259283e8625c3 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 15:31:50 -0500 Subject: [PATCH 09/12] Add `UInt128.hexadecimal` --- Sources/Bluetooth/UInt128.swift | 41 +++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/Sources/Bluetooth/UInt128.swift b/Sources/Bluetooth/UInt128.swift index ed4592e5c..fc931eb02 100644 --- a/Sources/Bluetooth/UInt128.swift +++ b/Sources/Bluetooth/UInt128.swift @@ -104,6 +104,29 @@ public extension UUID { // MARK: - Backwards compatibility +internal extension UInt128 { + + var hexadecimal: String { + let bytes = self.bigEndian.bytes + return bytes.0.toHexadecimal() + + bytes.1.toHexadecimal() + + bytes.2.toHexadecimal() + + bytes.3.toHexadecimal() + + bytes.4.toHexadecimal() + + bytes.5.toHexadecimal() + + bytes.6.toHexadecimal() + + bytes.7.toHexadecimal() + + bytes.8.toHexadecimal() + + bytes.9.toHexadecimal() + + bytes.10.toHexadecimal() + + bytes.11.toHexadecimal() + + bytes.12.toHexadecimal() + + bytes.13.toHexadecimal() + + bytes.14.toHexadecimal() + + bytes.15.toHexadecimal() + } +} + #if canImport(Darwin) /// A 128-bit signed integer value type. @frozen @@ -308,23 +331,7 @@ extension Bluetooth.UInt128: CustomStringConvertible { if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { return Swift.UInt128(self).description } else { - let bytes = self.bigEndian.bytes - return bytes.0.toHexadecimal() - + bytes.1.toHexadecimal() - + bytes.2.toHexadecimal() - + bytes.3.toHexadecimal() - + bytes.4.toHexadecimal() - + bytes.5.toHexadecimal() - + bytes.6.toHexadecimal() - + bytes.7.toHexadecimal() - + bytes.8.toHexadecimal() - + bytes.9.toHexadecimal() - + bytes.10.toHexadecimal() - + bytes.11.toHexadecimal() - + bytes.12.toHexadecimal() - + bytes.13.toHexadecimal() - + bytes.14.toHexadecimal() - + bytes.15.toHexadecimal() + return hexadecimal } } } From 44c4107cd42921db3aef9532e10727d9d20f550f Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 15:31:57 -0500 Subject: [PATCH 10/12] Update unit tests --- Tests/BluetoothTests/HCITests.swift | 30 ++++++++++++------------- Tests/BluetoothTests/UInt128Tests.swift | 22 ++++++++++-------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/Tests/BluetoothTests/HCITests.swift b/Tests/BluetoothTests/HCITests.swift index 49baac15f..450792fe8 100644 --- a/Tests/BluetoothTests/HCITests.swift +++ b/Tests/BluetoothTests/HCITests.swift @@ -9,7 +9,7 @@ #if canImport(BluetoothHCI) import XCTest import Foundation -import Bluetooth +@testable import Bluetooth @testable import BluetoothHCI final class HCITests: XCTestCase { @@ -144,7 +144,7 @@ final class HCITests: XCTestCase { for (index, name) in names.enumerated().filter({ $1 != skip }) { - XCTAssertEqual(T.init(rawValue: UInt16(index))?.name, name, "\(UInt8(index).toHexadecimal())") + XCTAssertEqual(T.init(rawValue: UInt16(index))?.name, name) } } @@ -1130,7 +1130,11 @@ final class HCITests: XCTestCase { let randomNumber: UInt64 = 0x0000000000000000 let encryptedDiversifier: UInt16 = 0x0000 let longTermKey = UInt128(bigEndian: UInt128(bytes: (0x23, 0x57, 0xEB, 0x0D, 0x0C, 0x24, 0xD8, 0x5A, 0x98, 0x57, 0x64, 0xEC, 0xCB, 0xEC, 0xEC, 0x05))) - XCTAssertEqual(longTermKey.description, "2357EB0D0C24D85A985764ECCBECEC05") + if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { + XCTAssertEqual(longTermKey.description, "46979477079145919533008304147725609989") + } else { + XCTAssertEqual(longTermKey.description, "2357EB0D0C24D85A985764ECCBECEC05") + } do { @@ -1220,12 +1224,10 @@ final class HCITests: XCTestCase { let hostController = TestHostController() let key = UInt128(bigEndian: UInt128(bytes: (0x4C, 0x68, 0x38, 0x41, 0x39, 0xF5, 0x74, 0xD8, 0x36, 0xBC, 0xF3, 0x4E, 0x9D, 0xFB, 0x01, 0xBF))) - - XCTAssertEqual(key.description, "4C68384139F574D836BCF34E9DFB01BF") + XCTAssertEqual(key.hexadecimal, "4C68384139F574D836BCF34E9DFB01BF") let plainTextData = UInt128(bigEndian: UInt128(bytes: (0x02, 0x13, 0x24, 0x35, 0x46, 0x57, 0x68, 0x79, 0xac, 0xbd, 0xce, 0xdf, 0xe0, 0xf1, 0x02, 0x13))) - - XCTAssertEqual(plainTextData.description, "0213243546576879ACBDCEDFE0F10213") + XCTAssertEqual(plainTextData.hexadecimal, "0213243546576879ACBDCEDFE0F10213") /** HCI_LE_Encrypt (length 0x20) – command @@ -1262,14 +1264,14 @@ final class HCITests: XCTestCase { hostController.queue.append(.event(eventHeader.data + [0x02, 0x17, 0x20, 0x00, 0x66, 0xc6, 0xc2, 0x27, 0x8e, 0x3b, 0x8e, 0x05, 0x3e, 0x7e, 0xa3, 0x26, 0x52, 0x1b, 0xad, 0x99])) - XCTAssertEqual(HCILEEncryptReturn(data: Data([/* 0x02, 0x17, 0x20, 0x00, */ 0x66, 0xc6, 0xc2, 0x27, 0x8e, 0x3b, 0x8e, 0x05, 0x3e, 0x7e, 0xa3, 0x26, 0x52, 0x1b, 0xad, 0x99]))?.encryptedData.description, "99AD1B5226A37E3E058E3B8E27C2C666") + XCTAssertEqual(HCILEEncryptReturn(data: Data([/* 0x02, 0x17, 0x20, 0x00, */ 0x66, 0xc6, 0xc2, 0x27, 0x8e, 0x3b, 0x8e, 0x05, 0x3e, 0x7e, 0xa3, 0x26, 0x52, 0x1b, 0xad, 0x99]))?.encryptedData.hexadecimal, "99AD1B5226A37E3E058E3B8E27C2C666") var encryptedData: UInt128 = .zero (encryptedData = try await hostController.lowEnergyEncrypt(key: key, data: plainTextData)) XCTAssert(hostController.queue.isEmpty) XCTAssertNotEqual(encryptedData, .zero) - XCTAssertEqual(encryptedData.description, "99AD1B5226A37E3E058E3B8E27C2C666") + XCTAssertEqual(encryptedData.hexadecimal, "99AD1B5226A37E3E058E3B8E27C2C666") } func testSetLERandomAddress() async throws { @@ -1506,10 +1508,10 @@ final class HCITests: XCTestCase { let hostController = TestHostController() - guard let maxDuration = HCIPeriodicInquiryMode.MaxDuration(rawValue: UInt16(bytes: (0x09, 0x00))) + guard let maxDuration = HCIPeriodicInquiryMode.MaxDuration(rawValue: 0x09) else { XCTFail("Unable to init variable"); return } - guard let minDuration = HCIPeriodicInquiryMode.MinDuration(rawValue: UInt16(bytes: (0x05, 0x00))) + guard let minDuration = HCIPeriodicInquiryMode.MinDuration(rawValue: 0x05) else { XCTFail("Unable to init variable"); return } guard let lap = HCIPeriodicInquiryMode.LAP(rawValue: UInt24(bytes: (0x00, 0x8b, 0x9e))) @@ -1936,8 +1938,7 @@ final class HCITests: XCTestCase { */ hostController.queue.append(.event([0x0b, 0x0b, 0x00, 0x0d, 0x00, 0xbd, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00])) - let data = Data([0xbd, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00]) - let value = UInt64(littleEndian: UInt64(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]))) + let value: UInt64 = 0x00000008380402bd let features = BitMaskOptionSet(rawValue: value) var lmpFeatures: BitMaskOptionSet? @@ -2475,8 +2476,7 @@ final class HCITests: XCTestCase { hostController.queue.append(.event([0x0e, 0x0c, 0x01, 0x03, 0x10, 0x00, 0xbf, 0xfe, 0xcf, 0xfe, 0xdb, 0xff, 0x7b, 0x87])) - let data = Data([0xbf, 0xfe, 0xcf, 0xfe, 0xdb, 0xff, 0x7b, 0x87]) - let value = UInt64(littleEndian: UInt64(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]))) + let value: UInt64 = 0x877bffdbfecffebf let features = BitMaskOptionSet(rawValue: value) var lmpFeatures: BitMaskOptionSet? diff --git a/Tests/BluetoothTests/UInt128Tests.swift b/Tests/BluetoothTests/UInt128Tests.swift index 1d8167aa7..a3d488522 100644 --- a/Tests/BluetoothTests/UInt128Tests.swift +++ b/Tests/BluetoothTests/UInt128Tests.swift @@ -21,10 +21,15 @@ final class UInt128Tests: XCTestCase { func testUUID() { let uuid = UUID(uuidString: "60F14FE2-F972-11E5-B84F-23E070D5A8C7")! - let value = Bluetooth.UInt128(uuid: uuid) - - XCTAssertEqual(UUID(value), uuid) - XCTAssertEqual(value.description, "60F14FE2F97211E5B84F23E070D5A8C7") + let number = Bluetooth.UInt128(uuid: uuid) + + XCTAssertEqual(UUID(number), uuid) + XCTAssertEqual(number.uuidString, uuid.uuidString) + if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { + XCTAssertEqual(number.description, "128858851431381903469711580150894012615") + } else { + XCTAssertEqual(number.description, "60F14FE2F97211E5B84F23E070D5A8C7") + } } func testHashable() { @@ -40,11 +45,10 @@ final class UInt128Tests: XCTestCase { } let values: [(UInt128, String)] = [ - (UInt128.zero, "00000000000000000000000000000000"), - (0x00000000000000000000000000000000, "00000000000000000000000000000000"), - (0x00000000000000000000000000000001, "00000000000000000000000000000001"), - (100000000000000000000000000000, "10000000000000000000000000000000"), - (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + (0x00000000000000000000000000000000, "0"), + (0x00000000000000000000000000000001, "1"), + (100000000000000000000000000000, "100000000000000000000000000000"), + (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, "340282366920938463463374607431768211455") ] values.forEach { XCTAssertEqual($0.description, $1) } From 27f519901885c621573d670ab29d9f469803fe9e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 16:25:23 -0500 Subject: [PATCH 11/12] #158 Add `FixedWidthInteger` conformance to `UInt128` --- Sources/Bluetooth/UInt128.swift | 271 ++++++++++++++++++++++++++++++-- 1 file changed, 259 insertions(+), 12 deletions(-) diff --git a/Sources/Bluetooth/UInt128.swift b/Sources/Bluetooth/UInt128.swift index fc931eb02..c0f0667d7 100644 --- a/Sources/Bluetooth/UInt128.swift +++ b/Sources/Bluetooth/UInt128.swift @@ -58,13 +58,6 @@ public extension UInt128 { } #endif -// MARK: - Byte Swap - -// TODO: Conflicts with FixedWidthInteger -#if canImport(Darwin) -extension UInt128: ByteSwap { } -#endif - // MARK: - UUID public extension UInt128 { @@ -319,7 +312,7 @@ extension Bluetooth.UInt128: Comparable { @_transparent public static func < (lhs: Self, rhs: Self) -> Bool { - lhs._value < rhs._value + Swift.UInt128(lhs) < Swift.UInt128(rhs) } } @@ -336,15 +329,269 @@ extension Bluetooth.UInt128: CustomStringConvertible { } } -// TODO: Implement Integer protocols -extension Bluetooth.UInt128 { +// MARK: - Numeric + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension UInt128: Numeric { + public typealias Magnitude = Self + + @_transparent + public var magnitude: Self { + self + } +} + +// MARK: - AdditiveArithmetic + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension UInt128: AdditiveArithmetic { + @_transparent + public static func + (a: Self, b: Self) -> Self { + Bluetooth.UInt128(Swift.UInt128(a) + Swift.UInt128(b)) + } + + @_transparent + public static func - (a: Self, b: Self) -> Self { + Bluetooth.UInt128(Swift.UInt128(a) - Swift.UInt128(b)) + } +} + +// MARK: - Multiplication and division + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension UInt128 { + @_transparent + public static func * (a: Self, b: Self) -> Self { + Bluetooth.UInt128(Swift.UInt128(a) * Swift.UInt128(b)) + } + + @_transparent + public static func *= (a: inout Self, b: Self) { + a = a * b + } + + @_transparent + public static func /(a: Self, b: Self) -> Self { + a.dividedReportingOverflow(by: b).partialValue + } + + @_transparent + public static func /=(a: inout Self, b: Self) { + a = a / b + } + + @_transparent + public static func %(a: Self, b: Self) -> Self { + a.remainderReportingOverflow(dividingBy: b).partialValue + } + + @_transparent + public static func %=(a: inout Self, b: Self) { + a = a % b + } +} + +// MARK: - Overflow-reporting arithmetic + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension UInt128 { + @_transparent + public func addingReportingOverflow( + _ other: Self + ) -> (partialValue: Self, overflow: Bool) { + let (partialValue, overflow) = Swift.UInt128(self).addingReportingOverflow(.init(other)) + return (Self(partialValue), overflow) + } + + @_transparent + public func subtractingReportingOverflow( + _ other: Self + ) -> (partialValue: Self, overflow: Bool) { + let (partialValue, overflow) = Swift.UInt128(self).subtractingReportingOverflow(.init(other)) + return (Self(partialValue), overflow) + } + + @_transparent + public func multipliedReportingOverflow( + by other: Self + ) -> (partialValue: Self, overflow: Bool) { + let (partialValue, overflow) = Swift.UInt128(self).multipliedReportingOverflow(by: .init(other)) + return (Self(partialValue), overflow) + } + + @_transparent + public func dividedReportingOverflow( + by other: Self + ) -> (partialValue: Self, overflow: Bool) { + precondition(other != .zero, "Division by zero") + let (partialValue, overflow) = Swift.UInt128(self).dividedReportingOverflow(by: .init(other)) + return (Self(partialValue), overflow) + } + + @_transparent + public func remainderReportingOverflow( + dividingBy other: Self + ) -> (partialValue: Self, overflow: Bool) { + precondition(other != .zero, "Division by zero in remainder operation") + let (partialValue, overflow) = Swift.UInt128(self).remainderReportingOverflow(dividingBy: .init(other)) + return (Self(partialValue), overflow) + } +} + +// MARK: - BinaryInteger conformance + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension Bluetooth.UInt128: BinaryInteger { + + public typealias Words = Swift.UInt128.Words + + @_transparent + public var words: Words { + Words(_value: .init(self)) + } + + @_transparent + public static func &=(a: inout Self, b: Self) { + a = Bluetooth.UInt128(Swift.UInt128(a) & Swift.UInt128(b)) + } + + @_transparent + public static func |=(a: inout Self, b: Self) { + a = Bluetooth.UInt128(Swift.UInt128(a) | Swift.UInt128(b)) + } + + @_transparent + public static func ^=(a: inout Self, b: Self) { + a = Bluetooth.UInt128(Swift.UInt128(a) ^ Swift.UInt128(b)) + } + + @_transparent + public static func &>>=(a: inout Self, b: Self) { + a = Bluetooth.UInt128(Swift.UInt128(a) &>> Swift.UInt128(b)) + } + + @_transparent + public static func &<<=(a: inout Self, b: Self) { + a = Bluetooth.UInt128(Swift.UInt128(a) &<< Swift.UInt128(b)) + } + + @_transparent + public var trailingZeroBitCount: Int { + _low == 0 ? 64 + _high.trailingZeroBitCount : _low.trailingZeroBitCount + } + + @_transparent + public var _lowWord: UInt { + Swift.UInt128(self)._lowWord + } +} + +// MARK: - FixedWidthInteger conformance + +@available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) +extension UInt128: FixedWidthInteger, UnsignedInteger { } + +public extension UInt128 { @_transparent - public static var bitWidth: Int { 128 } + static var bitWidth: Int { 128 } - public var byteSwapped: Self { + @_transparent + var nonzeroBitCount: Int { + _high.nonzeroBitCount &+ _low.nonzeroBitCount + } + + @_transparent + var leadingZeroBitCount: Int { + _high == 0 ? 64 + _low.leadingZeroBitCount : _high.leadingZeroBitCount + } + + @_transparent + var byteSwapped: Self { return Self(_low: _high.byteSwapped, _high: _low.byteSwapped) } + + /// Creates an instance from its little-endian representation, changing the + /// byte order if necessary. + /// + /// - Parameter value: A value to use as the little-endian representation of + /// the new instance. + init(littleEndian value: Self) { + #if _endian(little) + self = value + #else + self = value.byteSwapped + #endif + } + + /// Creates an instance from its big-endian representation, changing the byte + /// order if necessary. + /// + /// - Parameter value: A value to use as the big-endian representation of the + /// new instance. + init(bigEndian value: Self) { + #if _endian(big) + self = value + #else + self = value.byteSwapped + #endif + } + + /// The little-endian representation of this value. + /// + /// If necessary, the byte order of this value is reversed from the typical + /// byte order of this address. On a little-endian platform, for any + /// address `x`, `x == x.littleEndian`. + var littleEndian: Self { + #if _endian(little) + return self + #else + return byteSwapped + #endif + } + + /// The big-endian representation of this value. + /// + /// If necessary, the byte order of this value is reversed from the typical + /// byte order of this address. On a big-endian platform, for any + /// address `x`, `x == x.bigEndian`. + var bigEndian: Self { + #if _endian(big) + return self + #else + return byteSwapped + #endif + } +} + +// MARK: - Integer comparison type inference + +extension UInt128 { + // IMPORTANT: The following four apparently unnecessary overloads of + // comparison operations are necessary for literal comparands to be + // inferred as the desired type. + @_transparent @_alwaysEmitIntoClient + public static func != (lhs: Self, rhs: Self) -> Bool { + return !(lhs == rhs) + } + + @available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) + @_transparent @_alwaysEmitIntoClient + public static func <= (lhs: Self, rhs: Self) -> Bool { + return !(rhs < lhs) + } + + @available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) + @_transparent @_alwaysEmitIntoClient + public static func >= (lhs: Self, rhs: Self) -> Bool { + return !(lhs < rhs) + } + + @available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) + @_transparent @_alwaysEmitIntoClient + public static func > (lhs: Self, rhs: Self) -> Bool { + return rhs < lhs + } } #else From 444ab3f9fc3935236e12bc029e9d66c967df791d Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 4 Nov 2024 17:11:34 -0500 Subject: [PATCH 12/12] Improve `BluetoothUUID` Embedded Swift support --- Sources/Bluetooth/BluetoothUUID.swift | 29 +++--- .../Bluetooth/Extensions/Hexadecimal.swift | 99 ++++++++++++++----- Sources/Bluetooth/Extensions/UUID.swift | 4 +- 3 files changed, 87 insertions(+), 45 deletions(-) diff --git a/Sources/Bluetooth/BluetoothUUID.swift b/Sources/Bluetooth/BluetoothUUID.swift index f91bc0f07..1c6b607f0 100644 --- a/Sources/Bluetooth/BluetoothUUID.swift +++ b/Sources/Bluetooth/BluetoothUUID.swift @@ -39,25 +39,13 @@ extension BluetoothUUID: CustomStringConvertible { return rawValue } #else - return _description + return rawValue #endif } - - internal var _description: String { - switch self { - case let .bit16(value): - return value.toHexadecimal() - case let .bit32(value): - return value.toHexadecimal() - case let .bit128(value): - return value.uuidString - } - } } // MARK: - RawRepresentable -#if !hasFeature(Embedded) extension BluetoothUUID: RawRepresentable { /// Initialize from a UUID string (in big endian representation). @@ -69,19 +57,18 @@ extension BluetoothUUID: RawRepresentable { case 4: - guard let value = UInt16(rawValue, radix: 16) + guard let value = UInt16(hexadecimal: rawValue) else { return nil } self = .bit16(value) case 8: - guard let value = UInt32(rawValue, radix: 16) + guard let value = UInt32(hexadecimal: rawValue) else { return nil } self = .bit32(value) case 36: - // UUID string is always big endian guard let uuid = UInt128(uuidString: rawValue) else { return nil } self = .bit128(uuid) @@ -92,10 +79,16 @@ extension BluetoothUUID: RawRepresentable { } public var rawValue: String { - _description + switch self { + case let .bit16(value): + return value.toHexadecimal() + case let .bit32(value): + return value.toHexadecimal() + case let .bit128(value): + return value.uuidString + } } } -#endif // MARK: - Data diff --git a/Sources/Bluetooth/Extensions/Hexadecimal.swift b/Sources/Bluetooth/Extensions/Hexadecimal.swift index e441a838f..e28bddd6d 100644 --- a/Sources/Bluetooth/Extensions/Hexadecimal.swift +++ b/Sources/Bluetooth/Extensions/Hexadecimal.swift @@ -30,37 +30,86 @@ internal extension Collection where Element: FixedWidthInteger { } } -#if !hasFeature(Embedded) -internal extension StringProtocol { +internal extension UInt { - var hexadecimal: UnfoldSequence { - sequence(state: startIndex) { startIndex in - guard startIndex < self.endIndex else { return nil } - let endIndex = self.index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex - defer { startIndex = endIndex } - return UInt8(self[startIndex..= radix { + return nil + } + result = result * radix + val + } else { + return nil + } + } + self = result + } +} + +internal extension UInt16 { + + init?(hexadecimal string: String) { + guard string.count == MemoryLayout.size * 2 else { + return nil + } + #if hasFeature(Embedded) || DEBUG + guard let value = UInt(parse: string, radix: 16) else { + return nil + } + self.init(value) + #else + self.init(string, radix: 16) + #endif + } +} + +internal extension UInt32 { + + init?(hexadecimal string: String) { + guard string.count == MemoryLayout.size * 2 else { + return nil + } + #if hasFeature(Embedded) || DEBUG + guard let value = UInt(parse: string, radix: 16) else { + return nil + } + self.init(value) + #else + self.init(string, radix: 16) + #endif + } +} + +internal extension String.UTF16View.Element { + + // Convert 0 ... 9, a ... f, A ...F to their decimal value, + // return nil for all other input characters + func decodeHexNibble() -> UInt8? { + switch self { + case 0x30 ... 0x39: + return UInt8(self - 0x30) + case 0x41 ... 0x46: + return UInt8(self - 0x41 + 10) + case 0x61 ... 0x66: + return UInt8(self - 0x61 + 10) + default: + return nil } } } -#endif internal extension [UInt8] { init?(hexadecimal string: S) { - // Convert 0 ... 9, a ... f, A ...F to their decimal value, - // return nil for all other input characters - func decodeNibble(_ u: UInt16) -> UInt8? { - switch(u) { - case 0x30 ... 0x39: - return UInt8(u - 0x30) - case 0x41 ... 0x46: - return UInt8(u - 0x41 + 10) - case 0x61 ... 0x66: - return UInt8(u - 0x61 + 10) - default: - return nil - } - } let str = String(string) let utf16: String.UTF16View @@ -74,9 +123,9 @@ internal extension [UInt8] { var i = utf16.startIndex while i != utf16.endIndex { - guard let hi = decodeNibble(utf16[i]), + guard let hi = utf16[i].decodeHexNibble(), let nxt = utf16.index(i, offsetBy:1, limitedBy: utf16.endIndex), - let lo = decodeNibble(utf16[nxt]) + let lo = utf16[nxt].decodeHexNibble() else { return nil } diff --git a/Sources/Bluetooth/Extensions/UUID.swift b/Sources/Bluetooth/Extensions/UUID.swift index a9679fbfa..11a2e6149 100644 --- a/Sources/Bluetooth/Extensions/UUID.swift +++ b/Sources/Bluetooth/Extensions/UUID.swift @@ -288,10 +288,10 @@ internal extension UInt128 { /// Parse a UUID string. init?(uuidString string: String) { - guard let value = Self.bigEndian(uuidString: string) else { + guard let bigEndian = Self.bigEndian(uuidString: string) else { return nil } - self.init(bigEndian: value) + self.init(bigEndian: bigEndian) } /// Generate UUID string, e.g. `0F4DD6A4-0F71-48EF-98A5-996301B868F9`.