Skip to content

Latest commit

 

History

History
121 lines (87 loc) · 3.88 KB

File metadata and controls

121 lines (87 loc) · 3.88 KB

TLVCoding

Swift

TLV8 (Type-Length-Value) Coder library, compatible with Embedded Swift.

Types are encoded and decoded explicitly via TLVCodable — either with conformances generated by the @TLVCodable macro, or written by hand. The library does not depend on Codable runtime machinery or Foundation, so it can be used on bare-metal targets.

Usage

Macro-generated conformance

import TLVCoding

@TLVCodable
struct ProvisioningState: Equatable {

    var state: State

    var result: Result

    enum CodingKeys: UInt8, TLVCodingKey {
        case state  = 0x01
        case result = 0x02
    }
}

let value = ProvisioningState(state: .provisioning, result: .success)
let data = value.tlvData // Data([0x01, 0x01, 0x01, 0x02, 0x01, 0x01])
let decoded = ProvisioningState(tlvData: data)

If no CodingKeys enum is declared, one is generated with sequential type codes in property declaration order.

Hand-written conformance

The same conformance can be written manually (e.g. for Embedded Swift or custom binary layouts):

extension ProvisioningState: TLVCodable {

    init?(tlvData: Data) {
        guard let container = TLVContainer(data: tlvData),
              let state = container.decode(State.self, forKey: CodingKeys.state),
              let result = container.decode(Result.self, forKey: CodingKeys.result)
              else { return nil }
        self.state = state
        self.result = result
    }

    var tlvData: Data {
        var container = TLVContainer()
        container.encode(state, forKey: CodingKeys.state)
        container.encode(result, forKey: CodingKeys.result)
        return container.data
    }
}

Types with a custom binary layout can skip TLVContainer entirely and produce their payload directly:

extension Version: TLVCodable {

    init?(tlvData: Data) {
        guard tlvData.count == 3 else { return nil }
        self.major = tlvData[0]
        self.minor = tlvData[1]
        self.patch = tlvData[2]
    }

    var tlvData: Data {
        Data([major, minor, patch])
    }
}

Swift Binary Parsing

On Swift 6.2+ toolchains the library integrates swift-binary-parsing: TLVContainer.init(data:) is backed by its safe, bounds-checked parser, and TLVTypeCode, TLVItem and TLVContainer conform to ExpressibleByParsing for throwing, span-based parsing:

import BinaryParsing
import TLVCoding

// parse a whole container
let container = try TLVContainer(parsing: data)

// or consume items from a ParserSpan
try data.withParserSpan { input in
    let item = try TLVItem(parsing: &input)
    ...
}

The dependency raises the Apple platform deployment targets (macOS 13, iOS 16, watchOS 9, tvOS 16) and can be skipped with the SWIFTPM_ENABLE_BINARY_PARSING environment variable:

SWIFTPM_ENABLE_BINARY_PARSING=0 swift build

Embedded Swift

Macros are disabled under Embedded Swift (write conformances by hand) and the swift-syntax dependency can be skipped entirely with the SWIFTPM_ENABLE_MACROS environment variable:

SWIFTPM_ENABLE_MACROS=0 SWIFTPM_ENABLE_BINARY_PARSING=0 \
    swift build --target TLVCoding \
    --triple armv7em-none-none-eabi \
    -Xswiftc -enable-experimental-feature -Xswiftc Embedded \
    -Xswiftc -wmo

On platforms without Foundation, a minimal Data type is provided by the library.

See Also

  • CoreModel - Swift ORM with the same explicit coding and macro design
  • BluetoothLinux - Pure Swift Linux Bluetooth Stack
  • GATT - Bluetooth Generic Attribute Profile (GATT) for Swift