From d87ed125c06134a7cbbaf1500aecd06027746acb Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 12:19:04 -0500 Subject: [PATCH 01/33] Working on Encoder --- Sources/CodingKey.swift | 58 ++++++++ Sources/Data.swift | 100 +++++++++++++ Sources/DataConvertible.swift | 118 +++++++++++++++ Sources/Decoder.swift | 8 +- Sources/Encoder.swift | 169 ++++++++++++++++++++-- Sources/Item.swift | 9 ++ Sources/TLVCodable.swift | 132 +++++------------ Tests/TLVCodingTests/TLVCodingTests.swift | 9 +- Xcode/TLVCoding.xcodeproj/project.pbxproj | 40 +++++ 9 files changed, 525 insertions(+), 118 deletions(-) create mode 100644 Sources/CodingKey.swift create mode 100644 Sources/Data.swift create mode 100644 Sources/DataConvertible.swift create mode 100644 Sources/Item.swift diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift new file mode 100644 index 0000000..49a4ef3 --- /dev/null +++ b/Sources/CodingKey.swift @@ -0,0 +1,58 @@ +// +// 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) + } +} + +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 { + + self.init(rawValue: unsafeBitCast(codingKey, to: UInt8.self)) + + } else { + + return nil + } + } +} 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..99b5125 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -10,7 +10,13 @@ import Foundation public struct TLVDecoder { - public static func decode(data: Data, from types: [TLVDecodable.Type]) throws -> [TLVDecodable] { + +} + + +public extension TLVDecoder { + + static func decode(data: Data, from types: [TLVDecodable.Type]) throws -> [TLVDecodable] { var offset = 0 diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 9f7d402..868aa13 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -10,24 +10,173 @@ import Foundation public struct TLVEncoder { - public static func encode(_ encodables: [TLVEncodable]) -> Data { + public func encode (_ encodable: T) throws -> Data { - var data = Data() - for encodable in encodables { + } +} + +public extension TLVEncoder { + + public enum EncodingError: Error { + + public typealias Context = Swift.EncodingError.Context + + /// Invalid coding key provided. + case invalidKey(CodingKey, Context) + } +} + +public extension TLVEncoder { + + static func encode(_ items: [TLVItem]) -> Data { + return Data(items) + } + + static 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. + private(set) var codingPath: [CodingKey] + + /// Any contextual information set by the user for encoding. + let userInfo: [CodingUserInfoKey : Any] + + /// Logger + let log: ((String) -> ())? + + private(set) var stack: Stack + + // MARK: - Initialization + + init(codingPath: [CodingKey] = [], + userInfo: [CodingUserInfoKey : Any], + log: ((String) -> ())?) { - let type = Swift.type(of: encodable).typeCode.rawValue + self.stack = Stack() + self.codingPath = codingPath + self.userInfo = userInfo + self.log = log + } + + // MARK: - Encoder + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - let valueData = encodable.valueData + log?("Requested container keyed by \(type) for path \"\(codingPathString)\"") - assert(valueData.isEmpty == false) + let stackContainer = AttributesContainer() + self.stack.push(.attributes(stackContainer)) - let length = UInt8(valueData.count) + let keyedContainer = KeyedContainer(referencing: self, wrapping: stackContainer) - data.append(Data([type, length])) - data.append(valueData) + return KeyedEncodingContainer(keyedContainer) } - return data + func unkeyedContainer() -> UnkeyedEncodingContainer { + + log?("Requested unkeyed container for path \"\(codingPathString)\"") + + let stackContainer = AttributesContainer() + self.stack.push(.attributes(stackContainer)) + + return AttributesUnkeyedEncodingContainer(referencing: self, wrapping: stackContainer) + } + + func singleValueContainer() -> SingleValueEncodingContainer { + + log?("Requested single value container for path \"\(codingPathString)\"") + + let stackContainer = AttributeContainer() + self.stack.push(.attribute(stackContainer)) + + return AttributeSingleValueEncodingContainer(referencing: self, wrapping: stackContainer) + } } } + +internal extension TLVEncoder.Encoder { + + /// KVC path string for current coding path. + var codingPathString: String { + + return codingPath.reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }) + } + + func typeCode (for key: Key) throws -> TLVTypeCode { + + if let tlvCodingKey = key as? TLVCodingKey { + + return tlvCodingKey.code + + } else if let intValue = key.intValue { + + guard intValue <= Int(UInt8.max), + intValue >= Int(UInt8.min) else { + + throw NetlinkAttributeEncoder.EncodingError.invalidKey(key, EncodingError.Context(codingPath: codingPath, debugDescription: "\(key) has an invalid integer value \(intValue)")) + } + + return TLVTypeCode(rawValue: UInt8(intValue)) + + } else if MemoryLayout.size == MemoryLayout.size { + + return TLVTypeCode(rawValue: unsafeBitCast(codingKey, to: UInt8.self)) + + } else { + + throw TLVEncoder.EncodingError.invalidKey(key, EncodingError.Context(codingPath: codingPath, debugDescription: "\(key) has no integer value")) + } + } +} + +// MARK: - Stack + +internal extension TLVEncoder.Encoder { + + internal struct Stack { + + private(set) var containers = [Container]() + + fileprivate init() { } + + var top: Container { + + guard let container = containers.last + else { fatalError("Empty container stack.") } + + return container + } + + var root: Container { + + guard let container = containers.first + else { fatalError("Empty container stack.") } + + return container + } + + mutating func push(_ container: Container) { + + containers.append(container) + } + + @discardableResult + mutating func pop() -> Container { + + guard let container = containers.popLast() + else { fatalError("Empty container stack.") } + + return container + } + } +} + diff --git a/Sources/Item.swift b/Sources/Item.swift new file mode 100644 index 0000000..45f8bd7 --- /dev/null +++ b/Sources/Item.swift @@ -0,0 +1,9 @@ +// +// Item.swift +// TLVCoding +// +// Created by Alsey Coleman Miller on 5/12/19. +// Copyright © 2019 PureSwift. All rights reserved. +// + +import Foundation diff --git a/Sources/TLVCodable.swift b/Sources/TLVCodable.swift index a98e660..c27e124 100644 --- a/Sources/TLVCodable.swift +++ b/Sources/TLVCodable.swift @@ -14,141 +14,75 @@ public typealias TLVCodable = TLVEncodable & TLVDecodable /// TLV Decodable type public protocol TLVDecodable { - static var typeCode: TLVTypeCode { get } - - init?(valueData: Foundation.Data) + init?(tlvData: Foundation.Data) } public protocol TLVEncodable { - static var typeCode: TLVTypeCode { get } - - var valueData: Foundation.Data { get } + var tlvData: Foundation.Data { get } } -/// TLV Type Code header -public protocol TLVTypeCode { - - init?(rawValue: UInt8) +public struct TLVTypeCode: RawRepresentable, Equatable, Hashable { - var rawValue: UInt8 { get } -} - -// MARK: - Codable Implementations - -#if swift(>=3.2) - -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue: RawRepresentable, Self.RawValue.RawValue == UInt8 { + public let 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 } + public init(rawValue: UInt8) { - self.init(rawValue: rawValue) + self.rawValue = rawValue } } -public extension TLVEncodable where Self: RawRepresentable, Self.RawValue: RawRepresentable, Self.RawValue.RawValue == UInt8 { +/** + TLV8 (Type-length-value) Item + */ +public struct TLVItem: Equatable, Hashable { - var valueData: Foundation.Data { - - let byte = rawValue.rawValue - - return Data([byte]) - } -} - -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue == String { + public var type: TLVTypeCode - init?(valueData: Foundation.Data) { - - guard let string = String(data: valueData, encoding: .utf8) - else { return nil } + public var value: Data + + public init(type: TLVTypeCode, value: Data) { - self.init(rawValue: string) + self.type = type + self.value = value } } -public extension TLVEncodable where Self: RawRepresentable, Self.RawValue == String { +public extension TLVItem { - var valueData: Foundation.Data { + var length: UInt8 { - guard let data = self.rawValue.data(using: .utf8) - else { fatalError("Could not encode string") } - - return data + return UInt8(value.count) } } -#elseif swift(>=3.0) - -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue: RawRepresentable, Self.RawValue.RawValue: UnsignedInteger { +public extension TLVItem { - public init?(valueData: Foundation.Data) { + init?(data: 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) + fatalError() } -} - -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 + var data: Data { - return Data([byte]) + return Data(self) } } -public extension TLVDecodable where Self: RawRepresentable, Self.RawValue: ExpressibleByStringLiteral { +// MARK: - DataConvertible + +extension TLVItem: DataConvertible { - public init?(valueData: Foundation.Data) { - - typealias StringType = Self.RawValue - - assert(Self.RawValue.self == String.self, "Default implementation only for String") + var dataLength: Int { - guard let string = String(data: valueData, encoding: .utf8) - else { return nil } - - self.init(rawValue: string as! StringType) + return 1 + 1 + value.count } -} - -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") + static func += (data: inout T, value: TLVItem) { - guard let data = (self.rawValue as! String).data(using: .utf8) - else { fatalError("Could not encode string") } - - return data + data += value.type.rawValue + data += value.length + data += value.value } } -#endif - diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index e02b000..65992d6 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -24,14 +24,13 @@ final class TLVCodingTests: XCTestCase { else { XCTFail("Could decode"); return } XCTAssert(value.data == data, "Could not encode") - XCTAssert(value == Person(gender: .male, name: "Coleman")) } } // MARK: - Supporting Types -public struct Person: Equatable { +public struct Person: Codable, Equatable, Hashable { public enum Gender: UInt8 { @@ -49,12 +48,6 @@ public struct Person: Equatable { self.name = name } - public static func == (lhs: Person, rhs: Person) -> Bool { - - return lhs.gender == rhs.gender - && lhs.name == rhs.name - } - public init?(data: Data) { guard let fields = try? TLVDecoder.decode(data: data, from: (TLVField.Gender.self, TLVField.Name.self)) diff --git a/Xcode/TLVCoding.xcodeproj/project.pbxproj b/Xcode/TLVCoding.xcodeproj/project.pbxproj index 82d8db9..af23491 100644 --- a/Xcode/TLVCoding.xcodeproj/project.pbxproj +++ b/Xcode/TLVCoding.xcodeproj/project.pbxproj @@ -20,6 +20,22 @@ 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 */; }; 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 */; }; @@ -60,6 +76,10 @@ 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 = ""; }; 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 = ""; }; @@ -162,6 +182,10 @@ 6E8F3AF62051DA65006964D5 /* TLVCodable.swift */, 6E8F3AFB2051DA85006964D5 /* Encoder.swift */, 6E8F3B002051DA8D006964D5 /* Decoder.swift */, + 6E8FE7612288824000CBCFA9 /* Item.swift */, + 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */, + 6E8FE7572288811800CBCFA9 /* Data.swift */, + 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */, ); name = Sources; path = ../Sources; @@ -474,8 +498,12 @@ buildActionMask = 2147483647; files = ( 6E8F3AFC2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE7582288811800CBCFA9 /* Data.swift in Sources */, 6E8F3B012051DA8D006964D5 /* Decoder.swift in Sources */, 6E8F3AF72051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7622288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75322887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE75D228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -492,8 +520,12 @@ buildActionMask = 2147483647; files = ( 6E8F3AFE2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE75A2288811800CBCFA9 /* Data.swift in Sources */, 6E8F3B032051DA8D006964D5 /* Decoder.swift in Sources */, 6E8F3AF92051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7642288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75522887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE75F228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -502,8 +534,12 @@ buildActionMask = 2147483647; files = ( 6E8F3AFF2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE75B2288811800CBCFA9 /* Data.swift in Sources */, 6E8F3B042051DA8D006964D5 /* Decoder.swift in Sources */, 6E8F3AFA2051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7652288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75622887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE760228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -512,8 +548,12 @@ buildActionMask = 2147483647; files = ( 6E8F3AFD2051DA85006964D5 /* Encoder.swift in Sources */, + 6E8FE7592288811800CBCFA9 /* Data.swift in Sources */, 6E8F3B022051DA8D006964D5 /* Decoder.swift in Sources */, 6E8F3AF82051DA65006964D5 /* TLVCodable.swift in Sources */, + 6E8FE7632288824000CBCFA9 /* Item.swift in Sources */, + 6E8FE75422887B2B00CBCFA9 /* CodingKey.swift in Sources */, + 6E8FE75E228881DF00CBCFA9 /* DataConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From a21d9d51e43d0173ab7e99758607e58cafea9631 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 13:58:10 -0500 Subject: [PATCH 02/33] Working on Encoder --- Sources/CodingKey.swift | 27 -- Sources/Decoder.swift | 123 +----- Sources/Encoder.swift | 508 +++++++++++++++++++++- Sources/Item.swift | 55 +++ Sources/TLVCodable.swift | 65 --- Sources/TypeCode.swift | 20 + Tests/TLVCodingTests/TLVCodingTests.swift | 80 +--- Xcode/TLVCoding.xcodeproj/project.pbxproj | 14 +- 8 files changed, 593 insertions(+), 299 deletions(-) create mode 100644 Sources/TypeCode.swift diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 49a4ef3..45865b2 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -29,30 +29,3 @@ public extension TLVCodingKey { return Int(code.rawValue) } } - -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 { - - self.init(rawValue: unsafeBitCast(codingKey, to: UInt8.self)) - - } else { - - return nil - } - } -} diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index 99b5125..815edc9 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -14,13 +14,12 @@ public struct TLVDecoder { } -public extension TLVDecoder { +internal extension TLVDecoder { - static func decode(data: Data, from types: [TLVDecodable.Type]) throws -> [TLVDecodable] { + static func decode(_ data: Data) throws -> [TLVItem] { var offset = 0 - - var decodables = [TLVDecodable]() + var items = [TLVItem]() while offset < data.count { @@ -32,26 +31,22 @@ public extension TLVDecoder { 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 } } @@ -72,109 +67,3 @@ public extension TLVDecoder { case decodableMismatch([TLVDecodable]) } } - - -// MARK: - Coder Convenience Extensions - -public extension TLVDecoder { - - static func decode (data: Data, from type: Decodable.Type) throws -> Decodable { - - let decodables = try decode(data: data, from: [type]) - - guard decodables.count == 1, - let decodable = decodables[0] as? Decodable - else { throw DecodingError.decodableMismatch(decodables) } - - return decodable - } - - static func decode - (data: Data, from types: (T1.Type, T2.Type)) throws -> (T1, T2) { - - let decodables = try decode(data: data, from: [types.0, types.1]) - - guard decodables.count == 2, - let decodable1 = decodables[0] as? T1, - let decodable2 = decodables[1] as? T2 - else { throw DecodingError.decodableMismatch(decodables) } - - return (decodable1, decodable2) - } - - static func decode - (data: Data, from types: (T1.Type, T2.Type, T3.Type)) throws -> (T1, T2, T3) { - - let decodables = try decode(data: data, from: [types.0, types.1, types.2]) - - 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) } - - return (decodable1, decodable2, decodable3) - } - - static func decode - (data: Data, from types: (T1.Type, T2.Type, T3.Type, T4.Type)) throws -> (T1, T2, T3, T4) { - - let decodables = try decode(data: data, from: [types.0, types.1, types.2, types.3]) - - 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) } - - return (decodable1, decodable2, decodable3, decodable4) - } - - static func decode - (data: Data, from types: (T1.Type, T2.Type, T3.Type, T4.Type, T5.Type)) throws -> (T1, T2, T3, T4, T5) { - - let decodables = try decode(data: data, from: [types.0, types.1, types.2, types.3, types.4]) - - 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 (decodable1, decodable2, decodable3, decodable4, decodable5) - } - - 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) { - - let decodables = try decode(data: data, from: [types.0, types.1, types.2, types.3, types.4, types.5]) - - 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) } - - return (decodable1, decodable2, decodable3, decodable4, decodable5, decodable6) - } - - 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?) { - - 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 }) - - 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) - } -} diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 868aa13..894bcd3 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -10,23 +10,47 @@ import Foundation public struct TLVEncoder { - public func encode (_ encodable: T) throws -> Data { + // MARK: - Properties + + /// Any contextual information set by the user for encoding. + public var userInfo = [CodingUserInfoKey : Any]() + + /// Logger handler + public var log: ((String) -> ())? + + // MARK: - Initialization + + public init() { } + + // MARK: - Methods + + public func encode (_ value: T) throws -> Data { + let encoder = Encoder(userInfo: userInfo, log: log) + 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 extension TLVEncoder { - public enum EncodingError: Error { - - public typealias Context = Swift.EncodingError.Context - - /// Invalid coding key provided. - case invalidKey(CodingKey, Context) + func encode(_ items: [TLVItem]) -> Data { + return Data(items) + } + + func encode(_ items: TLVItem...) -> Data { + return Data(items) } } +@available(*, deprecated, message: "Use TLVEncoder instance instead") public extension TLVEncoder { static func encode(_ items: [TLVItem]) -> Data { @@ -45,7 +69,7 @@ internal extension TLVEncoder { // MARK: - Properties /// The path of coding keys taken to get to this point in encoding. - private(set) var codingPath: [CodingKey] + fileprivate(set) var codingPath: [CodingKey] /// Any contextual information set by the user for encoding. let userInfo: [CodingUserInfoKey : Any] @@ -73,10 +97,10 @@ internal extension TLVEncoder { log?("Requested container keyed by \(type) for path \"\(codingPathString)\"") - let stackContainer = AttributesContainer() - self.stack.push(.attributes(stackContainer)) + let stackContainer = ItemsContainer() + self.stack.push(.items(stackContainer)) - let keyedContainer = KeyedContainer(referencing: self, wrapping: stackContainer) + let keyedContainer = TLVKeyedContainer(referencing: self, wrapping: stackContainer) return KeyedEncodingContainer(keyedContainer) } @@ -85,20 +109,20 @@ internal extension TLVEncoder { log?("Requested unkeyed container for path \"\(codingPathString)\"") - let stackContainer = AttributesContainer() - self.stack.push(.attributes(stackContainer)) + let stackContainer = ItemContainer() + self.stack.push(.item(stackContainer)) - return AttributesUnkeyedEncodingContainer(referencing: self, wrapping: stackContainer) + return TLVUnkeyedEncodingContainer(referencing: self, wrapping: stackContainer) } func singleValueContainer() -> SingleValueEncodingContainer { log?("Requested single value container for path \"\(codingPathString)\"") - let stackContainer = AttributeContainer() - self.stack.push(.attribute(stackContainer)) + let stackContainer = ItemContainer() + self.stack.push(.item(stackContainer)) - return AttributeSingleValueEncodingContainer(referencing: self, wrapping: stackContainer) + return TLVSingleValueEncodingContainer(referencing: self, wrapping: stackContainer) } } } @@ -111,7 +135,7 @@ internal extension TLVEncoder.Encoder { return codingPath.reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }) } - func typeCode (for key: Key) throws -> TLVTypeCode { + func typeCode (for key: Key, value: Any) throws -> TLVTypeCode { if let tlvCodingKey = key as? TLVCodingKey { @@ -122,18 +146,40 @@ internal extension TLVEncoder.Encoder { guard intValue <= Int(UInt8.max), intValue >= Int(UInt8.min) else { - throw NetlinkAttributeEncoder.EncodingError.invalidKey(key, EncodingError.Context(codingPath: codingPath, debugDescription: "\(key) has an invalid integer value \(intValue)")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has an invalid integer value \(intValue)")) } return TLVTypeCode(rawValue: UInt8(intValue)) } else if MemoryLayout.size == MemoryLayout.size { - return TLVTypeCode(rawValue: unsafeBitCast(codingKey, to: UInt8.self)) + return TLVTypeCode(rawValue: unsafeBitCast(key, to: UInt8.self)) } else { - throw TLVEncoder.EncodingError.invalidKey(key, EncodingError.Context(codingPath: codingPath, debugDescription: "\(key) has no integer value")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has no integer value")) + } + } +} + +internal extension TLVEncoder.Encoder { + + @inline(__always) + func box (_ value: T) -> Data { + return value.tlvData + } + + 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 } } } @@ -142,7 +188,7 @@ internal extension TLVEncoder.Encoder { internal extension TLVEncoder.Encoder { - internal struct Stack { + struct Stack { private(set) var containers = [Container]() @@ -180,3 +226,421 @@ internal extension TLVEncoder.Encoder { } } +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 + +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 _encode(value, forKey: key) } + + func encode(_ value: Int, forKey key: K) throws { try _encode(Int32(value), forKey: key) } + + func encode(_ value: Int8, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: Int16, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: Int32, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: Int64, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: UInt, forKey key: K) throws { try _encode(UInt32(value), forKey: key) } + + func encode(_ value: UInt8, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: UInt16, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: UInt32, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: UInt64, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: Float, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: Double, forKey key: K) throws { try _encode(value, forKey: key) } + + func encode(_ value: String, forKey key: K) throws { try _encode(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 { + + fatalError() + } + + func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer { + + fatalError() + } + + func superEncoder() -> Encoder { + + fatalError() + } + + func superEncoder(forKey key: K) -> Encoder { + + fatalError() + } + + // MARK: - Private Methods + + private func _encode (_ 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: Any, data: Data, for key: Key) throws { + + encoder.log?("Will encode value for key \(key.stringValue) at path \"\(encoder.codingPathString)\"") + + let type = try encoder.typeCode(for: key, value: value) + let item = TLVItem(type: type, value: data) + self.container.items.append(item) + } +} + +// MARK: - SingleValueEncodingContainer + +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.box(value)) } + + func encode(_ value: Float) throws { write(encoder.box(value)) } + + func encode(_ value: Int) throws { write(encoder.box(Int32(value))) } + + func encode(_ value: Int8) throws { write(encoder.box(value)) } + + func encode(_ value: Int16) throws { write(encoder.box(value)) } + + func encode(_ value: Int32) throws { write(encoder.box(value)) } + + func encode(_ value: Int64) throws { write(encoder.box(value)) } + + func encode(_ value: UInt) throws { write(encoder.box(UInt32(value))) } + + func encode(_ value: UInt8) throws { write(encoder.box(value)) } + + func encode(_ value: UInt16) throws { write(encoder.box(value)) } + + func encode(_ value: UInt32) throws { write(encoder.box(value)) } + + func encode(_ value: UInt64) throws { write(encoder.box(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 + +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.ItemContainer + + // MARK: - Initialization + + init(referencing encoder: TLVEncoder.Encoder, + wrapping container: TLVEncoder.Encoder.ItemContainer) { + + self.encoder = encoder + self.codingPath = encoder.codingPath + self.container = container + } + + // MARK: - Methods + + /// The number of elements encoded into the container. + private(set) var count: Int = 0 + + 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.box(value)) } + + func encode(_ value: Float) throws { append(encoder.box(value)) } + + func encode(_ value: Int) throws { append(encoder.box(Int32(value))) } + + func encode(_ value: Int8) throws { append(encoder.box(value)) } + + func encode(_ value: Int16) throws { append(encoder.box(value)) } + + func encode(_ value: Int32) throws { append(encoder.box(value)) } + + func encode(_ value: Int64) throws { append(encoder.box(value)) } + + func encode(_ value: UInt) throws { append(encoder.box(UInt32(value))) } + + func encode(_ value: UInt8) throws { append(encoder.box(value)) } + + func encode(_ value: UInt16) throws { append(encoder.box(value)) } + + func encode(_ value: UInt32) throws { append(encoder.box(value)) } + + func encode(_ value: UInt64) throws { append(encoder.box(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) { + + self.container.data += data + self.count += 1 + } +} + +// MARK: - Data Types + +private extension TLVEncodable { + + var copyingBytes: Data { + + var copy = self + return withUnsafePointer(to: ©, { Data(bytes: $0, count: MemoryLayout.size) }) + } +} + +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 copyingBytes + } +} + +extension Double: TLVEncodable { + + public var tlvData: Data { + + return 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/Item.swift b/Sources/Item.swift index 45f8bd7..02f6ddf 100644 --- a/Sources/Item.swift +++ b/Sources/Item.swift @@ -7,3 +7,58 @@ // 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/TLVCodable.swift b/Sources/TLVCodable.swift index c27e124..b3c22b4 100644 --- a/Sources/TLVCodable.swift +++ b/Sources/TLVCodable.swift @@ -21,68 +21,3 @@ public protocol TLVEncodable { var tlvData: Foundation.Data { get } } - -public struct TLVTypeCode: RawRepresentable, Equatable, Hashable { - - public let rawValue: UInt8 - - public init(rawValue: UInt8) { - - self.rawValue = rawValue - } -} - -/** - 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/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 65992d6..39bc2a3 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -13,31 +13,31 @@ import XCTest final class TLVCodingTests: XCTestCase { static var allTests = [ - ("testCodable", testCodable), + ("testEncode", testEncode), ] - func testCodable() { + func testEncode() { - let data = Data([1, 1, 0, 0, 7, 67, 111, 108, 101, 109, 97, 110]) + let data = Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110]) + let value = Person(gender: .male, name: "Coleman") - guard let value = Person(data: data) - else { XCTFail("Could decode"); return } - - XCTAssert(value.data == data, "Could not encode") - XCTAssert(value == Person(gender: .male, name: "Coleman")) + let encoder = TLVEncoder() + var encodedData = Data() + XCTAssertNoThrow(encodedData = try encoder.encode(value)) + XCTAssertEqual(encodedData, data) } } // MARK: - Supporting Types -public struct Person: Codable, Equatable, Hashable { +public enum Gender: UInt8, Codable { - public enum Gender: UInt8 { + case male + case female +} + +public struct Person: Codable, Equatable, Hashable { - case male - case female - } - public var gender: Gender public var name: String @@ -47,56 +47,4 @@ public struct Person: Codable, Equatable, Hashable { self.gender = gender self.name = name } - - public init?(data: Data) { - - 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 - } - - public var data: Data { - - let fields: [TLVEncodable] = [ - TLVField.Gender(rawValue: gender), - TLVField.Name(rawValue: name) - ] - - return TLVEncoder.encode(fields) - } - - private enum TLVField { - - enum TypeCode: UInt8, TLVTypeCode { - - case name - case gender - } - - struct Name: TLVCodable, RawRepresentable { - - static let typeCode: TLVTypeCode = TypeCode.name - - var rawValue: String - - init(rawValue: String) { - - self.rawValue = rawValue - } - } - - struct Gender: TLVCodable, RawRepresentable { - - static let typeCode: TLVTypeCode = TypeCode.gender - - var rawValue: Person.Gender - - init(rawValue: Person.Gender) { - - self.rawValue = rawValue - } - } - } } diff --git a/Xcode/TLVCoding.xcodeproj/project.pbxproj b/Xcode/TLVCoding.xcodeproj/project.pbxproj index af23491..13025e7 100644 --- a/Xcode/TLVCoding.xcodeproj/project.pbxproj +++ b/Xcode/TLVCoding.xcodeproj/project.pbxproj @@ -36,6 +36,10 @@ 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 */; }; 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 */; }; @@ -80,6 +84,7 @@ 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 = ""; }; 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 = ""; }; @@ -183,6 +188,7 @@ 6E8F3AFB2051DA85006964D5 /* Encoder.swift */, 6E8F3B002051DA8D006964D5 /* Decoder.swift */, 6E8FE7612288824000CBCFA9 /* Item.swift */, + 6E8FE7662288920600CBCFA9 /* TypeCode.swift */, 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */, 6E8FE7572288811800CBCFA9 /* Data.swift */, 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */, @@ -404,11 +410,11 @@ }; 52D6DA0E1BF000BD002C0205 = { CreatedOnToolsVersion = 7.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; DD7502791C68FCFC006590AF = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; DD75028C1C690C7A006590AF = { CreatedOnToolsVersion = 7.2.1; @@ -503,6 +509,7 @@ 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; @@ -525,6 +532,7 @@ 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; @@ -539,6 +547,7 @@ 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; @@ -553,6 +562,7 @@ 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; From 81f603c885ca1fe7a4b0256eecf1000d1dd85b7e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 20:51:13 -0500 Subject: [PATCH 03/33] Updated unit tests --- Sources/CodingKey.swift | 23 +++++- Tests/TLVCodingTests/TLVCodingTests.swift | 89 +++++++++++++++++++++-- 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 45865b2..93ebeda 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -25,7 +25,28 @@ public extension TLVCodingKey { } var intValue: Int? { - return Int(code.rawValue) } } + +public extension TLVCodingKey where Self: RawRepresentable, RawValue == TLVTypeCode.RawValue { + + init?(code: TLVTypeCode) { + self.init(rawValue: code.rawValue) + } + + var code: TLVTypeCode { + return TLVTypeCode(rawValue: rawValue) + } +} + +public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, RawValue == TLVTypeCode.RawValue { + + init?(stringValue: String) { + + guard let value = Self.allCases.first(where: { $0.stringValue == stringValue }) + else { return nil } + + self = value + } +} diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 39bc2a3..7e39ff3 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -18,13 +18,40 @@ final class TLVCodingTests: XCTestCase { func testEncode() { - let data = Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110]) - let value = Person(gender: .male, name: "Coleman") + func encode (_ value: T, _ data: Data) { + + let encoder = TLVEncoder() + + do { + + let encodedData = try encoder.encode(value) + XCTAssertEqual(encodedData, data) + + } catch { + dump(error) + XCTFail("Could not encode \(value)") + } + } - let encoder = TLVEncoder() - var encodedData = Data() - XCTAssertNoThrow(encodedData = try encoder.encode(value)) - XCTAssertEqual(encodedData, data) + encode(Person(gender: .male, name: "Coleman"), + Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110])) + + encode(ProvisioningState(state: .idle, result: .notAvailible), + Data([0x01, 0x01, 0x00, 0x02, 0x01, 0x00])) + + encode(ProvisioningState(state: .provisioning, result: .notAvailible), + Data([0x01, 0x01, 0x01, 0x02, 0x01, 0x00])) + } + + func testCodingKeys() { + + typealias CodingKeys = ProvisioningState.CodingKeys + + for codingKey in ProvisioningState.CodingKeys.allCases { + + XCTAssertEqual(CodingKeys(rawValue: codingKey.rawValue), codingKey) + XCTAssertEqual(CodingKeys(stringValue: codingKey.stringValue), codingKey) + } } } @@ -37,7 +64,7 @@ public enum Gender: UInt8, Codable { } public struct Person: Codable, Equatable, Hashable { - + public var gender: Gender public var name: String @@ -48,3 +75,51 @@ public struct Person: Codable, Equatable, Hashable { self.name = name } } + +public struct ProvisioningState: Codable, Equatable, Hashable { + + public var state: State + + public var result: Result +} + +internal extension ProvisioningState { + + enum CodingKeys: UInt8, TLVCodingKey, CaseIterable { + + case state = 0x01 + case result = 0x02 + + var stringValue: String { + switch self { + case .state: return "state" + case .result: return "result" + } + } + } + +} + +public extension ProvisioningState { + + enum State: UInt8, Codable { + + case idle = 0x00 + case provisioning = 0x01 + case provisioned = 0x02 + case declined = 0x03 + } + + enum Result: UInt8, Codable { + + case notAvailible = 0x00 + case success = 0x01 + case invalidConfiguration = 0x02 + case networkOutOfRange = 0x03 + case invalidKey = 0x04 + case otherError = 0x05 + case connectFailed = 0x06 + case connectTimeout = 0x07 + case insufficientNetwork = 0x08 + } +} From 621411636f6033a363b5db510f1ed1fa679093b2 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 23:08:39 -0500 Subject: [PATCH 04/33] Working on Encoder --- Sources/CodingKey.swift | 8 ++ Sources/Encoder.swift | 200 +++++++++++++++++++++++---------------- Sources/TLVCodable.swift | 11 ++- 3 files changed, 133 insertions(+), 86 deletions(-) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 93ebeda..1195483 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -50,3 +50,11 @@ public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, self = value } } + +internal extension Sequence where Element == CodingKey { + + /// KVC path string for current coding path. + var path: String { + return reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }) + } +} diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 894bcd3..60242c8 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -8,6 +8,7 @@ import Foundation +/// TLV8 Encoder public struct TLVEncoder { // MARK: - Properties @@ -18,6 +19,9 @@ public struct TLVEncoder { /// Logger handler public var log: ((String) -> ())? + /// Format for numeric values. + public var numericFormat: TLVNumericFormat = .littleEndian + // MARK: - Initialization public init() { } @@ -26,7 +30,8 @@ public struct TLVEncoder { public func encode (_ value: T) throws -> Data { - let encoder = Encoder(userInfo: userInfo, log: log) + 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) @@ -37,27 +42,12 @@ public struct TLVEncoder { return container.data } -} - -public extension TLVEncoder { - - func encode(_ items: [TLVItem]) -> Data { - return Data(items) - } - - func encode(_ items: TLVItem...) -> Data { - return Data(items) - } -} - -@available(*, deprecated, message: "Use TLVEncoder instance instead") -public extension TLVEncoder { - static func encode(_ items: [TLVItem]) -> Data { + public func encode(_ items: [TLVItem]) -> Data { return Data(items) } - static func encode(_ items: TLVItem...) -> Data { + public func encode(_ items: TLVItem...) -> Data { return Data(items) } } @@ -77,25 +67,29 @@ internal extension TLVEncoder { /// Logger let log: ((String) -> ())? + let options: Options + private(set) var stack: Stack // MARK: - Initialization init(codingPath: [CodingKey] = [], userInfo: [CodingUserInfoKey : Any], - log: ((String) -> ())?) { + 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) for path \"\(codingPathString)\"") + log?("Requested container keyed by \(type) for path \"\(codingPath.path)\"") let stackContainer = ItemsContainer() self.stack.push(.items(stackContainer)) @@ -107,7 +101,7 @@ internal extension TLVEncoder { func unkeyedContainer() -> UnkeyedEncodingContainer { - log?("Requested unkeyed container for path \"\(codingPathString)\"") + log?("Requested unkeyed container for path \"\(codingPath.path)\"") let stackContainer = ItemContainer() self.stack.push(.item(stackContainer)) @@ -117,7 +111,7 @@ internal extension TLVEncoder { func singleValueContainer() -> SingleValueEncodingContainer { - log?("Requested single value container for path \"\(codingPathString)\"") + log?("Requested single value container for path \"\(codingPath.path)\"") let stackContainer = ItemContainer() self.stack.push(.item(stackContainer)) @@ -129,13 +123,15 @@ internal extension TLVEncoder { internal extension TLVEncoder.Encoder { - /// KVC path string for current coding path. - var codingPathString: String { + struct Options { - return codingPath.reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }) + let numericFormat: TLVNumericFormat } +} + +internal extension TLVEncoder.Encoder { - func typeCode (for key: Key, value: Any) throws -> TLVTypeCode { + func typeCode (for key: Key, value: T) throws -> TLVTypeCode { if let tlvCodingKey = key as? TLVCodingKey { @@ -151,7 +147,8 @@ internal extension TLVEncoder.Encoder { return TLVTypeCode(rawValue: UInt8(intValue)) - } else if MemoryLayout.size == MemoryLayout.size { + } else if MemoryLayout.size == MemoryLayout.size, + Mirror(reflecting: Key.self).displayStyle == .enum { return TLVTypeCode(rawValue: unsafeBitCast(key, to: UInt8.self)) @@ -169,6 +166,19 @@ internal extension TLVEncoder.Encoder { return value.tlvData } + @inline(__always) + func boxNumeric (_ value: T) -> Data { + + let endianValue: T + switch options.numericFormat { + case .bigEndian: + endianValue = value.bigEndian + case .littleEndian: + endianValue = value.littleEndian + } + return endianValue.tlvData + } + func boxEncodable (_ value: T) throws -> Data { if let tlvEncodable = value as? TLVEncodable { @@ -269,7 +279,7 @@ internal extension TLVEncoder.Encoder { // MARK: - KeyedEncodingContainerProtocol -final class TLVKeyedContainer : KeyedEncodingContainerProtocol { +internal final class TLVKeyedContainer : KeyedEncodingContainerProtocol { typealias Key = K @@ -300,33 +310,61 @@ final class TLVKeyedContainer : KeyedEncodingContainerProtocol { // do nothing } - func encode(_ value: Bool, forKey key: K) throws { try _encode(value, forKey: key) } + func encode(_ value: Bool, forKey key: K) throws { + try encodeTLV(value, forKey: key) + } - func encode(_ value: Int, forKey key: K) throws { try _encode(Int32(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 _encode(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 _encode(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 _encode(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 _encode(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 _encode(UInt32(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 _encode(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 _encode(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 _encode(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 _encode(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 _encode(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 _encode(value, 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 _encode(value, forKey: key) } + func encode(_ value: String, forKey key: K) throws { + try encodeTLV(value, forKey: key) + } func encode (_ value: T, forKey key: K) throws { @@ -358,7 +396,15 @@ final class TLVKeyedContainer : KeyedEncodingContainerProtocol { // MARK: - Private Methods - private func _encode (_ value: T, forKey key: K) throws { + 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() } @@ -366,9 +412,9 @@ final class TLVKeyedContainer : KeyedEncodingContainerProtocol { try setValue(value, data: data, for: key) } - private func setValue(_ value: Any, data: Data, for key: Key) throws { + private func setValue (_ value: T, data: Data, for key: Key) throws { - encoder.log?("Will encode value for key \(key.stringValue) at path \"\(encoder.codingPathString)\"") + 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) @@ -378,7 +424,7 @@ final class TLVKeyedContainer : KeyedEncodingContainerProtocol { // MARK: - SingleValueEncodingContainer -final class TLVSingleValueEncodingContainer: SingleValueEncodingContainer { +internal final class TLVSingleValueEncodingContainer: SingleValueEncodingContainer { // MARK: - Properties @@ -414,29 +460,29 @@ final class TLVSingleValueEncodingContainer: SingleValueEncodingContainer { func encode(_ value: String) throws { write(encoder.box(value)) } - func encode(_ value: Double) throws { write(encoder.box(value)) } + func encode(_ value: Double) throws { write(encoder.boxNumeric(value.bitPattern)) } - func encode(_ value: Float) throws { write(encoder.box(value)) } + func encode(_ value: Float) throws { write(encoder.boxNumeric(value.bitPattern)) } - func encode(_ value: Int) throws { write(encoder.box(Int32(value))) } + 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.box(value)) } + func encode(_ value: Int16) throws { write(encoder.boxNumeric(value)) } - func encode(_ value: Int32) throws { write(encoder.box(value)) } + func encode(_ value: Int32) throws { write(encoder.boxNumeric(value)) } - func encode(_ value: Int64) throws { write(encoder.box(value)) } + func encode(_ value: Int64) throws { write(encoder.boxNumeric(value)) } - func encode(_ value: UInt) throws { write(encoder.box(UInt32(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.box(value)) } + func encode(_ value: UInt16) throws { write(encoder.boxNumeric(value)) } - func encode(_ value: UInt32) throws { write(encoder.box(value)) } + func encode(_ value: UInt32) throws { write(encoder.boxNumeric(value)) } - func encode(_ value: UInt64) throws { write(encoder.box(value)) } + func encode(_ value: UInt64) throws { write(encoder.boxNumeric(value)) } func encode (_ value: T) throws { write(try encoder.boxEncodable(value)) } @@ -452,7 +498,7 @@ final class TLVSingleValueEncodingContainer: SingleValueEncodingContainer { // MARK: - UnkeyedEncodingContainer -final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { +internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { // MARK: - Properties @@ -488,29 +534,29 @@ final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { func encode(_ value: String) throws { append(encoder.box(value)) } - func encode(_ value: Double) throws { append(encoder.box(value)) } + func encode(_ value: Double) throws { append(encoder.boxNumeric(value.bitPattern)) } - func encode(_ value: Float) throws { append(encoder.box(value)) } + func encode(_ value: Float) throws { append(encoder.boxNumeric(value.bitPattern)) } - func encode(_ value: Int) throws { append(encoder.box(Int32(value))) } + 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.box(value)) } + func encode(_ value: Int16) throws { append(encoder.boxNumeric(value)) } - func encode(_ value: Int32) throws { append(encoder.box(value)) } + func encode(_ value: Int32) throws { append(encoder.boxNumeric(value)) } - func encode(_ value: Int64) throws { append(encoder.box(value)) } + func encode(_ value: Int64) throws { append(encoder.boxNumeric(value)) } - func encode(_ value: UInt) throws { append(encoder.box(UInt32(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.box(value)) } + func encode(_ value: UInt16) throws { append(encoder.boxNumeric(value)) } - func encode(_ value: UInt32) throws { append(encoder.box(value)) } + func encode(_ value: UInt32) throws { append(encoder.boxNumeric(value)) } - func encode(_ value: UInt64) throws { append(encoder.box(value)) } + func encode(_ value: UInt64) throws { append(encoder.boxNumeric(value)) } func encode (_ value: T) throws { append(try encoder.boxEncodable(value)) } @@ -543,16 +589,13 @@ final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { private extension TLVEncodable { var copyingBytes: Data { - - var copy = self - return withUnsafePointer(to: ©, { Data(bytes: $0, count: MemoryLayout.size) }) + return withUnsafePointer(to: self, { Data(bytes: $0, count: MemoryLayout.size) }) } } extension UInt8: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -560,7 +603,6 @@ extension UInt8: TLVEncodable { extension UInt16: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -568,7 +610,6 @@ extension UInt16: TLVEncodable { extension UInt32: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -576,7 +617,6 @@ extension UInt32: TLVEncodable { extension UInt64: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -584,7 +624,6 @@ extension UInt64: TLVEncodable { extension Int8: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -592,7 +631,6 @@ extension Int8: TLVEncodable { extension Int16: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -600,7 +638,6 @@ extension Int16: TLVEncodable { extension Int32: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -608,7 +645,6 @@ extension Int32: TLVEncodable { extension Int64: TLVEncodable { public var tlvData: Data { - return copyingBytes } } @@ -616,23 +652,20 @@ extension Int64: TLVEncodable { extension Float: TLVEncodable { public var tlvData: Data { - - return copyingBytes + return bitPattern.copyingBytes } } extension Double: TLVEncodable { public var tlvData: Data { - - return copyingBytes + return bitPattern.copyingBytes } } extension Bool: TLVEncodable { public var tlvData: Data { - return UInt8(self ? 1 : 0).copyingBytes } } @@ -640,7 +673,6 @@ extension Bool: TLVEncodable { extension String: TLVEncodable { public var tlvData: Data { - return Data(self.utf8) } } diff --git a/Sources/TLVCodable.swift b/Sources/TLVCodable.swift index b3c22b4..006c795 100644 --- a/Sources/TLVCodable.swift +++ b/Sources/TLVCodable.swift @@ -14,10 +14,17 @@ public typealias TLVCodable = TLVEncodable & TLVDecodable /// TLV Decodable type public protocol TLVDecodable { - init?(tlvData: Foundation.Data) + init?(tlvData: Data) } public protocol TLVEncodable { - var tlvData: Foundation.Data { get } + var tlvData: Data { get } +} + +/// TLV Numeric Encoding Format +public enum TLVNumericFormat { + + case bigEndian + case littleEndian } From 26ee0ed85a0310a9b08f2898faab9e39bd7ea876 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 23:08:55 -0500 Subject: [PATCH 05/33] Added Integer extensions --- Sources/Integer.swift | 97 +++++++++++++++++++++++ Xcode/TLVCoding.xcodeproj/project.pbxproj | 10 +++ 2 files changed, 107 insertions(+) create mode 100644 Sources/Integer.swift 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/Xcode/TLVCoding.xcodeproj/project.pbxproj b/Xcode/TLVCoding.xcodeproj/project.pbxproj index 13025e7..60656cf 100644 --- a/Xcode/TLVCoding.xcodeproj/project.pbxproj +++ b/Xcode/TLVCoding.xcodeproj/project.pbxproj @@ -40,6 +40,10 @@ 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 */; }; 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 */; }; @@ -85,6 +89,7 @@ 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 = ""; }; 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 = ""; }; @@ -192,6 +197,7 @@ 6E8FE75222887B2B00CBCFA9 /* CodingKey.swift */, 6E8FE7572288811800CBCFA9 /* Data.swift */, 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */, + 6E8FE76F22890C8800CBCFA9 /* Integer.swift */, ); name = Sources; path = ../Sources; @@ -506,6 +512,7 @@ 6E8F3AFC2051DA85006964D5 /* Encoder.swift in Sources */, 6E8FE7582288811800CBCFA9 /* Data.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 */, @@ -529,6 +536,7 @@ 6E8F3AFE2051DA85006964D5 /* Encoder.swift in Sources */, 6E8FE75A2288811800CBCFA9 /* Data.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 */, @@ -544,6 +552,7 @@ 6E8F3AFF2051DA85006964D5 /* Encoder.swift in Sources */, 6E8FE75B2288811800CBCFA9 /* Data.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 */, @@ -559,6 +568,7 @@ 6E8F3AFD2051DA85006964D5 /* Encoder.swift in Sources */, 6E8FE7592288811800CBCFA9 /* Data.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 */, From a78db0a24a4ecc2f8f6cab3f7956ee3f537da723 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 23:34:56 -0500 Subject: [PATCH 06/33] Working on Decoder --- Sources/CodingKey.swift | 27 ++ Sources/Decoder.swift | 815 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 826 insertions(+), 16 deletions(-) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 1195483..1748078 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -51,6 +51,33 @@ public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, } } +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 { + + self.init(rawValue: unsafeBitCast(codingKey, to: UInt8.self)) + + } else { + + return nil + } + } +} + internal extension Sequence where Element == CodingKey { /// KVC path string for current coding path. diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index 815edc9..364829c 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -8,15 +8,47 @@ import Foundation +/// TLV8 Decoder public struct TLVDecoder { + // MARK: - Properties -} - - -internal extension TLVDecoder { + /// 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 { + + let items = try decode(data) + + 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: []) + } - static func decode(_ data: Data) throws -> [TLVItem] { + internal static func decode(_ data: Data, codingPath: [CodingKey]) throws -> [TLVItem] { var offset = 0 var items = [TLVItem]() @@ -24,8 +56,10 @@ internal extension TLVDecoder { 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 @@ -50,20 +84,769 @@ internal extension TLVDecoder { } } -// MARK: - Supporting Types +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) 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 + guard let items = try? TLVDecoder.decode(item.value, codingPath: codingPath) else { + + throw DecodingError.typeMismatch(UnkeyedDecodingContainer.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot get unkeyed decoding container, invalid top container \(container).")) + } + + // replace stack + self.stack.pop() + self.stack.push(.items(items)) + + return TLVUnkeyedDecodingContainer(referencing: self, wrapping: items) + } + } + + 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: - 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 + } + + /// 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: - Stack + +private extension TLVDecoder { + + 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) + } + + @discardableResult + mutating func pop() -> Container { + + guard let container = containers.popLast() + else { fatalError("Empty container stack.") } + + return container + } + } +} + +fileprivate extension TLVDecoder.Stack { + + enum Container { + + case items([TLVItem]) + case item(TLVItem) + } +} + + +// MARK: - KeyedDecodingContainer + +internal struct TLVKeyedDecodingContainer : KeyedDecodingContainerProtocol { + + typealias Key = K + + // MARK: Properties + + /// A reference to the encoder we're reading from. + private let decoder: TLVDecoder.Decoder + + /// A reference to the container we're reading from. + private let container: [TLVItem] + + /// The path of coding keys taken to get to this point in decoding. + public let codingPath: [CodingKey] + + /// All the keys the Decoder has for this container. + public let allKeys: [Key] + + // MARK: Initialization + + /// Initializes `self` by referencing the given decoder and container. + fileprivate 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 { + + // log + self.decoder.log?("Check whether key \"\(key.stringValue)\" exists") + + // check schema / model contains property + // FIXME: Remove unneccesary check as optimization + guard allKeys.contains(where: { $0.stringValue == key.stringValue }) + else { return false } + + // return whether value exists for key + guard let typeCode = TLVTypeCode(codingKey: 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 _decode(type, forKey: key) + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + + let value = try _decode(Int32.self, forKey: key) + + return Int(value) + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + + let value = try _decode(UInt32.self, forKey: key) + + return UInt(value) + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + + return try _decode(type, forKey: key) + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + + //return try _decode(type, forKey: key) + fatalError() + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + + //return try _decode(type, forKey: key) + fatalError() + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + + return try _decode(type, forKey: key) + } + + func decode (_ type: T.Type, forKey key: Key) throws -> T { + + 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.")) + } + + let value = try self.decoder.unboxDecodable(item, as: type) + + return value + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + + fatalError() + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + + fatalError() + } + + func superDecoder() throws -> Decoder { + + fatalError() + } + + func superDecoder(forKey key: Key) throws -> Decoder { + + fatalError() + } + + // MARK: Private Methods + + /// Decode native value type from TLV data. + private func _decode (_ type: T.Type, forKey key: Key) throws -> T { + + 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.")) + } + + let data = item.value + let value = try decoder.unbox(data, as: type) + return value + } + + /// Access actual value + private func value(for key: Key) throws -> TLVItem? { + + // log + decoder.log?("Will read value for key \(key.stringValue) at path \"\(decoder.codingPath.path)\"") + + // get value + return container.first { $0.type == TLVTypeCode(codingKey: key) } + } +} + +// MARK: - SingleValueDecodingContainer -public extension TLVDecoder { +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) { + + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + } + + // MARK: SingleValueDecodingContainer + + func decodeNil() -> Bool { + + return container.value.isEmpty + } + + 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.unbox(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.unbox(container.value, as: type) + } + + func decode(_ type: Int32.Type) throws -> Int32 { + + return try self.decoder.unbox(container.value, as: type) + } - struct DecodingContext { + func decode(_ type: Int64.Type) throws -> Int64 { - public let offset: Int + return try self.decoder.unbox(container.value, as: type) } - enum DecodingError: Swift.Error { + func decode(_ type: UInt.Type) throws -> UInt { + + let value = try self.decoder.unbox(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.unbox(container.value, as: type) + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + + return try self.decoder.unbox(container.value, as: type) + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + + return try self.decoder.unbox(container.value, as: type) + } + + func decode(_ type: Float.Type) throws -> Float { + + fatalError() + } + + func decode(_ type: Double.Type) throws -> Double { + + fatalError() + } + + 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 + } + + 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.")) + } + } +} + +fileprivate extension TLVUnkeyedDecodingContainer { + + struct Index: CodingKey { + + public let index: Int + + public init(intValue: Int) { + + self.index = intValue + } + + 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) { + + guard data.count == MemoryLayout.size + else { return nil } - case invalidSize(Int, context: DecodingContext) - case invalidType(UInt8, context: DecodingContext) - case invalidValue(Data, context: DecodingContext) - case decodableMismatch([TLVDecodable]) + self.init(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7])) } } From 4e0d33832dc5492d88442af5770bf2764b178507 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 23:43:35 -0500 Subject: [PATCH 07/33] Fixed encoder --- Sources/Encoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 60242c8..e17388f 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -148,7 +148,7 @@ internal extension TLVEncoder.Encoder { return TLVTypeCode(rawValue: UInt8(intValue)) } else if MemoryLayout.size == MemoryLayout.size, - Mirror(reflecting: Key.self).displayStyle == .enum { + Mirror(reflecting: key).displayStyle == .enum { return TLVTypeCode(rawValue: unsafeBitCast(key, to: UInt8.self)) From 7a92fc7cd2841ade7ba185527aea27b1116e845c Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 12 May 2019 23:44:26 -0500 Subject: [PATCH 08/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 30 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 7e39ff3..9fa659b 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -14,19 +14,17 @@ final class TLVCodingTests: XCTestCase { static var allTests = [ ("testEncode", testEncode), - ] + ("testCodingKeys", testCodingKeys) + ] func testEncode() { func encode (_ value: T, _ data: Data) { let encoder = TLVEncoder() - do { - let encodedData = try encoder.encode(value) XCTAssertEqual(encodedData, data) - } catch { dump(error) XCTFail("Could not encode \(value)") @@ -43,6 +41,30 @@ final class TLVCodingTests: XCTestCase { Data([0x01, 0x01, 0x01, 0x02, 0x01, 0x00])) } + func testDecode() { + + func decode (_ value: T, _ data: Data) { + + let decoder = TLVDecoder() + do { + let decodedValue = try decoder.decode(T.self, from: data) + XCTAssertEqual(decodedValue, value) + } catch { + dump(error) + XCTFail("Could not decode \(value)") + } + } + + decode(Person(gender: .male, name: "Coleman"), + Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110])) + + decode(ProvisioningState(state: .idle, result: .notAvailible), + Data([0x01, 0x01, 0x00, 0x02, 0x01, 0x00])) + + decode(ProvisioningState(state: .provisioning, result: .notAvailible), + Data([0x01, 0x01, 0x01, 0x02, 0x01, 0x00])) + } + func testCodingKeys() { typealias CodingKeys = ProvisioningState.CodingKeys From b78e1743e48fd8c0c5174b45b55d342e0fb27e0a Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 00:16:55 -0500 Subject: [PATCH 09/33] Working on Decoder --- Sources/Decoder.swift | 10 +-- Tests/TLVCodingTests/TLVCodingTests.swift | 93 ++++++++++++++--------- 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index 364829c..d89cca3 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -395,14 +395,14 @@ internal struct TLVKeyedDecodingContainer : KeyedDecodingContain func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - //return try _decode(type, forKey: key) - fatalError() + let bitPattern = try _decode(UInt32.self, forKey: key) + return Float(bitPattern: bitPattern) } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - //return try _decode(type, forKey: key) - fatalError() + let bitPattern = try _decode(UInt64.self, forKey: key) + return Double(bitPattern: bitPattern) } func decode(_ type: String.Type, forKey key: Key) throws -> String { @@ -844,7 +844,7 @@ extension Int64: TLVDecodable { public init?(tlvData data: Data) { - guard data.count == MemoryLayout.size + 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])) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 9fa659b..d330137 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -13,37 +13,22 @@ import XCTest final class TLVCodingTests: XCTestCase { static var allTests = [ - ("testEncode", testEncode), + ("testCodable", testCodable), ("testCodingKeys", testCodingKeys) ] - func testEncode() { + func testCodable() { - func encode (_ value: T, _ data: Data) { + func test (_ value: T, _ data: Data) { let encoder = TLVEncoder() do { let encodedData = try encoder.encode(value) - XCTAssertEqual(encodedData, data) + XCTAssertEqual(encodedData, data, "Invalid data \(Array(encodedData))") } catch { dump(error) XCTFail("Could not encode \(value)") } - } - - encode(Person(gender: .male, name: "Coleman"), - Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110])) - - encode(ProvisioningState(state: .idle, result: .notAvailible), - Data([0x01, 0x01, 0x00, 0x02, 0x01, 0x00])) - - encode(ProvisioningState(state: .provisioning, result: .notAvailible), - Data([0x01, 0x01, 0x01, 0x02, 0x01, 0x00])) - } - - func testDecode() { - - func decode (_ value: T, _ data: Data) { let decoder = TLVDecoder() do { @@ -55,14 +40,30 @@ final class TLVCodingTests: XCTestCase { } } - decode(Person(gender: .male, name: "Coleman"), - Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110])) + 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])) - decode(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])) - decode(ProvisioningState(state: .provisioning, result: .notAvailible), - Data([0x01, 0x01, 0x01, 0x02, 0x01, 0x00])) + 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])) } func testCodingKeys() { @@ -79,29 +80,21 @@ final class TLVCodingTests: XCTestCase { // MARK: - Supporting Types -public enum Gender: UInt8, Codable { - - case male - case female -} - public struct Person: Codable, Equatable, Hashable { public var gender: Gender - public var name: String +} + +public enum Gender: UInt8, Codable { - public init(gender: Gender, name: String) { - - self.gender = gender - self.name = name - } + case male + case female } public struct ProvisioningState: Codable, Equatable, Hashable { public var state: State - public var result: Result } @@ -145,3 +138,27 @@ public extension ProvisioningState { case insufficientNetwork = 0x08 } } + +public struct Profile: Codable, Equatable, Hashable { + + 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 +} From 5f6fda7e4ca6c1d0994b83069e22ca09e13e4f41 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 00:18:25 -0500 Subject: [PATCH 10/33] Updated `TLVNumericFormat` --- Sources/NumericFormat.swift | 14 ++++++++++++++ Sources/TLVCodable.swift | 7 ------- Xcode/TLVCoding.xcodeproj/project.pbxproj | 10 ++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 Sources/NumericFormat.swift 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 006c795..6a9d1ce 100644 --- a/Sources/TLVCodable.swift +++ b/Sources/TLVCodable.swift @@ -21,10 +21,3 @@ public protocol TLVEncodable { var tlvData: Data { get } } - -/// TLV Numeric Encoding Format -public enum TLVNumericFormat { - - case bigEndian - case littleEndian -} diff --git a/Xcode/TLVCoding.xcodeproj/project.pbxproj b/Xcode/TLVCoding.xcodeproj/project.pbxproj index 60656cf..69550bf 100644 --- a/Xcode/TLVCoding.xcodeproj/project.pbxproj +++ b/Xcode/TLVCoding.xcodeproj/project.pbxproj @@ -44,6 +44,10 @@ 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 */; }; @@ -90,6 +94,7 @@ 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 = ""; }; @@ -198,6 +203,7 @@ 6E8FE7572288811800CBCFA9 /* Data.swift */, 6E8FE75C228881DF00CBCFA9 /* DataConvertible.swift */, 6E8FE76F22890C8800CBCFA9 /* Integer.swift */, + 6E8FE7742289356200CBCFA9 /* NumericFormat.swift */, ); name = Sources; path = ../Sources; @@ -511,6 +517,7 @@ 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 */, @@ -535,6 +542,7 @@ 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 */, @@ -551,6 +559,7 @@ 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 */, @@ -567,6 +576,7 @@ 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 */, From 7523d758c36bcac72ff54562805752b1d1bf89bf Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 00:41:45 -0500 Subject: [PATCH 11/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index d330137..06a1f80 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -21,7 +21,8 @@ final class TLVCodingTests: XCTestCase { func test (_ value: T, _ data: Data) { - let encoder = TLVEncoder() + var encoder = TLVEncoder() + encoder.log = { print("Encoder:", $0) } do { let encodedData = try encoder.encode(value) XCTAssertEqual(encodedData, data, "Invalid data \(Array(encodedData))") @@ -30,7 +31,8 @@ final class TLVCodingTests: XCTestCase { XCTFail("Could not encode \(value)") } - let decoder = TLVDecoder() + var decoder = TLVDecoder() + decoder.log = { print("Decoder:", $0) } do { let decodedValue = try decoder.decode(T.self, from: data) XCTAssertEqual(decodedValue, value) From 24731b7274d8d947e2d1dae95b70296cd7ce806d Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 00:42:22 -0500 Subject: [PATCH 12/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 29 ++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 06a1f80..2614c96 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -66,6 +66,33 @@ final class TLVCodingTests: XCTestCase { 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])) + + 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: [ + // 1: "One", + // 2: "Two", + // 3: "🏎" + // ] + ), + Data([0, 12, 0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110, 1, 29, 0, 1, 1, 1, 4, 71, 105, 110, 97, 0, 1, 1, 1, 5, 74, 111, 115, 115, 121, 0, 1, 0, 1, 5, 74, 111, 114, 103, 101]) + ) } func testCodingKeys() { @@ -145,7 +172,7 @@ public struct Profile: Codable, Equatable, Hashable { public var person: Person public var friends: [Person] - public var userInfo: [UInt: String] + //public var userInfo: [UInt: String] } public struct Numeric: Codable, Equatable, Hashable { From 2092b4f24cdaefaf4ce489d71bae0e1ae7abb329 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 01:05:12 -0500 Subject: [PATCH 13/33] Fixed encoding arrays --- Sources/Encoder.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index e17388f..8f564c7 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -579,7 +579,11 @@ internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { private func append(_ data: Data) { - self.container.data += data + let index = TLVTypeCode(rawValue: UInt8(count)) // current index + let item = TLVItem(type: index, value: data) + + // write + self.container.data += item.data self.count += 1 } } From 79fbb408fc928d36070e3a5ddbb0346ef05fed99 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 01:07:23 -0500 Subject: [PATCH 14/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 2614c96..870cda7 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -67,6 +67,21 @@ final class TLVCodingTests: XCTestCase { 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])) + test( + Profile( + person: Person( + gender: .male, + name: "Coleman" + ), friends: [ + Person( + gender: .male, + name: "Coleman" + ) + ]), + 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]) + ) + test( Profile( person: Person( @@ -91,7 +106,11 @@ final class TLVCodingTests: XCTestCase { // 3: "🏎" // ] ), - Data([0, 12, 0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110, 1, 29, 0, 1, 1, 1, 4, 71, 105, 110, 97, 0, 1, 1, 1, 5, 74, 111, 115, 115, 121, 0, 1, 0, 1, 5, 74, 111, 114, 103, 101]) + 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]) ) } From c97d9730969270d6b6df78ae83cdbb8a746176d3 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 01:17:04 -0500 Subject: [PATCH 15/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 39 ++++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 870cda7..8ae7848 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -77,7 +77,9 @@ final class TLVCodingTests: XCTestCase { 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]) ) @@ -99,18 +101,31 @@ final class TLVCodingTests: XCTestCase { gender: .male, name: "Jorge" ) - ] - //userInfo: [ - // 1: "One", - // 2: "Two", - // 3: "🏎" - // ] + ], + userInfo: nil ), - Data([0, 12, 0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110, + 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]) + 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 + ]) ) } @@ -191,7 +206,7 @@ public struct Profile: Codable, Equatable, Hashable { public var person: Person public var friends: [Person] - //public var userInfo: [UInt: String] + public var userInfo: [UInt: String]? } public struct Numeric: Codable, Equatable, Hashable { From a90d00cef1b000847a076f53a4302680bfa63ae3 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 01:43:45 -0500 Subject: [PATCH 16/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 8ae7848..cafc73a 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -127,6 +127,14 @@ final class TLVCodingTests: XCTestCase { 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]) + ) } func testCodingKeys() { @@ -225,3 +233,33 @@ public struct Numeric: Codable, Equatable, Hashable { 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 { + + case zero + case one + case two + case three + + public init?(tlvData: Data) { + + guard let rawValue = UInt16(tlvData: tlvData)?.littleEndian + else { return nil } + + self.init(rawValue: rawValue) + } + + public var tlvData: Data { + + return rawValue.littleEndian.tlvData + } + } +} From 6bc8dc31cc90e057eae0ed7224f8d2319b93e194 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 01:50:54 -0500 Subject: [PATCH 17/33] Fixed compiler error --- Sources/Encoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 8f564c7..827afbb 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -583,7 +583,7 @@ internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { let item = TLVItem(type: index, value: data) // write - self.container.data += item.data + self.container.data.append(item.data) self.count += 1 } } From df7969807bb22d7d59786a355401dce71f3e1d60 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 14:16:52 -0500 Subject: [PATCH 18/33] Optimized Unkeyed Encoding Container Data buffer is allocated once, instead of resizing --- Sources/Encoder.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 827afbb..4ed3d0d 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -103,8 +103,8 @@ internal extension TLVEncoder { log?("Requested unkeyed container for path \"\(codingPath.path)\"") - let stackContainer = ItemContainer() - self.stack.push(.item(stackContainer)) + let stackContainer = ItemsContainer() + self.stack.push(.items(stackContainer)) return TLVUnkeyedEncodingContainer(referencing: self, wrapping: stackContainer) } @@ -509,12 +509,12 @@ internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { let codingPath: [CodingKey] /// A reference to the container we're writing to. - let container: TLVEncoder.Encoder.ItemContainer + let container: TLVEncoder.Encoder.ItemsContainer // MARK: - Initialization init(referencing encoder: TLVEncoder.Encoder, - wrapping container: TLVEncoder.Encoder.ItemContainer) { + wrapping container: TLVEncoder.Encoder.ItemsContainer) { self.encoder = encoder self.codingPath = encoder.codingPath @@ -524,7 +524,9 @@ internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { // MARK: - Methods /// The number of elements encoded into the container. - private(set) var count: Int = 0 + var count: Int { + return container.items.count + } func encodeNil() throws { // do nothing @@ -583,8 +585,7 @@ internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { let item = TLVItem(type: index, value: data) // write - self.container.data.append(item.data) - self.count += 1 + self.container.items.append(item) } } From 4f3cdb14fedb5e801158de855c02bf8b48770e4f Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 14:58:00 -0500 Subject: [PATCH 19/33] Fixed Swift 4 compilation --- Sources/CodingKey.swift | 4 +++- Sources/Encoder.swift | 5 +++++ Tests/TLVCodingTests/TLVCodingTests.swift | 12 ++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 1748078..e9e5a82 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -29,7 +29,7 @@ public extension TLVCodingKey { } } -public extension TLVCodingKey where Self: RawRepresentable, RawValue == TLVTypeCode.RawValue { +public extension TLVCodingKey where Self: RawRepresentable, Self.RawValue == TLVTypeCode.RawValue { init?(code: TLVTypeCode) { self.init(rawValue: code.rawValue) @@ -40,6 +40,7 @@ public extension TLVCodingKey where Self: RawRepresentable, RawValue == TLVTypeC } } +#if swift(>=4.2) public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, RawValue == TLVTypeCode.RawValue { init?(stringValue: String) { @@ -50,6 +51,7 @@ public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, self = value } } +#endif internal extension TLVTypeCode { diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 4ed3d0d..c332114 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -594,7 +594,12 @@ internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { 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 } } diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index cafc73a..dfa7d1a 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -14,7 +14,7 @@ final class TLVCodingTests: XCTestCase { static var allTests = [ ("testCodable", testCodable), - ("testCodingKeys", testCodingKeys) + ("testCodingKeys", testCodingKeys), ] func testCodable() { @@ -45,11 +45,13 @@ final class TLVCodingTests: XCTestCase { test(Person(gender: .male, name: "Coleman"), Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110])) + #if swift(>=4.2) 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])) + #endif test(Numeric( boolean: true, @@ -139,6 +141,7 @@ final class TLVCodingTests: XCTestCase { func testCodingKeys() { + #if swift(>=4.2) typealias CodingKeys = ProvisioningState.CodingKeys for codingKey in ProvisioningState.CodingKeys.allCases { @@ -146,6 +149,9 @@ final class TLVCodingTests: XCTestCase { XCTAssertEqual(CodingKeys(rawValue: codingKey.rawValue), codingKey) XCTAssertEqual(CodingKeys(stringValue: codingKey.stringValue), codingKey) } + #endif + } + } } @@ -163,6 +169,7 @@ public enum Gender: UInt8, Codable { case female } +#if swift(>=4.2) public struct ProvisioningState: Codable, Equatable, Hashable { public var state: State @@ -209,8 +216,9 @@ public extension ProvisioningState { case insufficientNetwork = 0x08 } } +#endif -public struct Profile: Codable, Equatable, Hashable { +public struct Profile: Codable, Equatable { public var person: Person public var friends: [Person] From 82c6ce93b09c5952fedeacef60e4589b36547513 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 16:48:29 -0500 Subject: [PATCH 20/33] Fixed Swift 4 compilation --- Tests/TLVCodingTests/TLVCodingTests.swift | 70 +++++++++++++---------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index dfa7d1a..1218545 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -14,7 +14,7 @@ final class TLVCodingTests: XCTestCase { static var allTests = [ ("testCodable", testCodable), - ("testCodingKeys", testCodingKeys), + ("testCodingKeys", testCodingKeys) ] func testCodable() { @@ -45,13 +45,11 @@ final class TLVCodingTests: XCTestCase { test(Person(gender: .male, name: "Coleman"), Data([0, 1, 0, 1, 7, 67, 111, 108, 101, 109, 97, 110])) - #if swift(>=4.2) 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])) - #endif test(Numeric( boolean: true, @@ -141,7 +139,6 @@ final class TLVCodingTests: XCTestCase { func testCodingKeys() { - #if swift(>=4.2) typealias CodingKeys = ProvisioningState.CodingKeys for codingKey in ProvisioningState.CodingKeys.allCases { @@ -149,9 +146,6 @@ final class TLVCodingTests: XCTestCase { XCTAssertEqual(CodingKeys(rawValue: codingKey.rawValue), codingKey) XCTAssertEqual(CodingKeys(stringValue: codingKey.stringValue), codingKey) } - #endif - } - } } @@ -169,33 +163,12 @@ public enum Gender: UInt8, Codable { case female } -#if swift(>=4.2) -public struct ProvisioningState: Codable, Equatable, Hashable { +public struct ProvisioningState: Codable, Equatable { public var state: State public var result: Result -} - -internal extension ProvisioningState { - enum CodingKeys: UInt8, TLVCodingKey, CaseIterable { - - case state = 0x01 - case result = 0x02 - - var stringValue: String { - switch self { - case .state: return "state" - case .result: return "result" - } - } - } - -} - -public extension ProvisioningState { - - enum State: UInt8, Codable { + public enum State: UInt8, Codable { case idle = 0x00 case provisioning = 0x01 @@ -203,7 +176,7 @@ public extension ProvisioningState { case declined = 0x03 } - enum Result: UInt8, Codable { + public enum Result: UInt8, Codable { case notAvailible = 0x00 case success = 0x01 @@ -216,6 +189,41 @@ public extension ProvisioningState { case insufficientNetwork = 0x08 } } + +internal extension ProvisioningState { + + enum CodingKeys: UInt8, TLVCodingKey { + + case state = 0x01 + case result = 0x02 + } +} + +extension ProvisioningState.CodingKeys { + + var stringValue: String { + switch self { + case .state: return "state" + case .result: return "result" + } + } +} + +#if swift(>=4.2) +extension ProvisioningState: CaseIterable { } +#else +extension ProvisioningState.CodingKeys { + + static let allCases: Set = [.state, .result] + + init?(stringValue: String) { + + guard let value = type(of: self).allCases.first(where: { $0.stringValue == stringValue }) + else { return nil } + + self = value + } +} #endif public struct Profile: Codable, Equatable { From 7383820181bdcbe2400db38de9f277e424822119 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 16:50:33 -0500 Subject: [PATCH 21/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 9 ----- Xcode/TLVCoding.xcodeproj/project.pbxproj | 46 +++++++++++------------ 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 1218545..3fde1c0 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -172,21 +172,12 @@ public struct ProvisioningState: Codable, Equatable { case idle = 0x00 case provisioning = 0x01 - case provisioned = 0x02 - case declined = 0x03 } public enum Result: UInt8, Codable { case notAvailible = 0x00 case success = 0x01 - case invalidConfiguration = 0x02 - case networkOutOfRange = 0x03 - case invalidKey = 0x04 - case otherError = 0x05 - case connectFailed = 0x06 - case connectTimeout = 0x07 - case insufficientNetwork = 0x08 } } diff --git a/Xcode/TLVCoding.xcodeproj/project.pbxproj b/Xcode/TLVCoding.xcodeproj/project.pbxproj index 69550bf..6e32db0 100644 --- a/Xcode/TLVCoding.xcodeproj/project.pbxproj +++ b/Xcode/TLVCoding.xcodeproj/project.pbxproj @@ -81,7 +81,7 @@ /* 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; }; @@ -98,8 +98,8 @@ 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 */ @@ -172,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 = ""; @@ -301,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 */ = { @@ -373,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 */ = { @@ -391,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 */ @@ -760,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"; }; @@ -780,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"; }; @@ -793,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; @@ -805,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; @@ -822,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; @@ -842,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"; @@ -863,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; @@ -883,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"; @@ -907,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; }; @@ -928,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"; @@ -944,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; @@ -958,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"; }; @@ -970,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; }; @@ -982,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; From daccf756ef3633d180f04c07b833fdfdd80b4a44 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 16:58:24 -0500 Subject: [PATCH 22/33] Support Swift 5 --- Package.swift | 4 ++-- Package@swift-4.1.swift | 15 +++++++++++++++ Package@swift-4.2.swift | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 Package@swift-4.1.swift create mode 100644 Package@swift-4.2.swift 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]) From 9ba0f02da9d70bcf1e612c8fdf1970d15ad67b02 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 18:51:17 -0500 Subject: [PATCH 23/33] Updated Xcode schemes --- .../xcshareddata/xcschemes/TLVCoding-iOS.xcscheme | 2 +- .../xcshareddata/xcschemes/TLVCoding-macOS.xcscheme | 2 +- .../xcshareddata/xcschemes/TLVCoding-tvOS.xcscheme | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 @@ From 6f1360d7514bfc96ab456fc4b7382dc1777cd39b Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 18:51:27 -0500 Subject: [PATCH 24/33] Fixed Swift 4 compilation --- Tests/TLVCodingTests/TLVCodingTests.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 3fde1c0..7f247a8 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -183,7 +183,7 @@ public struct ProvisioningState: Codable, Equatable { internal extension ProvisioningState { - enum CodingKeys: UInt8, TLVCodingKey { + enum CodingKeys: UInt8, TLVCodingKey, CaseIterable { case state = 0x01 case result = 0x02 @@ -201,8 +201,12 @@ extension ProvisioningState.CodingKeys { } #if swift(>=4.2) -extension ProvisioningState: CaseIterable { } #else +protocol CaseIterable: Hashable { + + static var allCases: Set { get } +} + extension ProvisioningState.CodingKeys { static let allCases: Set = [.state, .result] From 01acf4eccf55442f258a592c8c8704c38f0270a2 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 19:33:50 -0500 Subject: [PATCH 25/33] Fixed Swift 4.2 compilation error --- Sources/CodingKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index e9e5a82..202bd30 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -41,7 +41,7 @@ public extension TLVCodingKey where Self: RawRepresentable, Self.RawValue == TLV } #if swift(>=4.2) -public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, RawValue == TLVTypeCode.RawValue { +public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, Self.RawValue == TLVTypeCode.RawValue { init?(stringValue: String) { From a35b0c07e5b982cf6f6aee7e17c85dc852c3b384 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 20:16:21 -0500 Subject: [PATCH 26/33] Updated logging --- Sources/Decoder.swift | 2 ++ Sources/Encoder.swift | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index d89cca3..3a6d26d 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -30,6 +30,8 @@ public struct TLVDecoder { public func decode (_ type: T.Type, from data: Data) throws -> T { + log?("Will decode \(String(reflecting: T.self))") + let items = try decode(data) let options = Decoder.Options(numericFormat: numericFormat) diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index c332114..9951805 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -30,6 +30,8 @@ public struct TLVEncoder { 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) @@ -594,6 +596,7 @@ internal final class TLVUnkeyedEncodingContainer: UnkeyedEncodingContainer { private extension TLVEncodable { var copyingBytes: Data { + #if swift(>=5) return withUnsafePointer(to: self, { Data(bytes: $0, count: MemoryLayout.size) }) #else From ec2fb2284534b5f5ccc0dc777976fa8526f8dd10 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 22:51:34 -0500 Subject: [PATCH 27/33] Working on Encoder --- Sources/Encoder.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 9951805..220db19 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -171,14 +171,14 @@ internal extension TLVEncoder.Encoder { @inline(__always) func boxNumeric (_ value: T) -> Data { - let endianValue: T + let numericValue: T switch options.numericFormat { case .bigEndian: - endianValue = value.bigEndian + numericValue = value.bigEndian case .littleEndian: - endianValue = value.littleEndian + numericValue = value.littleEndian } - return endianValue.tlvData + return box(numericValue) } func boxEncodable (_ value: T) throws -> Data { From a894a26d10931516c98b20a10a92c88fded359b3 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 23:29:08 -0500 Subject: [PATCH 28/33] Working on Decoder --- Sources/Decoder.swift | 173 +++++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 80 deletions(-) diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index 3a6d26d..56d4db0 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -122,24 +122,17 @@ internal extension TLVDecoder { func container (keyedBy type: Key.Type) throws -> KeyedDecodingContainer { - log?("Requested container keyed by \(type) for path \"\(codingPath.path)\"") + log?("Requested container keyed by \(String(reflecting: type)) 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) } } @@ -159,16 +152,15 @@ internal extension TLVDecoder { case let .item(item): // forceably cast to array - guard let items = try? TLVDecoder.decode(item.value, codingPath: codingPath) else { - - throw DecodingError.typeMismatch(UnkeyedDecodingContainer.self, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot get unkeyed decoding container, invalid top container \(container).")) + 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).")) } - - // replace stack - self.stack.pop() - self.stack.push(.items(items)) - - return TLVUnkeyedDecodingContainer(referencing: self, wrapping: items) } } @@ -196,6 +188,37 @@ internal extension TLVDecoder.Decoder { } } +// MARK: - Coding Key + +internal extension TLVDecoder.Decoder { + + func typeCode (for key: Key) throws -> TLVTypeCode { + + if let tlvCodingKey = key as? TLVCodingKey { + + return tlvCodingKey.code + + } else if let intValue = key.intValue { + + guard intValue <= Int(UInt8.max), + intValue >= Int(UInt8.min) else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has an invalid integer value \(intValue)")) + } + + return TLVTypeCode(rawValue: UInt8(intValue)) + + } else if MemoryLayout.size == MemoryLayout.size, + Mirror(reflecting: key).displayStyle == .enum { + + return TLVTypeCode(rawValue: unsafeBitCast(key, to: UInt8.self)) + + } else { + + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has no integer value")) + } + } +} + // MARK: - Unboxing Values internal extension TLVDecoder.Decoder { @@ -210,21 +233,29 @@ internal extension TLVDecoder.Decoder { 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 } } @@ -279,28 +310,28 @@ fileprivate extension TLVDecoder.Stack { // MARK: - KeyedDecodingContainer -internal struct TLVKeyedDecodingContainer : KeyedDecodingContainerProtocol { +internal struct TLVKeyedDecodingContainer : KeyedDecodingContainerProtocol { typealias Key = K // MARK: Properties /// A reference to the encoder we're reading from. - private let decoder: TLVDecoder.Decoder + let decoder: TLVDecoder.Decoder /// A reference to the container we're reading from. - private let container: [TLVItem] + let container: [TLVItem] /// The path of coding keys taken to get to this point in decoding. - public let codingPath: [CodingKey] + let codingPath: [CodingKey] /// All the keys the Decoder has for this container. - public let allKeys: [Key] + let allKeys: [Key] // MARK: Initialization /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: TLVDecoder.Decoder, wrapping container: [TLVItem]) { + init(referencing decoder: TLVDecoder.Decoder, wrapping container: [TLVItem]) { self.decoder = decoder self.container = container @@ -312,18 +343,9 @@ internal struct TLVKeyedDecodingContainer : KeyedDecodingContain func contains(_ key: Key) -> Bool { - // log self.decoder.log?("Check whether key \"\(key.stringValue)\" exists") - - // check schema / model contains property - // FIXME: Remove unneccesary check as optimization - guard allKeys.contains(where: { $0.stringValue == key.stringValue }) + guard let typeCode = try? self.decoder.typeCode(for: key) else { return false } - - // return whether value exists for key - guard let typeCode = TLVTypeCode(codingKey: key) - else { return false } - return container.contains { $0.type == typeCode } } @@ -338,93 +360,81 @@ internal struct TLVKeyedDecodingContainer : KeyedDecodingContain func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - return try _decode(type, forKey: key) + return try decodeTLV(type, forKey: key) } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - let value = try _decode(Int32.self, forKey: key) - + let value = try decodeNumeric(Int32.self, forKey: key) return Int(value) } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - return try _decode(type, forKey: key) + return try decodeTLV(type, forKey: key) } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - return try _decode(type, forKey: key) + return try decodeNumeric(type, forKey: key) } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - return try _decode(type, forKey: key) + return try decodeNumeric(type, forKey: key) } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - return try _decode(type, forKey: key) + return try decodeNumeric(type, forKey: key) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - let value = try _decode(UInt32.self, forKey: key) - + let value = try decodeNumeric(UInt32.self, forKey: key) return UInt(value) } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - return try _decode(type, forKey: key) + return try decodeTLV(type, forKey: key) } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - return try _decode(type, forKey: key) + return try decodeNumeric(type, forKey: key) } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - return try _decode(type, forKey: key) + return try decodeNumeric(type, forKey: key) } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - return try _decode(type, forKey: key) + return try decodeNumeric(type, forKey: key) } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - let bitPattern = try _decode(UInt32.self, forKey: key) + let bitPattern = try decodeNumeric(UInt32.self, forKey: key) return Float(bitPattern: bitPattern) } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - let bitPattern = try _decode(UInt64.self, forKey: key) + let bitPattern = try decodeNumeric(UInt64.self, forKey: key) return Double(bitPattern: bitPattern) } func decode(_ type: String.Type, forKey key: Key) throws -> String { - return try _decode(type, forKey: key) + return try decodeTLV(type, forKey: key) } func decode (_ type: T.Type, forKey key: Key) throws -> T { - 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.")) - } - - let value = try self.decoder.unboxDecodable(item, as: type) - - return value + return try self.value(for: key, type: type) { try decoder.unboxDecodable($0, as: type) } } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { @@ -450,29 +460,34 @@ internal struct TLVKeyedDecodingContainer : KeyedDecodingContain // MARK: Private Methods /// Decode native value type from TLV data. - private func _decode (_ type: T.Type, forKey key: Key) throws -> T { + private func decodeTLV (_ type: T.Type, forKey key: Key) throws -> T { + + 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 { + + 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 { 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.")) } - - let data = item.value - let value = try decoder.unbox(data, as: type) - return value + return try decode(item) } /// Access actual value private func value(for key: Key) throws -> TLVItem? { - // log - decoder.log?("Will read value for key \(key.stringValue) at path \"\(decoder.codingPath.path)\"") - - // get value - return container.first { $0.type == TLVTypeCode(codingKey: key) } + decoder.log?("Will read value at path \"\(decoder.codingPath.path)\"") + let typeCode = try self.decoder.typeCode(for: key) + return container.first { $0.type == typeCode } } } @@ -718,19 +733,17 @@ internal struct TLVUnkeyedDecodingContainer: UnkeyedDecodingContainer { } } -fileprivate extension TLVUnkeyedDecodingContainer { +internal extension TLVUnkeyedDecodingContainer { struct Index: CodingKey { public let index: Int public init(intValue: Int) { - self.index = intValue } - init?(stringValue: String) { - + public init?(stringValue: String) { return nil } From 5758c7e2a9fa04ef9dd85ee6632a28eaba7c7ef6 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 23:29:27 -0500 Subject: [PATCH 29/33] Working on CodingKey --- Sources/CodingKey.swift | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 202bd30..876fc5e 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -55,7 +55,13 @@ public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, internal extension TLVTypeCode { - init? (codingKey: K) { + enum CodingKeyError: Error { + + case missingIntegerValue + case invalidIntegerValue(Int) + } + + init (codingKey: K) throws { if let tlvCodingKey = codingKey as? TLVCodingKey { @@ -65,17 +71,18 @@ internal extension TLVTypeCode { guard intValue <= Int(UInt8.max), intValue >= Int(UInt8.min) - else { return nil } + else { throw CodingKeyError.invalidIntegerValue(intValue) } self.init(rawValue: UInt8(intValue)) - } else if MemoryLayout.size == MemoryLayout.size { + } else if MemoryLayout.size == MemoryLayout.size, + Mirror(reflecting: codingKey).displayStyle == .enum { self.init(rawValue: unsafeBitCast(codingKey, to: UInt8.self)) } else { - return nil + throw CodingKeyError.missingIntegerValue } } } From 7c811a2b1f9b5a8ef7e129817028e103bf7efd04 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 23:45:33 -0500 Subject: [PATCH 30/33] Updated logging --- Sources/CodingKey.swift | 26 +++++++++++++++++--------- Sources/Decoder.swift | 28 ++++++++++++++-------------- Sources/Encoder.swift | 2 +- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 876fc5e..50e4b73 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -55,13 +55,7 @@ public extension TLVCodingKey where Self: CaseIterable, Self: RawRepresentable, internal extension TLVTypeCode { - enum CodingKeyError: Error { - - case missingIntegerValue - case invalidIntegerValue(Int) - } - - init (codingKey: K) throws { + init? (codingKey: K) { if let tlvCodingKey = codingKey as? TLVCodingKey { @@ -71,7 +65,7 @@ internal extension TLVTypeCode { guard intValue <= Int(UInt8.max), intValue >= Int(UInt8.min) - else { throw CodingKeyError.invalidIntegerValue(intValue) } + else { return nil } self.init(rawValue: UInt8(intValue)) @@ -82,7 +76,7 @@ internal extension TLVTypeCode { } else { - throw CodingKeyError.missingIntegerValue + return nil } } } @@ -94,3 +88,17 @@ internal extension Sequence where Element == CodingKey { return reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }) } } + +internal extension CodingKey { + + static var sanitizedName: String { + + let rawName = String(reflecting: self) + var elements = rawName.split(separator: ".") + guard elements.count > 2 + else { return rawName } + elements.removeFirst() + elements.removeAll { $0.contains("(unknown context") } + return elements.reduce("", { $0 + ($0.isEmpty ? "" : ".") + $1 }) + } +} diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index 56d4db0..f579633 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -122,7 +122,7 @@ internal extension TLVDecoder { func container (keyedBy type: Key.Type) throws -> KeyedDecodingContainer { - log?("Requested container keyed by \(String(reflecting: type)) for path \"\(codingPath.path)\"") + log?("Requested container keyed by \(type.sanitizedName) for path \"\(codingPath.path)\"") let container = self.stack.top @@ -530,8 +530,7 @@ internal struct TLVSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_ type: Int.Type) throws -> Int { - let value = try self.decoder.unbox(container.value, as: Int32.self) - + let value = try self.decoder.unboxNumeric(container.value, as: Int32.self) return Int(value) } @@ -542,23 +541,22 @@ internal struct TLVSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_ type: Int16.Type) throws -> Int16 { - return try self.decoder.unbox(container.value, as: type) + return try self.decoder.unboxNumeric(container.value, as: type) } func decode(_ type: Int32.Type) throws -> Int32 { - return try self.decoder.unbox(container.value, as: type) + return try self.decoder.unboxNumeric(container.value, as: type) } func decode(_ type: Int64.Type) throws -> Int64 { - return try self.decoder.unbox(container.value, as: type) + return try self.decoder.unboxNumeric(container.value, as: type) } func decode(_ type: UInt.Type) throws -> UInt { - let value = try self.decoder.unbox(container.value, as: UInt32.self) - + let value = try self.decoder.unboxNumeric(container.value, as: UInt32.self) return UInt(value) } @@ -569,27 +567,29 @@ internal struct TLVSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_ type: UInt16.Type) throws -> UInt16 { - return try self.decoder.unbox(container.value, as: type) + return try self.decoder.unboxNumeric(container.value, as: type) } func decode(_ type: UInt32.Type) throws -> UInt32 { - return try self.decoder.unbox(container.value, as: type) + return try self.decoder.unboxNumeric(container.value, as: type) } func decode(_ type: UInt64.Type) throws -> UInt64 { - return try self.decoder.unbox(container.value, as: type) + return try self.decoder.unboxNumeric(container.value, as: type) } func decode(_ type: Float.Type) throws -> Float { - fatalError() + let value = try self.decoder.unboxNumeric(container.value, as: UInt32.self) + return Float(bitPattern: value) } func decode(_ type: Double.Type) throws -> Double { - fatalError() + let value = try self.decoder.unboxNumeric(container.value, as: UInt64.self) + return Double(bitPattern: value) } func decode(_ type: String.Type) throws -> String { @@ -636,7 +636,7 @@ internal struct TLVUnkeyedDecodingContainer: UnkeyedDecodingContainer { return _count } - var _count: Int { + private var _count: Int { return container.count } diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 220db19..96a2c0d 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -91,7 +91,7 @@ internal extension TLVEncoder { func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - log?("Requested container keyed by \(type) for path \"\(codingPath.path)\"") + log?("Requested container keyed by \(type.sanitizedName) for path \"\(codingPath.path)\"") let stackContainer = ItemsContainer() self.stack.push(.items(stackContainer)) From 3c4ace62f20b7e68309a2d919bc7409ded2854ee Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 23:50:12 -0500 Subject: [PATCH 31/33] Working on Coding Key --- Sources/Decoder.swift | 26 ++++++-------------------- Sources/Encoder.swift | 27 ++++++--------------------- 2 files changed, 12 insertions(+), 41 deletions(-) diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index f579633..8e74e12 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -194,28 +194,14 @@ internal extension TLVDecoder.Decoder { func typeCode (for key: Key) throws -> TLVTypeCode { - if let tlvCodingKey = key as? TLVCodingKey { - - return tlvCodingKey.code - - } else if let intValue = key.intValue { - - guard intValue <= Int(UInt8.max), - intValue >= Int(UInt8.min) else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has an invalid integer value \(intValue)")) + 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 TLVTypeCode(rawValue: UInt8(intValue)) - - } else if MemoryLayout.size == MemoryLayout.size, - Mirror(reflecting: key).displayStyle == .enum { - - return TLVTypeCode(rawValue: unsafeBitCast(key, to: UInt8.self)) - - } else { - - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has no integer value")) } + return typeCode } } diff --git a/Sources/Encoder.swift b/Sources/Encoder.swift index 96a2c0d..4bd52c0 100644 --- a/Sources/Encoder.swift +++ b/Sources/Encoder.swift @@ -135,29 +135,14 @@ internal extension TLVEncoder.Encoder { func typeCode (for key: Key, value: T) throws -> TLVTypeCode { - if let tlvCodingKey = key as? TLVCodingKey { - - return tlvCodingKey.code - - } else if let intValue = key.intValue { - - guard intValue <= Int(UInt8.max), - intValue >= Int(UInt8.min) else { - - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has an invalid integer value \(intValue)")) + 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 TLVTypeCode(rawValue: UInt8(intValue)) - - } else if MemoryLayout.size == MemoryLayout.size, - Mirror(reflecting: key).displayStyle == .enum { - - return TLVTypeCode(rawValue: unsafeBitCast(key, to: UInt8.self)) - - } else { - - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Coding key \(key) has no integer value")) } + return typeCode } } From b62d05bc9a6ca438ce5ac01081fb3f5363c872c9 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 13 May 2019 23:57:49 -0500 Subject: [PATCH 32/33] Fixed Swift 4 compilation --- Sources/CodingKey.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/CodingKey.swift b/Sources/CodingKey.swift index 50e4b73..27ca396 100644 --- a/Sources/CodingKey.swift +++ b/Sources/CodingKey.swift @@ -94,11 +94,21 @@ 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 }) } } From 095c998fa98716524a24840815ead19ab97311d4 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Tue, 14 May 2019 00:12:50 -0500 Subject: [PATCH 33/33] Updated unit tests --- Tests/TLVCodingTests/TLVCodingTests.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Tests/TLVCodingTests/TLVCodingTests.swift b/Tests/TLVCodingTests/TLVCodingTests.swift index 7f247a8..15dfb68 100644 --- a/Tests/TLVCodingTests/TLVCodingTests.swift +++ b/Tests/TLVCodingTests/TLVCodingTests.swift @@ -135,6 +135,14 @@ final class TLVCodingTests: XCTestCase { ), 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() { @@ -274,3 +282,9 @@ public extension Binary { } } } + +public struct PrimitiveArray: Codable, Equatable { + + var strings: [String] + var integers: [Int] +}