-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
894a05e
commit 9efbac0
Showing
1 changed file
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
// | ||
// Decoder.swift | ||
// Bluetooth | ||
// | ||
// Created by Alsey Coleman Miller on 11/4/24. | ||
// | ||
|
||
#if canImport(Foundation) | ||
import Foundation | ||
#endif | ||
import Bluetooth | ||
|
||
/// GAP Data Decoder | ||
public struct GAPDataDecoder <Data: DataContainer> { | ||
|
||
// MARK: - Properties | ||
|
||
#if !hasFeature(Embedded) | ||
/// Ignore unknown types. | ||
public var ignoreUnknownType: Bool = false | ||
|
||
public var types = [GAPData.Type]() { | ||
didSet { | ||
dataTypes = [GAPDataType: GAPData.Type](minimumCapacity: types.count) | ||
types.forEach { dataTypes[$0.dataType] = $0 } | ||
} | ||
} | ||
|
||
internal private(set) var dataTypes: [GAPDataType: GAPData.Type] = [:] | ||
#endif | ||
|
||
// MARK: - Initialization | ||
|
||
/// Initialize with default data types. | ||
public init() { | ||
|
||
#if !hasFeature(Embedded) | ||
/// initialize with default precomputed values | ||
self.types = GAPDataDecoderCache.defaultTypes | ||
self.dataTypes = GAPDataDecoderCache.defaultDataTypes | ||
#endif | ||
} | ||
|
||
// MARK: - Methods | ||
|
||
#if !hasFeature(Embedded) | ||
public func decode(from data: Data) throws(GAPDataDecoderError) -> [GAPData] { | ||
return try decode(data: data, reserveCapacity: 3) | ||
} | ||
|
||
@usableFromInline | ||
internal func decode(data: Data, reserveCapacity capacity: Int) throws(GAPDataDecoderError) -> [GAPData] { | ||
|
||
guard data.isEmpty == false | ||
else { return [] } | ||
|
||
var elements = [GAPData]() | ||
elements.reserveCapacity(capacity) | ||
|
||
var offset = 0 | ||
try Self.decode(data: data, offset: &offset) { (type, slice, offset) throws(GAPDataDecoderError) in | ||
if let gapType = dataTypes[type] { | ||
guard let decodable = gapType.init(data: slice) | ||
else { throw .cannotDecode(type, offset: offset) } | ||
elements.append(decodable) | ||
return true | ||
} else if ignoreUnknownType { | ||
return true | ||
} else { | ||
throw .unknownType(type, offset: offset) | ||
} | ||
} | ||
|
||
return elements | ||
} | ||
|
||
@usableFromInline | ||
internal static func decode(data: Data, reserveCapacity capacity: Int = 3) throws(GAPDataDecoderError) -> [(GAPDataType, Data)] { | ||
|
||
guard data.isEmpty == false | ||
else { return [] } | ||
|
||
var elements = [(GAPDataType, Data)]() | ||
elements.reserveCapacity(capacity) | ||
|
||
var offset = 0 | ||
try decode(data: data, offset: &offset) { (type, data, offset) in | ||
elements.append((type, data)) | ||
return true | ||
} | ||
|
||
return elements | ||
} | ||
#endif | ||
|
||
@usableFromInline | ||
internal static func decode(data: Data, offset: inout Int, _ block: (GAPDataType, Data, Int) throws(GAPDataDecoderError) -> (Bool)) throws(GAPDataDecoderError) { | ||
|
||
while offset < data.count { | ||
|
||
// get length | ||
let length = Int(data[offset]) // 0 | ||
offset += 1 | ||
guard offset < data.count else { | ||
if length == 0 { | ||
break // EOF | ||
} else { | ||
throw .insufficientBytes(expected: offset + 1, actual: data.count) | ||
} | ||
} | ||
|
||
// get type | ||
let type = GAPDataType(rawValue: data[offset]) // 1 | ||
|
||
// ignore zeroed bytes | ||
guard (type.rawValue == 0 && length == 0) == false | ||
else { break } | ||
|
||
// get value | ||
let slice: Data | ||
|
||
if length > 0 { | ||
let dataRange = offset + 1 ..< offset + length // 2 ..< 2 + length | ||
offset = dataRange.upperBound | ||
guard offset <= data.count | ||
else { throw .insufficientBytes(expected: offset + 1, actual: data.count) } | ||
|
||
slice = data.subdata(in: dataRange) | ||
} else { | ||
slice = Data() | ||
} | ||
|
||
// process and continue | ||
guard try block(type, slice, offset) else { return } | ||
} | ||
} | ||
} | ||
|
||
internal extension GAPDataDecoder { | ||
|
||
static func decodeFirst<T: GAPData>(_ type: T.Type, _ offset: inout Int, _ data: Data) throws(GAPDataDecoderError) -> T? { | ||
|
||
var offset = 0 | ||
var value: T? | ||
try decode(data: data, offset: &offset) { (dataType, slice, offset) throws(GAPDataDecoderError) in | ||
guard dataType == T.dataType else { return true } | ||
value = T.init(data: slice) | ||
guard value != nil else { | ||
throw .cannotDecode(dataType, offset: offset) | ||
} | ||
return false | ||
} | ||
return value | ||
} | ||
|
||
static func decodeFirst<T: GAPData>(_ type: T.Type, from data: Data) throws(GAPDataDecoderError) -> T? { | ||
|
||
var offset = 0 | ||
return try decodeFirst(type, &offset, data) | ||
} | ||
} | ||
|
||
public extension GAPDataDecoder { | ||
|
||
static func decode<T: GAPData>(_ type: T.Type, from data: Data) throws(GAPDataDecoderError) -> T { | ||
|
||
var offset = 0 | ||
guard let value = try decodeFirst(type, &offset, data) else { | ||
throw .notFound(T.dataType) | ||
} | ||
return value | ||
} | ||
|
||
static func decode<T0: GAPData, T1: GAPData>(_ type0: T0.Type, _ type1: T1.Type, from data: Data) throws(GAPDataDecoderError) -> (T0, T1) { | ||
|
||
var offset = 0 | ||
guard let value0 = try decodeFirst(type0, &offset, data) else { | ||
throw .notFound(T0.dataType) | ||
} | ||
guard let value1 = try decodeFirst(type1, &offset, data) else { | ||
throw .notFound(T1.dataType) | ||
} | ||
return (value0, value1) | ||
} | ||
|
||
static func decode<T0: GAPData, T1: GAPData, T2: GAPData>(_ type0: T0.Type, _ type1: T1.Type, _ type2: T2.Type, from data: Data) throws(GAPDataDecoderError) -> (T0, T1, T2) { | ||
|
||
var offset = 0 | ||
guard let value0 = try decodeFirst(type0, &offset, data) else { | ||
throw .notFound(T0.dataType) | ||
} | ||
guard let value1 = try decodeFirst(type1, &offset, data) else { | ||
throw .notFound(T1.dataType) | ||
} | ||
guard let value2 = try decodeFirst(type2, &offset, data) else { | ||
throw .notFound(T2.dataType) | ||
} | ||
return (value0, value1, value2) | ||
} | ||
|
||
static func decode<T0: GAPData, T1: GAPData, T2: GAPData, T3: GAPData>(_ type0: T0.Type, _ type1: T1.Type, _ type2: T2.Type, _ type3: T3.Type, from data: Data) throws(GAPDataDecoderError) -> (T0, T1, T2, T3) { | ||
|
||
var offset = 0 | ||
guard let value0 = try decodeFirst(type0, &offset, data) else { | ||
throw .notFound(T0.dataType) | ||
} | ||
guard let value1 = try decodeFirst(type1, &offset, data) else { | ||
throw .notFound(T1.dataType) | ||
} | ||
guard let value2 = try decodeFirst(type2, &offset, data) else { | ||
throw .notFound(T2.dataType) | ||
} | ||
guard let value3 = try decodeFirst(type3, &offset, data) else { | ||
throw .notFound(T3.dataType) | ||
} | ||
return (value0, value1, value2, value3) | ||
} | ||
} | ||
|
||
// MARK: - Supporting Types | ||
|
||
/// GAP Data Decoder Error | ||
public enum GAPDataDecoderError: Swift.Error, Sendable { | ||
|
||
case insufficientBytes(expected: Int, actual: Int) | ||
case cannotDecode(GAPDataType, offset: Int) | ||
case unknownType(GAPDataType, offset: Int) | ||
case notFound(GAPDataType) | ||
} | ||
|
||
// MARK: - Constants | ||
|
||
#if !hasFeature(Embedded) | ||
internal enum GAPDataDecoderCache { | ||
|
||
static let defaultDataTypes: [GAPDataType: GAPData.Type] = { | ||
var types = [GAPDataType: GAPData.Type](minimumCapacity: defaultTypes.count) | ||
defaultTypes.forEach { types[$0.dataType] = $0 } | ||
return types | ||
}() | ||
|
||
static let defaultTypes: [GAPData.Type] = [ | ||
GAP3DInformation.self, | ||
GAPAdvertisingInterval.self, | ||
GAPAppearanceData.self, | ||
GAPChannelMapUpdateIndication.self, | ||
GAPClassOfDevice.self, | ||
GAPCompleteListOf16BitServiceClassUUIDs.self, | ||
GAPCompleteListOf32BitServiceClassUUIDs.self, | ||
GAPCompleteListOf128BitServiceClassUUIDs.self, | ||
GAPCompleteLocalName.self, | ||
GAPFlags.self, | ||
GAPIncompleteListOf16BitServiceClassUUIDs.self, | ||
GAPIncompleteListOf32BitServiceClassUUIDs.self, | ||
GAPIncompleteListOf128BitServiceClassUUIDs.self, | ||
GAPIndoorPositioning.self, | ||
GAPLEDeviceAddress.self, | ||
GAPLERole.self, | ||
GAPLESecureConnectionsConfirmation.self, | ||
GAPLESecureConnectionsRandom.self, | ||
//GAPLESupportedFeatures.self, | ||
GAPListOf16BitServiceSolicitationUUIDs.self, | ||
GAPListOf32BitServiceSolicitationUUIDs.self, | ||
GAPListOf128BitServiceSolicitationUUIDs.self, | ||
GAPManufacturerSpecificData.self, | ||
GAPMeshBeacon.self, | ||
GAPMeshMessage.self, | ||
GAPPBADV.self, | ||
GAPPublicTargetAddress.self, | ||
GAPRandomTargetAddress.self, | ||
GAPSecurityManagerOOBFlags.self, | ||
GAPSecurityManagerTKValue.self, | ||
GAPServiceData16BitUUID.self, | ||
GAPServiceData32BitUUID.self, | ||
GAPServiceData128BitUUID.self, | ||
GAPShortLocalName.self, | ||
GAPSimplePairingHashC.self, | ||
GAPSimplePairingRandomizerR.self, | ||
GAPSlaveConnectionIntervalRange.self, | ||
GAPTransportDiscoveryData.self, | ||
GAPTxPowerLevel.self, | ||
GAPURI.self | ||
] | ||
} | ||
#endif | ||
|