diff --git a/Package.swift b/Package.swift index 0cffd2c..e03ffd8 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.1 +// swift-tools-version:5.0 import PackageDescription let package = Package(name: "TLVCoding", @@ -12,4 +12,4 @@ let package = Package(name: "TLVCoding", .target(name: "TLVCoding", path: "./Sources"), .testTarget(name: "TLVCodingTests", dependencies: ["TLVCoding"]) ], - swiftLanguageVersions: [4]) + swiftLanguageVersions: [.v5]) diff --git a/Package@swift-4.1.swift b/Package@swift-4.1.swift new file mode 100644 index 0000000..0cffd2c --- /dev/null +++ b/Package@swift-4.1.swift @@ -0,0 +1,15 @@ +// swift-tools-version:4.1 +import PackageDescription + +let package = Package(name: "TLVCoding", + products: [ + .library( + name: "TLVCoding", + targets: ["TLVCoding"] + ) + ], + targets: [ + .target(name: "TLVCoding", path: "./Sources"), + .testTarget(name: "TLVCodingTests", dependencies: ["TLVCoding"]) + ], + swiftLanguageVersions: [4]) diff --git a/Package@swift-4.2.swift b/Package@swift-4.2.swift new file mode 100644 index 0000000..74a8ca9 --- /dev/null +++ b/Package@swift-4.2.swift @@ -0,0 +1,15 @@ +// swift-tools-version:4.2 +import PackageDescription + +let package = Package(name: "TLVCoding", + products: [ + .library( + name: "TLVCoding", + targets: ["TLVCoding"] + ) + ], + targets: [ + .target(name: "TLVCoding", path: "./Sources"), + .testTarget(name: "TLVCodingTests", dependencies: ["TLVCoding"]) + ], + swiftLanguageVersions: [.v4_2]) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift new file mode 100644 index 0000000..27ca396 --- /dev/null +++ b/Sources/CodingKey.swift @@ -0,0 +1,114 @@ +// +// CodingKey.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/12/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +public protocol TLVCodingKey: CodingKey { + + init?(code: TLVTypeCode) + + var code: TLVTypeCode { get } +} + +public extension TLVCodingKey { + + init?(intValue: Int) { + + guard intValue <= Int(UInt8.max), + intValue >= Int(UInt8.min) + else { return nil } + + self.init(code: TLVTypeCode(rawValue: UInt8(intValue))) + } + + var intValue: Int? { + return Int(code.rawValue) + } +} + +public extension TLVCodingKey where Self: RawRepresentable, Self.RawValue == TLVTypeCode.RawValue { + + init?(code: TLVTypeCode) { + self.init(rawValue: code.rawValue) + } + + var code: TLVTypeCode { + return TLVTypeCode(rawValue: rawValue) + } +} + +#if swift(>=4.2) +public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, Self.RawValue == TLVTypeCode.RawValue { + + init?(stringValue: String) { + + guard let value = Self.allCases.first(where: { $0.stringValue == stringValue }) + else { return nil } + + self = value + } +} +#endif + +internal extension TLVTypeCode { + + init? (codingKey: K) { + + if let tlvCodingKey = codingKey as? TLVCodingKey { + + self = tlvCodingKey.code + + } else if let intValue = codingKey.intValue { + + guard intValue <= Int(UInt8.max), + intValue >= Int(UInt8.min) + else { return nil } + + self.init(rawValue: UInt8(intValue)) + + } else if MemoryLayout.size == MemoryLayout.size, + Mirror(reflecting: codingKey).displayStyle == .enum { + + self.init(rawValue: unsafeBitCast(codingKey, to: UInt8.self)) + + } else { + + return nil + } + } +} + +internal extension Sequence where Element == CodingKey { + + /// KVC path string for current coding path. + var path: String { + return reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }) + } +} + +internal extension CodingKey { + + static var sanitizedName: String { + + let rawName = String(reflecting: self) + #if swift(>=5.0) + var elements = rawName.split(separator: ".") + #else + var elements = rawName.components(separatedBy: ".") + #endif + guard elements.count > 2 + else { return rawName } + elements.removeFirst() + #if swift(>=5.0) + elements.removeAll { $0.contains("(unknown context") } + #else + while let index = elements.index(where: { $0.contains("(unknown context") }) { + elements.remove(at: index) + } + #endif + return elements.reduce("", { $0 + ($0.isEmpty ? "" : ".") + $1 }) + } +} diff --git a/Sources/Data.swift b/Sources/Data.swift new file mode 100644 index 0000000..9492343 --- /dev/null +++ b/Sources/Data.swift @@ -0,0 +1,100 @@ +// +// Data.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/12/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +import Foundation + +internal extension Data { + + #if swift(>=5.0) || (swift(>=4.2) && XCODE) + func subdataNoCopy(in range: Range) -> Data { + + // stored in heap, can reuse buffer + if count > Data.inlineBufferSize { + + #if swift(>=5.0) + return withUnsafeBytes { (buffer: UnsafeRawBufferPointer) in + Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer.baseAddress!.advanced(by: range.lowerBound)), + count: range.count, + deallocator: .none) + } + #else + return withUnsafeBytes { + Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: $0.advanced(by: range.lowerBound)), + count: range.count, + deallocator: .none) + } + #endif + + } else { + + // stored in stack, must copy + return subdata(in: range) + } + } + #elseif swift(>=4.2) + func subdataNoCopy(in range: Range) -> Data { + + return withUnsafeBytes { + Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: $0.advanced(by: range.lowerBound)), + count: range.count, + deallocator: .none) + } + } + #elseif swift(>=4.0) + func subdataNoCopy(in range: CountableRange) -> Data { + + let pointer = withUnsafeBytes { UnsafeMutableRawPointer(mutating: $0).advanced(by: range.lowerBound) } + return Data(bytesNoCopy: pointer, count: range.count, deallocator: .none) + } + + /// Returns a new copy of the data in a specified range. + func subdata(in range: CountableRange) -> Data { + return Data(self[range]) + } + #endif + + func suffixNoCopy(from index: Int) -> Data { + + return subdataNoCopy(in: index ..< count) + } + + func suffixCheckingBounds(from start: Int) -> Data { + + if count > start { + + return Data(suffix(from: start)) + + } else { + + return Data() + } + } +} + +#if swift(>=5.0) || (swift(>=4.2) && XCODE) +private extension Data { + + /// Size of the inline buffer for `Foundation.Data` used in Swift 5. + /// + /// Used to determine wheather data is stored on stack or in heap. + static var inlineBufferSize: Int { + + // Keep up to date + // https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/Data.swift#L621 + #if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le) + typealias Buffer = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, + UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) //len //enum + #elseif arch(i386) || arch(arm) + typealias Buffer = (UInt8, UInt8, UInt8, UInt8, + UInt8, UInt8) + #endif + + return MemoryLayout.size + } +} +#endif diff --git a/Sources/DataConvertible.swift b/Sources/DataConvertible.swift new file mode 100644 index 0000000..308ba23 --- /dev/null +++ b/Sources/DataConvertible.swift @@ -0,0 +1,118 @@ +// +// DataConvertible.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/12/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +import Foundation + +/// Can be converted into data. +internal protocol DataConvertible { + + /// Append data representation into buffer. + static func += (data: inout T, value: Self) + + /// Length of value when encoded into data. + var dataLength: Int { get } +} + +extension Data { + + /// Initialize data with contents of value. + @inline(__always) + init (_ value: T) { + self.init(capacity: value.dataLength) + self += value + } + + init (_ sequence: T) where T.Element: DataConvertible { + + let dataLength = sequence.reduce(0, { $0 + $1.dataLength }) + self.init(capacity: dataLength) + sequence.forEach { self += $0 } + } +} + +// MARK: - UnsafeDataConvertible + +/// Internal Data casting protocol +internal protocol UnsafeDataConvertible: DataConvertible { } + +extension UnsafeDataConvertible { + + var dataLength: Int { + return MemoryLayout.size + } + + /// Append data representation into buffer. + static func += (data: inout T, value: Self) { + #if swift(>=4.2) + withUnsafePointer(to: value) { + $0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size) { + data.append($0, count: MemoryLayout.size) + } + } + #else + var value = value + withUnsafePointer(to: &value) { + $0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size) { + data.append($0, count: MemoryLayout.size) + } + } + #endif + } +} + +extension UInt16: UnsafeDataConvertible { } +extension UInt32: UnsafeDataConvertible { } +extension UInt64: UnsafeDataConvertible { } + +// MARK: - DataContainer + +/// Data container type. +internal protocol DataContainer: RandomAccessCollection where Self.Index == Int { + + subscript(index: Int) -> UInt8 { get } + + subscript(range: Range) -> Slice { get } + + mutating func append(_ newElement: UInt8) + + mutating func append(_ pointer: UnsafePointer, count: Int) + + mutating func append (contentsOf bytes: C) where C.Element == UInt8 + + #if swift(>=4.2) + static func += (lhs: inout Self, rhs: UInt8) + static func += (lhs: inout Self, rhs: C) where C.Element == UInt8 + #endif +} + +extension DataContainer { + + #if swift(>=4.2) + #else + static func += (lhs: inout Self, rhs: UInt8) { + lhs.append(rhs) + } + + static func += (lhs: inout Self, rhs: C) where C.Element == UInt8 { + lhs.append(contentsOf: rhs) + } + #endif + + mutating func append (_ value: T) { + self += value + } +} + +extension Data: DataContainer { + + #if swift(>=4.2) + static func += (lhs: inout Data, rhs: UInt8) { + lhs.append(rhs) + } + #endif +} diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index e3a696e..8e74e12 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -8,167 +8,846 @@ import Foundation +/// TLV8 Decoder public struct TLVDecoder { - public static func decode(data: Data, from types: [TLVDecodable.Type]) throws -> [TLVDecodable] { + // MARK: - Properties + + /// Any contextual information set by the user for encoding. + public var userInfo = [CodingUserInfoKey : Any]() + + /// Logger handler + public var log: ((String) -> ())? + + /// Format for numeric values. + public var numericFormat: TLVNumericFormat = .littleEndian + + // MARK: - Initialization + + public init() { } + + // MARK: - Methods + + public func decode (_ type: T.Type, from data: Data) throws -> T { - var offset = 0 + log?("Will decode \(String(reflecting: T.self))") + + let items = try decode(data) - var decodables = [TLVDecodable]() + let options = Decoder.Options(numericFormat: numericFormat) + + let decoder = Decoder(referencing: .items(items), + userInfo: userInfo, + log: log, + options: options) + + // decode from container + return try T.init(from: decoder) + } + + public func decode(_ data: Data) throws -> [TLVItem] { + + return try TLVDecoder.decode(data, codingPath: []) + } + + internal static func decode(_ data: Data, codingPath: [CodingKey]) throws -> [TLVItem] { + + var offset = 0 + var items = [TLVItem]() while offset < data.count { // validate size - guard data.count >= 3 - else { throw DecodingError.invalidSize(data.count, context: DecodingContext(offset: offset)) } + guard data.count >= 3 else { + //throw DecodingError.invalidSize(data.count, context: DecodingContext(offset: offset)) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Not enough bytes (\(data.count)) to continue parsing at offset \(offset)")) + } // get type let typeByte = data[offset] // 0 offset += 1 - guard let type = types.first(where: { $0.typeCode.rawValue == typeByte }) - else { throw DecodingError.invalidType(typeByte, context: DecodingContext(offset: offset)) } - let length = Int(data[offset]) // 1 offset += 1 // get value let valueData = Data(data[offset ..< offset + length]) - guard let value = type.init(valueData: valueData) - else { throw DecodingError.invalidValue(valueData, context: DecodingContext(offset: offset)) } + let item = TLVItem(type: TLVTypeCode(rawValue: typeByte), value: valueData) // append result - decodables.append(value) + items.append(item) // adjust offset for next value offset += length } - return decodables + return items + } +} + +internal extension TLVDecoder { + + final class Decoder: Swift.Decoder { + + /// The path of coding keys taken to get to this point in decoding. + fileprivate(set) var codingPath: [CodingKey] + + /// Any contextual information set by the user for decoding. + let userInfo: [CodingUserInfoKey : Any] + + fileprivate var stack: Stack + + /// Logger + let log: ((String) -> ())? + + let options: Options + + // MARK: - Initialization + + fileprivate init(referencing container: Stack.Container, + at codingPath: [CodingKey] = [], + userInfo: [CodingUserInfoKey : Any], + log: ((String) -> ())?, + options: Options) { + + self.stack = Stack(container) + self.codingPath = codingPath + self.userInfo = userInfo + self.log = log + self.options = options + } + + // MARK: - Methods + + func container (keyedBy type: Key.Type) throws -> KeyedDecodingContainer { + + log?("Requested container keyed by \(type.sanitizedName) for path \"\(codingPath.path)\"") + + let container = self.stack.top + + switch container { + case let .items(items): + let keyedContainer = TLVKeyedDecodingContainer(referencing: self, wrapping: items) + return KeyedDecodingContainer(keyedContainer) + case let .item(item): + let items = try decode(item.value, codingPath: codingPath) + let keyedContainer = TLVKeyedDecodingContainer(referencing: self, wrapping: items) + return KeyedDecodingContainer(keyedContainer) + } + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + + log?("Requested unkeyed container for path \"\(codingPath.path)\"") + + let container = self.stack.top + + switch container { + + case let .items(items): + + return TLVUnkeyedDecodingContainer(referencing: self, wrapping: items) + + case let .item(item): + + // forceably cast to array + do { + let items = try TLVDecoder.decode(item.value, codingPath: codingPath) + self.stack.pop() // replace stack + self.stack.push(.items(items)) + return TLVUnkeyedDecodingContainer(referencing: self, wrapping: items) + } catch { + log?("Could not decode for unkeyed container: \(error)") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot get unkeyed decoding container, invalid top container \(container).")) + } + } + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + + log?("Requested single value container for path \"\(codingPath.path)\"") + + let container = self.stack.top + + guard case let .item(item) = container else { + + throw DecodingError.typeMismatch(SingleValueDecodingContainer.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot get single value decoding container, invalid top container \(container).")) + } + + return TLVSingleValueDecodingContainer(referencing: self, wrapping: item) + } + } +} + +internal extension TLVDecoder.Decoder { + + struct Options { + + let numericFormat: TLVNumericFormat + } +} + +// MARK: - Coding Key + +internal extension TLVDecoder.Decoder { + + func typeCode (for key: Key) throws -> TLVTypeCode { + + guard let typeCode = TLVTypeCode(codingKey: key) else { + if let intValue = key.intValue { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has an invalid integer value \(intValue)")) + } else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has no integer value")) + } + } + return typeCode + } +} + +// MARK: - Unboxing Values + +internal extension TLVDecoder.Decoder { + + func unbox (_ data: Data, as type: T.Type) throws -> T { + + guard let value = T.init(tlvData: data) else { + + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Could not parse \(type) from \(data)")) + } + + return value + } + + func unboxNumeric (_ data: Data, as type: T.Type) throws -> T { + + var numericValue = try unbox(data, as: type) + switch options.numericFormat { + case .bigEndian: + numericValue = T.init(bigEndian: numericValue) + case .littleEndian: + numericValue = T.init(littleEndian: numericValue) + } + return numericValue + } + + /// Attempt to decode native value to expected type. + func unboxDecodable (_ item: TLVItem, as type: T.Type) throws -> T { + + // override for native types + if type == Data.self { + return item.value as! T // In this case T is Data + } else { + // push container to stack and decode using Decodable implementation + stack.push(.item(item)) + let decoded = try T(from: self) + stack.pop() + return decoded + } } } -// MARK: - Supporting Types +// MARK: - Stack -public extension TLVDecoder { +private extension TLVDecoder { - struct DecodingContext { + struct Stack { + + private(set) var containers = [Container]() + + fileprivate init(_ container: Container) { + + self.containers = [container] + } + + var top: Container { + + guard let container = containers.last + else { fatalError("Empty container stack.") } + + return container + } + + mutating func push(_ container: Container) { + + containers.append(container) + } - public let offset: Int + @discardableResult + mutating func pop() -> Container { + + guard let container = containers.popLast() + else { fatalError("Empty container stack.") } + + return container + } } +} + +fileprivate extension TLVDecoder.Stack { - enum DecodingError: Swift.Error { + enum Container { - case invalidSize(Int, context: DecodingContext) - case invalidType(UInt8, context: DecodingContext) - case invalidValue(Data, context: DecodingContext) - case decodableMismatch([TLVDecodable]) + case items([TLVItem]) + case item(TLVItem) } } -// MARK: - Coder Convenience Extensions +// MARK: - KeyedDecodingContainer -public extension TLVDecoder { +internal struct TLVKeyedDecodingContainer : KeyedDecodingContainerProtocol { + + typealias Key = K + + // MARK: Properties + + /// A reference to the encoder we're reading from. + let decoder: TLVDecoder.Decoder + + /// A reference to the container we're reading from. + let container: [TLVItem] + + /// The path of coding keys taken to get to this point in decoding. + let codingPath: [CodingKey] + + /// All the keys the Decoder has for this container. + let allKeys: [Key] + + // MARK: Initialization + + /// Initializes `self` by referencing the given decoder and container. + init(referencing decoder: TLVDecoder.Decoder, wrapping container: [TLVItem]) { + + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + self.allKeys = container.compactMap { Key(intValue: Int($0.type.rawValue)) } + } + + // MARK: KeyedDecodingContainerProtocol + + func contains(_ key: Key) -> Bool { + + self.decoder.log?("Check whether key \"\(key.stringValue)\" exists") + guard let typeCode = try? self.decoder.typeCode(for: key) + else { return false } + return container.contains { $0.type == typeCode } + } + + func decodeNil(forKey key: Key) throws -> Bool { + + // set coding key context + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + return try self.value(for: key) == nil + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + + return try decodeTLV(type, forKey: key) + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + + let value = try decodeNumeric(Int32.self, forKey: key) + return Int(value) + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + + return try decodeTLV(type, forKey: key) + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + + return try decodeNumeric(type, forKey: key) + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + + return try decodeNumeric(type, forKey: key) + } - static func decode (data: Data, from type: Decodable.Type) throws -> Decodable { + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - let decodables = try decode(data: data, from: [type]) + return try decodeNumeric(type, forKey: key) + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - guard decodables.count == 1, - let decodable = decodables[0] as? Decodable - else { throw DecodingError.decodableMismatch(decodables) } + let value = try decodeNumeric(UInt32.self, forKey: key) + return UInt(value) + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - return decodable + return try decodeTLV(type, forKey: key) } - static func decode - (data: Data, from types: (T1.Type, T2.Type)) throws -> (T1, T2) { + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - let decodables = try decode(data: data, from: [types.0, types.1]) + return try decodeNumeric(type, forKey: key) + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - guard decodables.count == 2, - let decodable1 = decodables[0] as? T1, - let decodable2 = decodables[1] as? T2 - else { throw DecodingError.decodableMismatch(decodables) } + return try decodeNumeric(type, forKey: key) + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - return (decodable1, decodable2) + return try decodeNumeric(type, forKey: key) } - static func decode - (data: Data, from types: (T1.Type, T2.Type, T3.Type)) throws -> (T1, T2, T3) { + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - let decodables = try decode(data: data, from: [types.0, types.1, types.2]) + let bitPattern = try decodeNumeric(UInt32.self, forKey: key) + return Float(bitPattern: bitPattern) + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - guard decodables.count == 3, - let decodable1 = decodables[0] as? T1, - let decodable2 = decodables[1] as? T2, - let decodable3 = decodables[2] as? T3 - else { throw DecodingError.decodableMismatch(decodables) } + let bitPattern = try decodeNumeric(UInt64.self, forKey: key) + return Double(bitPattern: bitPattern) + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + + return try decodeTLV(type, forKey: key) + } + + func decode (_ type: T.Type, forKey key: Key) throws -> T { - return (decodable1, decodable2, decodable3) + return try self.value(for: key, type: type) { try decoder.unboxDecodable($0, as: type) } } - static func decode - (data: Data, from types: (T1.Type, T2.Type, T3.Type, T4.Type)) throws -> (T1, T2, T3, T4) { + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - let decodables = try decode(data: data, from: [types.0, types.1, types.2, types.3]) + fatalError() + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + + fatalError() + } + + func superDecoder() throws -> Decoder { - guard decodables.count == 4, - let decodable1 = decodables[0] as? T1, - let decodable2 = decodables[1] as? T2, - let decodable3 = decodables[2] as? T3, - let decodable4 = decodables[3] as? T4 - else { throw DecodingError.decodableMismatch(decodables) } + fatalError() + } + + func superDecoder(forKey key: Key) throws -> Decoder { - return (decodable1, decodable2, decodable3, decodable4) + fatalError() } - static func decode - (data: Data, from types: (T1.Type, T2.Type, T3.Type, T4.Type, T5.Type)) throws -> (T1, T2, T3, T4, T5) { + // MARK: Private Methods + + /// Decode native value type from TLV data. + private func decodeTLV (_ type: T.Type, forKey key: Key) throws -> T { - let decodables = try decode(data: data, from: [types.0, types.1, types.2, types.3, types.4]) + return try self.value(for: key, type: type) { try decoder.unbox($0.value, as: type) } + } + + private func decodeNumeric (_ type: T.Type, forKey key: Key) throws -> T { - guard decodables.count == 5, - let decodable1 = decodables[0] as? T1, - let decodable2 = decodables[1] as? T2, - let decodable3 = decodables[2] as? T3, - let decodable4 = decodables[3] as? T4, - let decodable5 = decodables[4] as? T5 - else { throw DecodingError.decodableMismatch(decodables) } + return try self.value(for: key, type: type) { try decoder.unboxNumeric($0.value, as: type) } + } + + /// Access actual value + @inline(__always) + private func value (for key: Key, type: T.Type, decode: (TLVItem) throws -> T) throws -> T { - return (decodable1, decodable2, decodable3, decodable4, decodable5) + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + guard let item = try self.value(for: key) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + return try decode(item) } - static func decode - (data: Data, from types: (T1.Type, T2.Type, T3.Type, T4.Type, T5.Type, T6.Type)) throws -> (T1, T2, T3, T4, T5, T6) { + /// Access actual value + private func value(for key: Key) throws -> TLVItem? { - let decodables = try decode(data: data, from: [types.0, types.1, types.2, types.3, types.4, types.5]) + decoder.log?("Will read value at path \"\(decoder.codingPath.path)\"") + let typeCode = try self.decoder.typeCode(for: key) + return container.first { $0.type == typeCode } + } +} + +// MARK: - SingleValueDecodingContainer + +internal struct TLVSingleValueDecodingContainer: SingleValueDecodingContainer { + + // MARK: Properties + + /// A reference to the decoder we're reading from. + let decoder: TLVDecoder.Decoder + + /// A reference to the container we're reading from. + let container: TLVItem + + /// The path of coding keys taken to get to this point in decoding. + let codingPath: [CodingKey] + + // MARK: Initialization + + /// Initializes `self` by referencing the given decoder and container. + init(referencing decoder: TLVDecoder.Decoder, wrapping container: TLVItem) { - guard decodables.count == 6, - let decodable1 = decodables[0] as? T1, - let decodable2 = decodables[1] as? T2, - let decodable3 = decodables[2] as? T3, - let decodable4 = decodables[3] as? T4, - let decodable5 = decodables[4] as? T5, - let decodable6 = decodables[5] as? T6 - else { throw DecodingError.decodableMismatch(decodables) } + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + } + + // MARK: SingleValueDecodingContainer + + func decodeNil() -> Bool { - return (decodable1, decodable2, decodable3, decodable4, decodable5, decodable6) + return container.value.isEmpty } - static func decodeOptional - (data: Data, from types: (T1.Type, T2.Type, T3.Type, T4.Type, T5.Type, T6.Type)) throws -> (T1?, T2?, T3?, T4?, T5?, T6?) { + func decode(_ type: Bool.Type) throws -> Bool { + + return try self.decoder.unbox(container.value, as: type) + } + + func decode(_ type: Int.Type) throws -> Int { + + let value = try self.decoder.unboxNumeric(container.value, as: Int32.self) + return Int(value) + } + + func decode(_ type: Int8.Type) throws -> Int8 { + + return try self.decoder.unbox(container.value, as: type) + } + + func decode(_ type: Int16.Type) throws -> Int16 { + + return try self.decoder.unboxNumeric(container.value, as: type) + } + + func decode(_ type: Int32.Type) throws -> Int32 { + + return try self.decoder.unboxNumeric(container.value, as: type) + } + + func decode(_ type: Int64.Type) throws -> Int64 { + + return try self.decoder.unboxNumeric(container.value, as: type) + } + + func decode(_ type: UInt.Type) throws -> UInt { + + let value = try self.decoder.unboxNumeric(container.value, as: UInt32.self) + return UInt(value) + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + + return try self.decoder.unbox(container.value, as: type) + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + + return try self.decoder.unboxNumeric(container.value, as: type) + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + + return try self.decoder.unboxNumeric(container.value, as: type) + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + + return try self.decoder.unboxNumeric(container.value, as: type) + } + + func decode(_ type: Float.Type) throws -> Float { + + let value = try self.decoder.unboxNumeric(container.value, as: UInt32.self) + return Float(bitPattern: value) + } + + func decode(_ type: Double.Type) throws -> Double { + + let value = try self.decoder.unboxNumeric(container.value, as: UInt64.self) + return Double(bitPattern: value) + } + + func decode(_ type: String.Type) throws -> String { + + return try self.decoder.unbox(container.value, as: type) + } + + func decode (_ type: T.Type) throws -> T { + + return try self.decoder.unboxDecodable(container, as: type) + } +} + +// MARK: UnkeyedDecodingContainer + +internal struct TLVUnkeyedDecodingContainer: UnkeyedDecodingContainer { + + // MARK: Properties + + /// A reference to the encoder we're reading from. + let decoder: TLVDecoder.Decoder + + /// A reference to the container we're reading from. + let container: [TLVItem] + + /// The path of coding keys taken to get to this point in decoding. + let codingPath: [CodingKey] + + private(set) var currentIndex: Int = 0 + + // MARK: Initialization + + /// Initializes `self` by referencing the given decoder and container. + init(referencing decoder: TLVDecoder.Decoder, wrapping container: [TLVItem]) { + + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + } + + // MARK: UnkeyedDecodingContainer + + var count: Int? { + return _count + } + + private var _count: Int { + return container.count + } + + var isAtEnd: Bool { + return currentIndex >= _count + } + + mutating func decodeNil() throws -> Bool { + + try assertNotEnd() + + // never optional, decode + return false + } + + mutating func decode(_ type: Bool.Type) throws -> Bool { fatalError("stub") } + mutating func decode(_ type: Int.Type) throws -> Int { fatalError("stub") } + mutating func decode(_ type: Int8.Type) throws -> Int8 { fatalError("stub") } + mutating func decode(_ type: Int16.Type) throws -> Int16 { fatalError("stub") } + mutating func decode(_ type: Int32.Type) throws -> Int32 { fatalError("stub") } + mutating func decode(_ type: Int64.Type) throws -> Int64 { fatalError("stub") } + mutating func decode(_ type: UInt.Type) throws -> UInt { fatalError("stub") } + mutating func decode(_ type: UInt8.Type) throws -> UInt8 { fatalError("stub") } + mutating func decode(_ type: UInt16.Type) throws -> UInt16 { fatalError("stub") } + mutating func decode(_ type: UInt32.Type) throws -> UInt32 { fatalError("stub") } + mutating func decode(_ type: UInt64.Type) throws -> UInt64 { fatalError("stub") } + mutating func decode(_ type: Float.Type) throws -> Float { fatalError("stub") } + mutating func decode(_ type: Double.Type) throws -> Double { fatalError("stub") } + mutating func decode(_ type: String.Type) throws -> String { fatalError("stub") } + + mutating func decode (_ type: T.Type) throws -> T { + + try assertNotEnd() + + self.decoder.codingPath.append(Index(intValue: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + let item = self.container[self.currentIndex] + + let decoded = try self.decoder.unboxDecodable(item, as: type) + + self.currentIndex += 1 + + return decoded + } + + mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode \(type)")) + } + + mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + + throw DecodingError.typeMismatch([Any].self, DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode unkeyed container.")) + } + + mutating func superDecoder() throws -> Decoder { + + // set coding key context + self.decoder.codingPath.append(Index(intValue: currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + // log + self.decoder.log?("Requested super decoder for path \"\(self.decoder.codingPath.path)\"") + + // check for end of array + try assertNotEnd() + + // get item + let item = container[currentIndex] + + // increment counter + self.currentIndex += 1 + + // create new decoder + let decoder = TLVDecoder.Decoder(referencing: .item(item), + at: self.decoder.codingPath, + userInfo: self.decoder.userInfo, + log: self.decoder.log, + options: self.decoder.options) + + return decoder + } + + // MARK: Private Methods + + @inline(__always) + private func assertNotEnd() throws { + + guard isAtEnd == false else { + + throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [Index(intValue: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + } +} + +internal extension TLVUnkeyedDecodingContainer { + + struct Index: CodingKey { + + public let index: Int + + public init(intValue: Int) { + self.index = intValue + } + + public init?(stringValue: String) { + return nil + } + + public var intValue: Int? { + return index + } + + public var stringValue: String { + return "\(index)" + } + } +} + +// MARK: - Decodable Types + +extension String: TLVDecodable { + + public init?(tlvData data: Data) { + + self.init(data: data, encoding: .utf8) + } +} + +extension Bool: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self = data[0] != 0 + } +} + +extension UInt8: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self = data[0] + } +} + +extension UInt16: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self.init(bytes: (data[0], data[1])) + } +} + +extension UInt32: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self.init(bytes: (data[0], data[1], data[2], data[3])) + } +} + +extension UInt64: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self.init(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7])) + } +} + +extension Int8: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self = Int8(bitPattern: data[0]) + } +} + +extension Int16: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self.init(bytes: (data[0], data[1])) + } +} + +extension Int32: TLVDecodable { + + public init?(tlvData data: Data) { + + guard data.count == MemoryLayout.size + else { return nil } + + self.init(bytes: (data[0], data[1], data[2], data[3])) + } +} + +extension Int64: TLVDecodable { + + public init?(tlvData data: Data) { - let decodables = try decode(data: data, from: [types.0, types.1, types.2, types.3, types.4, types.5]) - .sorted(by: { Swift.type(of: $0).typeCode.rawValue < type(of: $1).typeCode.rawValue }) + guard data.count == MemoryLayout.size + else { return nil } - return (decodables.count > 0 ? decodables[0] as? T1 : nil, - decodables.count > 1 ? decodables[1] as? T2 : nil, - decodables.count > 2 ? decodables[2] as? T3 : nil, - decodables.count > 3 ? decodables[3] as? T4 : nil, - decodables.count > 4 ? decodables[4] as? T5 : nil, - decodables.count > 5 ? decodables[5] as? T6 : nil) + self.init(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7])) } } diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 9f7d402..4bd52c0 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -8,26 +8,669 @@ import Foundation +/// TLV8 Encoder public struct TLVEncoder { - public static func encode(_ encodables: [TLVEncodable]) -> Data { + // MARK: - Properties + + /// Any contextual information set by the user for encoding. + public var userInfo = [CodingUserInfoKey : Any]() + + /// Logger handler + public var log: ((String) -> ())? + + /// Format for numeric values. + public var numericFormat: TLVNumericFormat = .littleEndian + + // MARK: - Initialization + + public init() { } + + // MARK: - Methods + + public func encode (_ value: T) throws -> Data { + + log?("Will encode \(String(reflecting: T.self))") + + let options = Encoder.Options(numericFormat: numericFormat) + let encoder = Encoder(userInfo: userInfo, log: log, options: options) + try value.encode(to: encoder) + assert(encoder.stack.containers.count == 1) + + guard case let .items(container) = encoder.stack.root else { + + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) is not encoded as items.")) + } + + return container.data + } + + public func encode(_ items: [TLVItem]) -> Data { + return Data(items) + } + + public func encode(_ items: TLVItem...) -> Data { + return Data(items) + } +} + +internal extension TLVEncoder { + + final class Encoder: Swift.Encoder { + + // MARK: - Properties + + /// The path of coding keys taken to get to this point in encoding. + fileprivate(set) var codingPath: [CodingKey] + + /// Any contextual information set by the user for encoding. + let userInfo: [CodingUserInfoKey : Any] + + /// Logger + let log: ((String) -> ())? + + let options: Options + + private(set) var stack: Stack + + // MARK: - Initialization + + init(codingPath: [CodingKey] = [], + userInfo: [CodingUserInfoKey : Any], + log: ((String) -> ())?, + options: Options) { + + self.stack = Stack() + self.codingPath = codingPath + self.userInfo = userInfo + self.log = log + self.options = options + } + + // MARK: - Encoder + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + + log?("Requested container keyed by \(type.sanitizedName) for path \"\(codingPath.path)\"") + + let stackContainer = ItemsContainer() + self.stack.push(.items(stackContainer)) + + let keyedContainer = TLVKeyedContainer(referencing: self, wrapping: stackContainer) + + return KeyedEncodingContainer(keyedContainer) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + + log?("Requested unkeyed container for path \"\(codingPath.path)\"") + + let stackContainer = ItemsContainer() + self.stack.push(.items(stackContainer)) + + return TLVUnkeyedEncodingContainer(referencing: self, wrapping: stackContainer) + } + + func singleValueContainer() -> SingleValueEncodingContainer { + + log?("Requested single value container for path \"\(codingPath.path)\"") + + let stackContainer = ItemContainer() + self.stack.push(.item(stackContainer)) + + return TLVSingleValueEncodingContainer(referencing: self, wrapping: stackContainer) + } + } +} + +internal extension TLVEncoder.Encoder { + + struct Options { + + let numericFormat: TLVNumericFormat + } +} + +internal extension TLVEncoder.Encoder { + + func typeCode (for key: Key, value: T) throws -> TLVTypeCode { + + guard let typeCode = TLVTypeCode(codingKey: key) else { + if let intValue = key.intValue { + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has an invalid integer value \(intValue)")) + } else { + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has no integer value")) + } + } + return typeCode + } +} + +internal extension TLVEncoder.Encoder { + + @inline(__always) + func box (_ value: T) -> Data { + return value.tlvData + } + + @inline(__always) + func boxNumeric (_ value: T) -> Data { + + let numericValue: T + switch options.numericFormat { + case .bigEndian: + numericValue = value.bigEndian + case .littleEndian: + numericValue = value.littleEndian + } + return box(numericValue) + } + + func boxEncodable (_ value: T) throws -> Data { + + if let tlvEncodable = value as? TLVEncodable { + return tlvEncodable.tlvData + } else if let data = value as? Data { + return data + } else { + // encode using Encodable, should push new container. + try value.encode(to: self) + let nestedContainer = stack.pop() + return nestedContainer.data + } + } +} + +// MARK: - Stack + +internal extension TLVEncoder.Encoder { + + struct Stack { + + private(set) var containers = [Container]() - var data = Data() + fileprivate init() { } - for encodable in encodables { + var top: Container { - let type = Swift.type(of: encodable).typeCode.rawValue + guard let container = containers.last + else { fatalError("Empty container stack.") } - let valueData = encodable.valueData + return container + } + + var root: Container { + + guard let container = containers.first + else { fatalError("Empty container stack.") } + + return container + } + + mutating func push(_ container: Container) { - assert(valueData.isEmpty == false) + containers.append(container) + } + + @discardableResult + mutating func pop() -> Container { - let length = UInt8(valueData.count) + guard let container = containers.popLast() + else { fatalError("Empty container stack.") } - data.append(Data([type, length])) - data.append(valueData) + return container + } + } +} + +internal extension TLVEncoder.Encoder { + + final class ItemsContainer { + + var items = [TLVItem]() + + init() { } + + var data: Data { + return Data(items) } + } + + final class ItemContainer { + + var data: Data + + init(_ data: Data = Data()) { + + self.data = data + } + } + + enum Container { + + case items(ItemsContainer) + case item(ItemContainer) + + var data: Data { + + switch self { + case let .items(container): + return container.data + case let .item(container): + return container.data + } + } + } +} + + +// MARK: - KeyedEncodingContainerProtocol + +internal final class TLVKeyedContainer : KeyedEncodingContainerProtocol { + + typealias Key = K + + // MARK: - Properties + + /// A reference to the encoder we're writing to. + let encoder: TLVEncoder.Encoder + + /// The path of coding keys taken to get to this point in encoding. + let codingPath: [CodingKey] + + /// A reference to the container we're writing to. + let container: TLVEncoder.Encoder.ItemsContainer + + // MARK: - Initialization + + init(referencing encoder: TLVEncoder.Encoder, + wrapping container: TLVEncoder.Encoder.ItemsContainer) { + + self.encoder = encoder + self.codingPath = encoder.codingPath + self.container = container + } + + // MARK: - Methods + + func encodeNil(forKey key: K) throws { + // do nothing + } + + func encode(_ value: Bool, forKey key: K) throws { + try encodeTLV(value, forKey: key) + } + + func encode(_ value: Int, forKey key: K) throws { + try encodeNumeric(Int32(value), forKey: key) + } + + func encode(_ value: Int8, forKey key: K) throws { + try encodeTLV(value, forKey: key) + } + + func encode(_ value: Int16, forKey key: K) throws { + try encodeNumeric(value, forKey: key) + } + + func encode(_ value: Int32, forKey key: K) throws { + try encodeNumeric(value, forKey: key) + } + + func encode(_ value: Int64, forKey key: K) throws { + try encodeNumeric(value, forKey: key) + } + + func encode(_ value: UInt, forKey key: K) throws { + try encodeNumeric(UInt32(value), forKey: key) + } + + func encode(_ value: UInt8, forKey key: K) throws { + try encodeTLV(value, forKey: key) + } + + func encode(_ value: UInt16, forKey key: K) throws { + try encodeNumeric(value, forKey: key) + } + + func encode(_ value: UInt32, forKey key: K) throws { + try encodeNumeric(value, forKey: key) + } + + func encode(_ value: UInt64, forKey key: K) throws { + try encodeNumeric(value, forKey: key) + } + + func encode(_ value: Float, forKey key: K) throws { + try encodeNumeric(value.bitPattern, forKey: key) + } + + func encode(_ value: Double, forKey key: K) throws { + try encodeNumeric(value.bitPattern, forKey: key) + } + + func encode(_ value: String, forKey key: K) throws { + try encodeTLV(value, forKey: key) + } + + func encode (_ value: T, forKey key: K) throws { + + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + let data = try encoder.boxEncodable(value) + try setValue(value, data: data, for: key) + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: K) -> KeyedEncodingContainer where NestedKey : CodingKey { - return data + fatalError() + } + + func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer { + + fatalError() + } + + func superEncoder() -> Encoder { + + fatalError() + } + + func superEncoder(forKey key: K) -> Encoder { + + fatalError() + } + + // MARK: - Private Methods + + private func encodeNumeric (_ value: T, forKey key: K) throws { + + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + let data = encoder.boxNumeric(value) + try setValue(value, data: data, for: key) + } + + private func encodeTLV (_ value: T, forKey key: K) throws { + + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + let data = encoder.box(value) + try setValue(value, data: data, for: key) + } + + private func setValue (_ value: T, data: Data, for key: Key) throws { + + encoder.log?("Will encode value for key \(key.stringValue) at path \"\(encoder.codingPath.path)\"") + + let type = try encoder.typeCode(for: key, value: value) + let item = TLVItem(type: type, value: data) + self.container.items.append(item) + } +} + +// MARK: - SingleValueEncodingContainer + +internal final class TLVSingleValueEncodingContainer: SingleValueEncodingContainer { + + // MARK: - Properties + + /// A reference to the encoder we're writing to. + let encoder: TLVEncoder.Encoder + + /// The path of coding keys taken to get to this point in encoding. + let codingPath: [CodingKey] + + /// A reference to the container we're writing to. + let container: TLVEncoder.Encoder.ItemContainer + + /// Whether the data has been written + private var didWrite = false + + // MARK: - Initialization + + init(referencing encoder: TLVEncoder.Encoder, + wrapping container: TLVEncoder.Encoder.ItemContainer) { + + self.encoder = encoder + self.codingPath = encoder.codingPath + self.container = container + } + + // MARK: - Methods + + func encodeNil() throws { + // do nothing + } + + func encode(_ value: Bool) throws { write(encoder.box(value)) } + + func encode(_ value: String) throws { write(encoder.box(value)) } + + func encode(_ value: Double) throws { write(encoder.boxNumeric(value.bitPattern)) } + + func encode(_ value: Float) throws { write(encoder.boxNumeric(value.bitPattern)) } + + func encode(_ value: Int) throws { write(encoder.boxNumeric(Int32(value))) } + + func encode(_ value: Int8) throws { write(encoder.box(value)) } + + func encode(_ value: Int16) throws { write(encoder.boxNumeric(value)) } + + func encode(_ value: Int32) throws { write(encoder.boxNumeric(value)) } + + func encode(_ value: Int64) throws { write(encoder.boxNumeric(value)) } + + func encode(_ value: UInt) throws { write(encoder.boxNumeric(UInt32(value))) } + + func encode(_ value: UInt8) throws { write(encoder.box(value)) } + + func encode(_ value: UInt16) throws { write(encoder.boxNumeric(value)) } + + func encode(_ value: UInt32) throws { write(encoder.boxNumeric(value)) } + + func encode(_ value: UInt64) throws { write(encoder.boxNumeric(value)) } + + func encode (_ value: T) throws { write(try encoder.boxEncodable(value)) } + + // MARK: - Private Methods + + private func write(_ data: Data) { + + precondition(didWrite == false, "Data already written") + self.container.data = data + self.didWrite = true + } +} + +// MARK: - UnkeyedEncodingContainer + +internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { + + // MARK: - Properties + + /// A reference to the encoder we're writing to. + let encoder: TLVEncoder.Encoder + + /// The path of coding keys taken to get to this point in encoding. + let codingPath: [CodingKey] + + /// A reference to the container we're writing to. + let container: TLVEncoder.Encoder.ItemsContainer + + // MARK: - Initialization + + init(referencing encoder: TLVEncoder.Encoder, + wrapping container: TLVEncoder.Encoder.ItemsContainer) { + + self.encoder = encoder + self.codingPath = encoder.codingPath + self.container = container + } + + // MARK: - Methods + + /// The number of elements encoded into the container. + var count: Int { + return container.items.count + } + + func encodeNil() throws { + // do nothing + } + + func encode(_ value: Bool) throws { append(encoder.box(value)) } + + func encode(_ value: String) throws { append(encoder.box(value)) } + + func encode(_ value: Double) throws { append(encoder.boxNumeric(value.bitPattern)) } + + func encode(_ value: Float) throws { append(encoder.boxNumeric(value.bitPattern)) } + + func encode(_ value: Int) throws { append(encoder.boxNumeric(Int32(value))) } + + func encode(_ value: Int8) throws { append(encoder.box(value)) } + + func encode(_ value: Int16) throws { append(encoder.boxNumeric(value)) } + + func encode(_ value: Int32) throws { append(encoder.boxNumeric(value)) } + + func encode(_ value: Int64) throws { append(encoder.boxNumeric(value)) } + + func encode(_ value: UInt) throws { append(encoder.boxNumeric(UInt32(value))) } + + func encode(_ value: UInt8) throws { append(encoder.box(value)) } + + func encode(_ value: UInt16) throws { append(encoder.boxNumeric(value)) } + + func encode(_ value: UInt32) throws { append(encoder.boxNumeric(value)) } + + func encode(_ value: UInt64) throws { append(encoder.boxNumeric(value)) } + + func encode (_ value: T) throws { append(try encoder.boxEncodable(value)) } + + func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { + + fatalError() + } + + func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + + fatalError() + } + + func superEncoder() -> Encoder { + + fatalError() + } + + // MARK: - Private Methods + + private func append(_ data: Data) { + + let index = TLVTypeCode(rawValue: UInt8(count)) // current index + let item = TLVItem(type: index, value: data) + + // write + self.container.items.append(item) + } +} + +// MARK: - Data Types + +private extension TLVEncodable { + + var copyingBytes: Data { + + #if swift(>=5) + return withUnsafePointer(to: self, { Data(bytes: $0, count: MemoryLayout.size) }) + #else + var copy = self + return withUnsafePointer(to: ©, { Data(bytes: $0, count: MemoryLayout.size) }) + #endif + } +} + +extension UInt8: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension UInt16: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension UInt32: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension UInt64: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension Int8: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension Int16: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension Int32: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension Int64: TLVEncodable { + + public var tlvData: Data { + return copyingBytes + } +} + +extension Float: TLVEncodable { + + public var tlvData: Data { + return bitPattern.copyingBytes + } +} + +extension Double: TLVEncodable { + + public var tlvData: Data { + return bitPattern.copyingBytes + } +} + +extension Bool: TLVEncodable { + + public var tlvData: Data { + return UInt8(self ? 1 : 0).copyingBytes + } +} + +extension String: TLVEncodable { + + public var tlvData: Data { + return Data(self.utf8) } } diff --git a/Sources/Integer.swift b/Sources/Integer.swift new file mode 100644 index 0000000..1c34038 --- /dev/null +++ b/Sources/Integer.swift @@ -0,0 +1,97 @@ +// +// Integer.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/12/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +internal extension UInt16 { + + /// Initializes value from two bytes. + init(bytes: (UInt8, UInt8)) { + + self = unsafeBitCast(bytes, to: UInt16.self) + } + + /// Converts to two bytes. + var bytes: (UInt8, UInt8) { + + return unsafeBitCast(self, to: (UInt8, UInt8).self) + } +} + +internal extension UInt32 { + + /// Initializes value from four bytes. + init(bytes: (UInt8, UInt8, UInt8, UInt8)) { + + self = unsafeBitCast(bytes, to: UInt32.self) + } + + /// Converts to four bytes. + var bytes: (UInt8, UInt8, UInt8, UInt8) { + + return unsafeBitCast(self, to: (UInt8, UInt8, UInt8, UInt8).self) + } +} + +internal extension UInt64 { + + /// Initializes value from four bytes. + init(bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) { + + self = unsafeBitCast(bytes, to: UInt64.self) + } + + /// Converts to eight bytes. + var bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) { + + return unsafeBitCast(self, to: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8).self) + } +} + +internal extension Int16 { + + /// Initializes value from two bytes. + init(bytes: (UInt8, UInt8)) { + + self = unsafeBitCast(bytes, to: Int16.self) + } + + /// Converts to two bytes. + var bytes: (UInt8, UInt8) { + + return unsafeBitCast(self, to: (UInt8, UInt8).self) + } +} + +internal extension Int32 { + + /// Initializes value from four bytes. + init(bytes: (UInt8, UInt8, UInt8, UInt8)) { + + self = unsafeBitCast(bytes, to: Int32.self) + } + + /// Converts to four bytes. + var bytes: (UInt8, UInt8, UInt8, UInt8) { + + return unsafeBitCast(self, to: (UInt8, UInt8, UInt8, UInt8).self) + } +} + +internal extension Int64 { + + /// Initializes value from four bytes. + init(bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) { + + self = unsafeBitCast(bytes, to: Int64.self) + } + + /// Converts to eight bytes. + var bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) { + + return unsafeBitCast(self, to: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8).self) + } +} diff --git a/Sources/Item.swift b/Sources/Item.swift new file mode 100644 index 0000000..02f6ddf --- /dev/null +++ b/Sources/Item.swift @@ -0,0 +1,64 @@ +// +// Item.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/12/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +import Foundation + +/** + TLV8 (Type-length-value) Item + */ +public struct TLVItem: Equatable, Hashable { + + public var type: TLVTypeCode + + public var value: Data + + public init(type: TLVTypeCode, value: Data) { + + self.type = type + self.value = value + } +} + +public extension TLVItem { + + var length: UInt8 { + + return UInt8(value.count) + } +} + +public extension TLVItem { + + init?(data: Data) { + + fatalError() + } + + var data: Data { + + return Data(self) + } +} + +// MARK: - DataConvertible + +extension TLVItem: DataConvertible { + + var dataLength: Int { + + return 1 + 1 + value.count + } + + static func += (data: inout T, value: TLVItem) { + + data += value.type.rawValue + data += value.length + data += value.value + } +} + diff --git a/Sources/NumericFormat.swift b/Sources/NumericFormat.swift new file mode 100644 index 0000000..5d87d97 --- /dev/null +++ b/Sources/NumericFormat.swift @@ -0,0 +1,14 @@ +// +// NumericFormat.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/13/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +/// TLV Numeric Encoding Format +public enum TLVNumericFormat { + + case bigEndian + case littleEndian +} diff --git a/Sources/TLVCodable.swift b/Sources/TLVCodable.swift index a98e660..6a9d1ce 100644 --- a/Sources/TLVCodable.swift +++ b/Sources/TLVCodable.swift @@ -14,141 +14,10 @@ public typealias TLVCodable = TLVEncodable & TLVDecodable /// TLV Decodable type public protocol TLVDecodable { - static var typeCode: TLVTypeCode { get } - - init?(valueData: Foundation.Data) + init?(tlvData: Data) } public protocol TLVEncodable { - static var typeCode: TLVTypeCode { get } - - var valueData: Foundation.Data { get } -} - -/// TLV Type Code header -public protocol TLVTypeCode { - - init?(rawValue: UInt8) - - var rawValue: UInt8 { get } -} - -// MARK: - Codable Implementations - -#if swift(>=3.2) - -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue: RawRepresentable, Self.RawValue.RawValue == UInt8 { - - init?(valueData: Foundation.Data) { - - guard valueData.count == 1 - else { return nil } - - let valueByte = valueData[0] - - guard let rawValue = RawValue.init(rawValue: valueByte) - else { return nil } - - self.init(rawValue: rawValue) - } -} - -public extension TLVEncodable where Self: RawRepresentable, Self.RawValue: RawRepresentable, Self.RawValue.RawValue == UInt8 { - - var valueData: Foundation.Data { - - let byte = rawValue.rawValue - - return Data([byte]) - } + var tlvData: Data { get } } - -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue == String { - - init?(valueData: Foundation.Data) { - - guard let string = String(data: valueData, encoding: .utf8) - else { return nil } - - self.init(rawValue: string) - } -} - -public extension TLVEncodable where Self: RawRepresentable, Self.RawValue == String { - - var valueData: Foundation.Data { - - guard let data = self.rawValue.data(using: .utf8) - else { fatalError("Could not encode string") } - - return data - } -} - -#elseif swift(>=3.0) - -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue: RawRepresentable, Self.RawValue.RawValue: UnsignedInteger { - - public init?(valueData: Foundation.Data) { - - typealias IntegerType = Self.RawValue.RawValue - - assert(MemoryLayout.size == 1, "Default implementation only for UInt8 enums") - - guard valueData.count == 1 - else { return nil } - - let valueByte = valueData[0] - - guard let rawValue = RawValue.init(rawValue: valueByte as! IntegerType) - else { return nil } - - self.init(rawValue: rawValue) - } -} - -public extension TLVEncodable where Self: RawRepresentable, Self.RawValue: RawRepresentable, Self.RawValue.RawValue: UnsignedInteger { - - public var valueData: Foundation.Data { - - typealias IntegerType = Self.RawValue.RawValue - - assert(MemoryLayout.size == 1, "Default implementation only for UInt8 enums") - - let byte = numericCast(rawValue.rawValue) as UInt8 - - return Data([byte]) - } -} - -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue: ExpressibleByStringLiteral { - - public init?(valueData: Foundation.Data) { - - typealias StringType = Self.RawValue - - assert(Self.RawValue.self == String.self, "Default implementation only for String") - - guard let string = String(data: valueData, encoding: .utf8) - else { return nil } - - self.init(rawValue: string as! StringType) - } -} - -public extension TLVEncodable where Self: RawRepresentable, Self.RawValue: ExpressibleByStringLiteral { - - public var valueData: Foundation.Data { - - assert(Self.RawValue.self == String.self, "Default implementation only for String") - - guard let data = (self.rawValue as! String).data(using: .utf8) - else { fatalError("Could not encode string") } - - return data - } -} - -#endif - diff --git a/Sources/TypeCode.swift b/Sources/TypeCode.swift new file mode 100644 index 0000000..2ceb29f --- /dev/null +++ b/Sources/TypeCode.swift @@ -0,0 +1,20 @@ +// +// TypeCode.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/12/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +import Foundation + +/// TLV8 type code +public struct TLVTypeCode: RawRepresentable, Equatable, Hashable { + + public let rawValue: UInt8 + + public init(rawValue: UInt8) { + + self.rawValue = rawValue + } +} diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index e02b000..15dfb68 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -14,96 +14,277 @@ final class TLVCodingTests: XCTestCase { static var allTests = [ ("testCodable", testCodable), - ] + ("testCodingKeys", testCodingKeys) + ] func testCodable() { - let data = Data([1, 1, 0, 0, 7, 67, 111, 108, 101, 109, 97, 110]) + func test (_ value: T, _ data: Data) { + + var encoder = TLVEncoder() + encoder.log = { print("Encoder:", $0) } + do { + let encodedData = try encoder.encode(value) + XCTAssertEqual(encodedData, data, "Invalid data \(Array(encodedData))") + } catch { + dump(error) + XCTFail("Could not encode \(value)") + } + + var decoder = TLVDecoder() + decoder.log = { print("Decoder:", $0) } + do { + let decodedValue = try decoder.decode(T.self, from: data) + XCTAssertEqual(decodedValue, value) + } catch { + dump(error) + XCTFail("Could not decode \(value)") + } + } + + test(Person(gender: .male, name: "Coleman"), + Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110])) + + test(ProvisioningState(state: .idle, result: .notAvailible), + Data([0x01, 0x01, 0x00, 0x02, 0x01, 0x00])) + + test(ProvisioningState(state: .provisioning, result: .notAvailible), + Data([0x01, 0x01, 0x01, 0x02, 0x01, 0x00])) - guard let value = Person(data: data) - else { XCTFail("Could decode"); return } + test(Numeric( + boolean: true, + int: -10, + uint: 10, + float: 1.1234, + double: 10.9999, + int8: .max, + int16: -200, + int32: -2000, + int64: -20_000, + uint8: .max, + uint16: 300, + uint32: 3000, + uint64: 30_000), + Data([0, 1, 1, 1, 4, 246, 255, 255, 255, 2, 4, 10, 0, 0, 0, 3, 4, 146, 203, 143, 63, 4, 8, 114, 138, 142, 228, 242, 255, 37, 64, 5, 1, 127, 6, 2, 56, 255, 7, 4, 48, 248, 255, 255, 8, 8, 224, 177, 255, 255, 255, 255, 255, 255, 9, 1, 255, 10, 2, 44, 1, 11, 4, 184, 11, 0, 0, 12, 8, 48, 117, 0, 0, 0, 0, 0, 0])) - XCTAssert(value.data == data, "Could not encode") + test( + Profile( + person: Person( + gender: .male, + name: "Coleman" + ), friends: [ + Person( + gender: .male, + name: "Coleman" + ) + ], + userInfo: nil + ), + Data([0, 12, 0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110, + 1, 14, 0, 12, 0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110]) + ) - XCTAssert(value == Person(gender: .male, name: "Coleman")) + test( + Profile( + person: Person( + gender: .male, + name: "Coleman" + ), friends: [ + Person( + gender: .female, + name: "Gina" + ), + Person( + gender: .female, + name: "Jossy" + ),Person( + gender: .male, + name: "Jorge" + ) + ], + userInfo: nil + ), + Data([0, 12, + 0, 1, + 0, + 1, 7, + 67, 111, 108, 101, 109, 97, 110, + 1, 35, + 0, 9, + 0, 1, + 1, + 1, 4, + 71, 105, 110, 97, + 1, 10, + 0, 1, + 1, + 1, 5, + 74, 111, 115, 115, 121, + 2, 10, + 0, 1, + 0, + 1, 5, + 74, 111, 114, 103, 101 + ]) + ) + + test( + Binary( + data: Data([0x01, 0x02, 0x03, 0x04]), + value: .one + ), + Data([0, 4, 1, 2, 3, 4, 1, 2, 1, 0]) + ) + + test( + PrimitiveArray( + strings: ["1", "two", "three", ""], + integers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ), + Data([0, 17, 0, 1, 49, 1, 3, 116, 119, 111, 2, 5, 116, 104, 114, 101, 101, 3, 0, 1, 60, 0, 4, 1, 0, 0, 0, 1, 4, 2, 0, 0, 0, 2, 4, 3, 0, 0, 0, 3, 4, 4, 0, 0, 0, 4, 4, 5, 0, 0, 0, 5, 4, 6, 0, 0, 0, 6, 4, 7, 0, 0, 0, 7, 4, 8, 0, 0, 0, 8, 4, 9, 0, 0, 0, 9, 4, 10, 0, 0, 0]) + ) + } + + func testCodingKeys() { + + typealias CodingKeys = ProvisioningState.CodingKeys + + for codingKey in ProvisioningState.CodingKeys.allCases { + + XCTAssertEqual(CodingKeys(rawValue: codingKey.rawValue), codingKey) + XCTAssertEqual(CodingKeys(stringValue: codingKey.stringValue), codingKey) + } } } // MARK: - Supporting Types -public struct Person: Equatable { - - public enum Gender: UInt8 { - - case male - case female - } +public struct Person: Codable, Equatable, Hashable { public var gender: Gender - public var name: String +} + +public enum Gender: UInt8, Codable { + + case male + case female +} + +public struct ProvisioningState: Codable, Equatable { + + public var state: State + public var result: Result - public init(gender: Gender, name: String) { + public enum State: UInt8, Codable { - self.gender = gender - self.name = name + case idle = 0x00 + case provisioning = 0x01 } - public static func == (lhs: Person, rhs: Person) -> Bool { + public enum Result: UInt8, Codable { - return lhs.gender == rhs.gender - && lhs.name == rhs.name + case notAvailible = 0x00 + case success = 0x01 } +} + +internal extension ProvisioningState { - public init?(data: Data) { + enum CodingKeys: UInt8, TLVCodingKey, CaseIterable { - guard let fields = try? TLVDecoder.decode(data: data, from: (TLVField.Gender.self, TLVField.Name.self)) - else { return nil } - - self.gender = fields.0.rawValue - self.name = fields.1.rawValue + case state = 0x01 + case result = 0x02 } +} + +extension ProvisioningState.CodingKeys { - public var data: Data { + var stringValue: String { + switch self { + case .state: return "state" + case .result: return "result" + } + } +} + +#if swift(>=4.2) +#else +protocol CaseIterable: Hashable { + + static var allCases: Set { get } +} + +extension ProvisioningState.CodingKeys { + + static let allCases: Set = [.state, .result] + + init?(stringValue: String) { - let fields: [TLVEncodable] = [ - TLVField.Gender(rawValue: gender), - TLVField.Name(rawValue: name) - ] + guard let value = type(of: self).allCases.first(where: { $0.stringValue == stringValue }) + else { return nil } - return TLVEncoder.encode(fields) + self = value } +} +#endif + +public struct Profile: Codable, Equatable { - private enum TLVField { + public var person: Person + public var friends: [Person] + public var userInfo: [UInt: String]? +} + +public struct Numeric: Codable, Equatable, Hashable { + + public var boolean: Bool + public var int: Int + public var uint: UInt + public var float: Float + public var double: Double + public var int8: Int8 + public var int16: Int16 + public var int32: Int32 + public var int64: Int64 + public var uint8: UInt8 + public var uint16: UInt16 + public var uint32: UInt32 + public var uint64: UInt64 +} + +public struct Binary: Codable, Equatable, Hashable { + + public var data: Data + public var value: Codable +} + +public extension Binary { + + enum Codable: UInt16, Equatable, Hashable, Swift.Codable, TLVCodable { - enum TypeCode: UInt8, TLVTypeCode { - - case name - case gender - } + case zero + case one + case two + case three - struct Name: TLVCodable, RawRepresentable { - - static let typeCode: TLVTypeCode = TypeCode.name + public init?(tlvData: Data) { - var rawValue: String + guard let rawValue = UInt16(tlvData: tlvData)?.littleEndian + else { return nil } - init(rawValue: String) { - - self.rawValue = rawValue - } + self.init(rawValue: rawValue) } - struct Gender: TLVCodable, RawRepresentable { - - static let typeCode: TLVTypeCode = TypeCode.gender + public var tlvData: Data { - var rawValue: Person.Gender - - init(rawValue: Person.Gender) { - - self.rawValue = rawValue - } + return rawValue.littleEndian.tlvData } } } + +public struct PrimitiveArray: Codable, Equatable { + + var strings: [String] + var integers: [Int] +} diff --git a/Xcode/TLVCoding.xcodeproj/project.pbxproj b/Xcode/TLVCoding.xcodeproj/project.pbxproj index 82d8db9..6e32db0 100644 --- a/Xcode/TLVCoding.xcodeproj/project.pbxproj +++ b/Xcode/TLVCoding.xcodeproj/project.pbxproj @@ -20,6 +20,34 @@ 6E8F3B022051DA8D006964D5 /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8F3B002051DA8D006964D5 /* Decoder.swift */; }; 6E8F3B032051DA8D006964D5 /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8F3B002051DA8D006964D5 /* Decoder.swift */; }; 6E8F3B042051DA8D006964D5 /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8F3B002051DA8D006964D5 /* Decoder.swift */; }; + 6E8FE75322887B2B00CBCFA9 /* CodingKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */; }; + 6E8FE75422887B2B00CBCFA9 /* CodingKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */; }; + 6E8FE75522887B2B00CBCFA9 /* CodingKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */; }; + 6E8FE75622887B2B00CBCFA9 /* CodingKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */; }; + 6E8FE7582288811800CBCFA9 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7572288811800CBCFA9 /* Data.swift */; }; + 6E8FE7592288811800CBCFA9 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7572288811800CBCFA9 /* Data.swift */; }; + 6E8FE75A2288811800CBCFA9 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7572288811800CBCFA9 /* Data.swift */; }; + 6E8FE75B2288811800CBCFA9 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7572288811800CBCFA9 /* Data.swift */; }; + 6E8FE75D228881DF00CBCFA9 /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */; }; + 6E8FE75E228881DF00CBCFA9 /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */; }; + 6E8FE75F228881DF00CBCFA9 /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */; }; + 6E8FE760228881DF00CBCFA9 /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */; }; + 6E8FE7622288824000CBCFA9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7612288824000CBCFA9 /* Item.swift */; }; + 6E8FE7632288824000CBCFA9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7612288824000CBCFA9 /* Item.swift */; }; + 6E8FE7642288824000CBCFA9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7612288824000CBCFA9 /* Item.swift */; }; + 6E8FE7652288824000CBCFA9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7612288824000CBCFA9 /* Item.swift */; }; + 6E8FE7672288920600CBCFA9 /* TypeCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7662288920600CBCFA9 /* TypeCode.swift */; }; + 6E8FE7682288920600CBCFA9 /* TypeCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7662288920600CBCFA9 /* TypeCode.swift */; }; + 6E8FE7692288920600CBCFA9 /* TypeCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7662288920600CBCFA9 /* TypeCode.swift */; }; + 6E8FE76A2288920600CBCFA9 /* TypeCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7662288920600CBCFA9 /* TypeCode.swift */; }; + 6E8FE77022890C8800CBCFA9 /* Integer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE76F22890C8800CBCFA9 /* Integer.swift */; }; + 6E8FE77122890C8800CBCFA9 /* Integer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE76F22890C8800CBCFA9 /* Integer.swift */; }; + 6E8FE77222890C8800CBCFA9 /* Integer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE76F22890C8800CBCFA9 /* Integer.swift */; }; + 6E8FE77322890C8800CBCFA9 /* Integer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE76F22890C8800CBCFA9 /* Integer.swift */; }; + 6E8FE7752289356200CBCFA9 /* NumericFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7742289356200CBCFA9 /* NumericFormat.swift */; }; + 6E8FE7762289356200CBCFA9 /* NumericFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7742289356200CBCFA9 /* NumericFormat.swift */; }; + 6E8FE7772289356200CBCFA9 /* NumericFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7742289356200CBCFA9 /* NumericFormat.swift */; }; + 6E8FE7782289356200CBCFA9 /* NumericFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8FE7742289356200CBCFA9 /* NumericFormat.swift */; }; 8933C78E1EB5B82C000D00A4 /* TLVCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7891EB5B82A000D00A4 /* TLVCodingTests.swift */; }; 8933C78F1EB5B82C000D00A4 /* TLVCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7891EB5B82A000D00A4 /* TLVCodingTests.swift */; }; 8933C7901EB5B82D000D00A4 /* TLVCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7891EB5B82A000D00A4 /* TLVCodingTests.swift */; }; @@ -53,18 +81,25 @@ /* Begin PBXFileReference section */ 52D6D97C1BEFF229002C0205 /* TLVCoding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TLVCoding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 52D6D9861BEFF229002C0205 /* TLVCoding-iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TLVCoding-iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 52D6D9861BEFF229002C0205 /* TLVCodingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TLVCodingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6D9E21BEFFF6E002C0205 /* TLVCoding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TLVCoding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6D9F01BEFFFBE002C0205 /* TLVCoding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TLVCoding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6DA0F1BF000BD002C0205 /* TLVCoding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TLVCoding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6E8F3AF62051DA65006964D5 /* TLVCodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLVCodable.swift; sourceTree = ""; }; 6E8F3AFB2051DA85006964D5 /* Encoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encoder.swift; sourceTree = ""; }; 6E8F3B002051DA8D006964D5 /* Decoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decoder.swift; sourceTree = ""; }; + 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodingKey.swift; sourceTree = ""; }; + 6E8FE7572288811800CBCFA9 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; + 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataConvertible.swift; sourceTree = ""; }; + 6E8FE7612288824000CBCFA9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; + 6E8FE7662288920600CBCFA9 /* TypeCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeCode.swift; sourceTree = ""; }; + 6E8FE76F22890C8800CBCFA9 /* Integer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Integer.swift; sourceTree = ""; }; + 6E8FE7742289356200CBCFA9 /* NumericFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericFormat.swift; sourceTree = ""; }; 8933C7891EB5B82A000D00A4 /* TLVCodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLVCodingTests.swift; sourceTree = ""; }; AD2FAA261CD0B6D800659CF4 /* TLVCoding.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TLVCoding.plist; sourceTree = ""; }; AD2FAA281CD0B6E100659CF4 /* TLVCodingTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TLVCodingTests.plist; sourceTree = ""; }; - DD75027A1C68FCFC006590AF /* TLVCoding-macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TLVCoding-macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DD75028D1C690C7A006590AF /* TLVCoding-tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TLVCoding-tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DD75027A1C68FCFC006590AF /* TLVCodingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TLVCodingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DD75028D1C690C7A006590AF /* TLVCodingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TLVCodingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -137,12 +172,12 @@ isa = PBXGroup; children = ( 52D6D97C1BEFF229002C0205 /* TLVCoding.framework */, - 52D6D9861BEFF229002C0205 /* TLVCoding-iOS Tests.xctest */, + 52D6D9861BEFF229002C0205 /* TLVCodingTests.xctest */, 52D6D9E21BEFFF6E002C0205 /* TLVCoding.framework */, 52D6D9F01BEFFFBE002C0205 /* TLVCoding.framework */, 52D6DA0F1BF000BD002C0205 /* TLVCoding.framework */, - DD75027A1C68FCFC006590AF /* TLVCoding-macOS Tests.xctest */, - DD75028D1C690C7A006590AF /* TLVCoding-tvOS Tests.xctest */, + DD75027A1C68FCFC006590AF /* TLVCodingTests.xctest */, + DD75028D1C690C7A006590AF /* TLVCodingTests.xctest */, ); name = Products; sourceTree = ""; @@ -162,6 +197,13 @@ 6E8F3AF62051DA65006964D5 /* TLVCodable.swift */, 6E8F3AFB2051DA85006964D5 /* Encoder.swift */, 6E8F3B002051DA8D006964D5 /* Decoder.swift */, + 6E8FE7612288824000CBCFA9 /* Item.swift */, + 6E8FE7662288920600CBCFA9 /* TypeCode.swift */, + 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */, + 6E8FE7572288811800CBCFA9 /* Data.swift */, + 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */, + 6E8FE76F22890C8800CBCFA9 /* Integer.swift */, + 6E8FE7742289356200CBCFA9 /* NumericFormat.swift */, ); name = Sources; path = ../Sources; @@ -259,7 +301,7 @@ ); name = "TLVCoding-iOS Tests"; productName = TLVCodingTests; - productReference = 52D6D9861BEFF229002C0205 /* TLVCoding-iOS Tests.xctest */; + productReference = 52D6D9861BEFF229002C0205 /* TLVCodingTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 52D6D9E11BEFFF6E002C0205 /* TLVCoding-watchOS */ = { @@ -331,7 +373,7 @@ ); name = "TLVCoding-macOS Tests"; productName = "TLVCoding-OS Tests"; - productReference = DD75027A1C68FCFC006590AF /* TLVCoding-macOS Tests.xctest */; + productReference = DD75027A1C68FCFC006590AF /* TLVCodingTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; DD75028C1C690C7A006590AF /* TLVCoding-tvOS Tests */ = { @@ -349,7 +391,7 @@ ); name = "TLVCoding-tvOS Tests"; productName = "TLVCoding-tvOS Tests"; - productReference = DD75028D1C690C7A006590AF /* TLVCoding-tvOS Tests.xctest */; + productReference = DD75028D1C690C7A006590AF /* TLVCodingTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -380,11 +422,11 @@ }; 52D6DA0E1BF000BD002C0205 = { CreatedOnToolsVersion = 7.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; DD7502791C68FCFC006590AF = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; DD75028C1C690C7A006590AF = { CreatedOnToolsVersion = 7.2.1; @@ -474,8 +516,15 @@ buildActionMask = 2147483647; files = ( 6E8F3AFC2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE7582288811800CBCFA9 /* Data.swift in Sources */, + 6E8FE7752289356200CBCFA9 /* NumericFormat.swift in Sources */, 6E8F3B012051DA8D006964D5 /* Decoder.swift in Sources */, + 6E8FE77022890C8800CBCFA9 /* Integer.swift in Sources */, 6E8F3AF72051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7622288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75322887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE7672288920600CBCFA9 /* TypeCode.swift in Sources */, + 6E8FE75D228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -492,8 +541,15 @@ buildActionMask = 2147483647; files = ( 6E8F3AFE2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE75A2288811800CBCFA9 /* Data.swift in Sources */, + 6E8FE7772289356200CBCFA9 /* NumericFormat.swift in Sources */, 6E8F3B032051DA8D006964D5 /* Decoder.swift in Sources */, + 6E8FE77222890C8800CBCFA9 /* Integer.swift in Sources */, 6E8F3AF92051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7642288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75522887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE7692288920600CBCFA9 /* TypeCode.swift in Sources */, + 6E8FE75F228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -502,8 +558,15 @@ buildActionMask = 2147483647; files = ( 6E8F3AFF2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE75B2288811800CBCFA9 /* Data.swift in Sources */, + 6E8FE7782289356200CBCFA9 /* NumericFormat.swift in Sources */, 6E8F3B042051DA8D006964D5 /* Decoder.swift in Sources */, + 6E8FE77322890C8800CBCFA9 /* Integer.swift in Sources */, 6E8F3AFA2051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7652288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75622887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE76A2288920600CBCFA9 /* TypeCode.swift in Sources */, + 6E8FE760228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -512,8 +575,15 @@ buildActionMask = 2147483647; files = ( 6E8F3AFD2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE7592288811800CBCFA9 /* Data.swift in Sources */, + 6E8FE7762289356200CBCFA9 /* NumericFormat.swift in Sources */, 6E8F3B022051DA8D006964D5 /* Decoder.swift in Sources */, + 6E8FE77122890C8800CBCFA9 /* Integer.swift in Sources */, 6E8F3AF82051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7632288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75422887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE7682288920600CBCFA9 /* TypeCode.swift in Sources */, + 6E8FE75E228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -690,7 +760,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; ONLY_ACTIVE_ARCH = NO; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -710,7 +780,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; @@ -723,7 +793,7 @@ INFOPLIST_FILE = Configs/TLVCodingTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.TLVCoding.TLVCoding-iOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -735,7 +805,7 @@ INFOPLIST_FILE = Configs/TLVCodingTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.TLVCoding.TLVCoding-iOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; @@ -752,7 +822,7 @@ INFOPLIST_FILE = Configs/TLVCoding.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; @@ -772,7 +842,7 @@ INFOPLIST_FILE = Configs/TLVCoding.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -793,7 +863,7 @@ INFOPLIST_FILE = Configs/TLVCoding.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; @@ -813,7 +883,7 @@ INFOPLIST_FILE = Configs/TLVCoding.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -837,7 +907,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; }; @@ -858,7 +928,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_NAME = TLVCoding; + PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -874,7 +944,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "com.TLVCoding.TLVCoding-macOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; SDKROOT = macosx; }; name = Debug; @@ -888,7 +958,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "com.TLVCoding.TLVCoding-macOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; @@ -900,7 +970,7 @@ INFOPLIST_FILE = Configs/TLVCodingTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.TLVCoding.TLVCoding-tvOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; SDKROOT = appletvos; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -912,7 +982,7 @@ INFOPLIST_FILE = Configs/TLVCodingTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.TLVCoding.TLVCoding-tvOS-Tests"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TVOS_DEPLOYMENT_TARGET = 9.1; diff --git a/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-iOS.xcscheme b/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-iOS.xcscheme index b06f339..b636169 100644 --- a/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-iOS.xcscheme +++ b/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-iOS.xcscheme @@ -34,7 +34,7 @@ diff --git a/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-macOS.xcscheme b/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-macOS.xcscheme index 71d3707..623eb51 100644 --- a/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-macOS.xcscheme +++ b/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-macOS.xcscheme @@ -34,7 +34,7 @@ diff --git a/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-tvOS.xcscheme b/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-tvOS.xcscheme index 5ed7905..be81c77 100644 --- a/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-tvOS.xcscheme +++ b/Xcode/TLVCoding.xcodeproj/xcshareddata/xcschemes/TLVCoding-tvOS.xcscheme @@ -34,7 +34,7 @@