Skip to content

Instantly share code, notes, and snippets.

@ytyubox
Created August 10, 2021 08:59
Show Gist options
  • Save ytyubox/debdf77be242f920be0704dc6c166c23 to your computer and use it in GitHub Desktop.
Save ytyubox/debdf77be242f920be0704dc6c166c23 to your computer and use it in GitHub Desktop.
import XCTest
@testable import DataCutter
@propertyWrapper struct Byte<Value>: ByteDecode {
func decodeValue(from data: Data) throws {
let value = data
.subdata(in: inner.range)
.withUnsafeBytes { $0.load(as: Value.self) }
inner.value = .already(value)
}
typealias Index = Data.Index
typealias Range = Swift.Range<Index>
private let inner: Inner
public var wrappedValue: Value {
get {
switch inner.value {
case .yet:
fatalError("did not decode")
case let .already(v):
return v
case .none:
fatalError("😅 @Keyed current not support Optional property, please consider using @OptionalKeyed")
}
}
nonmutating set {
inner.value = .already(newValue)
}
}
init(_ range: Range) {
self.inner = Inner(range)
}
init(_ index: Index) {
self.inner = Inner(index..<index+1)
}
public init(range: Range, wrappedValue: Value) {
self.inner = Inner(range)
inner.value = .already(wrappedValue)
}
private final class Inner {
// MARK: Lifecycle
public init(_ range: Range) {
self.range = range
}
// MARK: Fileprivate
let range: Range
fileprivate var value: Lazy<Value> = .none
}
}
protocol ByteDecode {
func decodeValue(from data: Data) throws
}
public protocol ByteDecodable {
init(from data: Data) throws
init()
}
extension ByteDecodable {
init(from data: Data) throws {
self.init()
for child in Mirror(reflecting: self).children {
guard let decodableKey = child.value as? ByteDecode else { continue }
try decodableKey.decodeValue(from: data)
}
}
}
enum Lazy<T> {
case yet
case already(T)
case none
}
struct XPLBeacon {
let majorVersion, minorVersion: UInt8 // 1 + 1 = 2 Bytes
let applicationHostId, versionNumber: UInt32 // 4 + 4 = 8 Bytes
init(data: Data) {
self.majorVersion = data[0]
self.minorVersion = data[1]
self.applicationHostId = data
.subdata(in: 2..<6)
.withUnsafeBytes { $0.load(as: UInt32.self) }
self.versionNumber = data
.subdata(in: 6..<10)
.withUnsafeBytes { $0.load(as: UInt32.self) }
}
}
final class DataCutterTests: XCTestCase {
func testCut() {
let data = Data([0x01,0x02, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00,0x00])
let rawData = data.hexEncodedString(options: [.upperCase])
XCTAssertEqual(rawData, "01020A0000000B000000")
let beacon = XPLBeacon(data: data)
XCTAssertEqual(beacon.majorVersion ,1)
XCTAssertEqual(beacon.minorVersion ,2)
XCTAssertEqual(beacon.applicationHostId ,10)
XCTAssertEqual(beacon.versionNumber ,11)
}
final class Beacon: ByteDecodable {
@Byte(0)
var majorVersion: UInt8
@Byte(1)
var minorVersion: UInt8
@Byte(2..<6)
var applicationHostId: UInt32
@Byte(6..<10)
var versionNumber: UInt32
}
func testDecode() throws {
let data = Data([0x01,0x02, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00,0x00])
let rawData = data.hexEncodedString(options: [.upperCase])
XCTAssertEqual(rawData, "01020A0000000B000000")
let beacon = try Beacon(from: data)
XCTAssertEqual(beacon.majorVersion ,1)
XCTAssertEqual(beacon.minorVersion ,2)
XCTAssertEqual(beacon.applicationHostId ,10)
XCTAssertEqual(beacon.versionNumber ,11)
}
}
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return self.map { String(format: format, $0) }.joined()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment