A fast, safe, and efficient CBOR serialization library for Swift on any platform. swiftpackageindex.com/thecoolwinter/CBOR/1.1.1/documentation/cbor
atproto swift cbor

Compare changes

Choose any two refs to compare.

+469 -1059
+7 -4
.github/workflows/ci.yml
··· 22 22 name: Testing CBOR (ubuntu) 23 23 needs: swiftlint 24 24 runs-on: ubuntu-latest 25 - container: swift:6.2-noble 25 + container: swift:6.0-noble 26 26 steps: 27 27 - name: Checkout repository 28 28 uses: actions/checkout@v5 29 + - uses: swift-actions/setup-swift@next 30 + with: 31 + swift-version: "6.0" 29 32 - name: Restore .build 30 33 id: "restore-build" 31 34 uses: actions/cache/restore@v4 ··· 53 56 uses: actions/checkout@v5 54 57 - uses: swift-actions/setup-swift@next 55 58 with: 56 - swift-version: "6.1" 59 + swift-version: "6.0" 57 60 - name: Restore .build 58 61 id: "restore-build" 59 62 uses: actions/cache/restore@v4 ··· 62 65 key: "swiftpm-tests-build-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" 63 66 restore-keys: "swiftpm-tests-build-${{ runner.os }}-" 64 67 - name: Building Package 65 - run: set -o pipefail && swift build --build-tests | xcbeautify --renderer github-actions 68 + run: set -o pipefail && swift build --build-tests | xcbeautify 66 69 # run: set -o pipefail && swift build --build-tests // --sanitize fuzzer | xcbeautify 67 70 - name: Cache .build 68 71 if: steps.restore-build.outputs.cache-hit != 'true' ··· 71 74 path: .build 72 75 key: "swiftpm-tests-build-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" 73 76 - name: Testing Package 74 - run: set -o pipefail && swift test --skip-build | xcbeautify --renderer github-actions 77 + run: set -o pipefail && swift test --skip-build | xcbeautify 75 78 # TODO: The Xcode version of the swift toolchain does not come with libfuzzer 76 79 # What needs to happen is we install the open source toolchain and then we can fuzz here... 77 80 # - name: Fuzzing For 15 Seconds
+13 -12
README.md
··· 32 32 - Supports decoding half precision floats (Float16) as a regular Float. 33 33 - Runs on Linux, Android, and Windows using the swift-foundation project when available. 34 34 - Fuzz tested for reliability against crashes. 35 - - ***NEW*** Supports tagged items with custom tag injection. 36 - - Dates are a special case, handled by the library. 37 - - Contains UUID example implementation. 35 + - Supports tagged items (will expand and add ability to inject your own tags in the future): 36 + - Dates 37 + - UUIDs 38 + - [CIDs](https://github.com/multiformats/cid) (tag 42) 38 39 - Flexible date parsing (tags `0` or `1` with support for any numeric value representation). 39 40 - Decoding multiple top-level objects using `decodeMultiple(_:from:)`. 40 - - ***NEW*** IPLD compatible DAG-CBOR encoder for content addressable data. 41 - - ***NEW*** Flexible date decoding for untagged date items encoded as strings, floating point values, or integers. 41 + - *NEW* IPLD compatible DAG-CBOR encoder for content addressable data. 42 + - *NEW* Flexible date decoding for untagged date items encoded as strings, floating point values, or integers. 43 + 44 + > Note: This is not a valid CBOR/CDE encoder, it merely always outputs countable collections. CBOR/CDE should be implemented in the future as it's quite similar. 42 45 43 46 ## Usage 44 47 ··· 87 90 let dagEncoder = DAGCBOREncoder(dateEncodingStrategy: .double) 88 91 ``` 89 92 90 - > [!NOTE] 91 - > DAG-CBOR does not allow tagged items (besides the CID item), and thus encoding dates must be done by encoding their 'raw' value directly. This is an application specific behavior, so ensure the encoder is using the correct date encoding behavior for compatibility. By default, the encoder will encode dates as an epoch `Double` timestamp. 92 - 93 - To use with CIDs, conform your internal CID type to ``CIDType``. Do not conform standard types like `String` or `Data` to ``CIDType``, or the encoder may attempt to encode all of those data as tagged items. 93 + To use, conform your internal CID type to ``CIDType``. Do not conform standard types like `String` or `Data` to ``CIDType``, or the encoder will attempt to encode all of those data as tagged items. 94 94 ```swift 95 95 struct CID: CIDType { 96 96 let data: Data ··· 112 112 } 113 113 } 114 114 ``` 115 - 116 115 > [!WARNING] 117 116 > 118 - > It's *your* responsibility to correctly encode CIDs in the data container. That includes the `NULL` byte for raw binary CID encoding (which DAG-CBOR expects). 117 + > You **need** to prefix your data with the `NULL` byte when encoding. This library will not handle that for you. It is invalid DAG-CBOR encoding to not include the prefixed byte. 119 118 120 119 Now, any time the encoder finds a `CID` type it will encode it using the correct tag. 121 120 ```swift ··· 128 127 // 4A # bytes(10) 129 128 // 00000102030405060708 # "\u0000\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b" 130 129 ``` 130 + > [!NOTE] 131 + > DAG-CBOR does not allow tagged items (besides the CID item), and thus encoding dates must be done by encoding their 'raw' value directly. This is an application specific behavior, so ensure the encoder is using the correct date encoding behavior for compatibility. By default, the encoder will encode dates as an epoch `Double` timestamp. 131 132 132 133 ## Documentation 133 134 ··· 139 140 140 141 ```swift 141 142 dependencies: [ 142 - .package(url: "https://github.com/thecoolwinter/CBOR.git", from: "1.1.0") 143 + .package(url: "https://github.com/thecoolwinter/CBOR.git", from: "1.0.0") 143 144 ] 144 145 ``` 145 146
+51 -24
Sources/CBOR/Decoder/CBORDecoder.swift
··· 23 23 public var options: DecodingOptions 24 24 25 25 /// Create a new CBOR decoder. 26 - /// 27 - /// All parameters match flags in ``DecodingOptions``. 28 - public init( 29 - rejectIndeterminateLengths: Bool = true, 30 - recursionDepth: Int = 50, 31 - rejectIntKeys: Bool = false, 32 - rejectUnorderedMap: Bool = false, 33 - rejectUndefined: Bool = false, 34 - rejectNaN: Bool = false, 35 - rejectInf: Bool = false, 36 - singleTopLevelItem: Bool = false 37 - ) { 38 - self.options = DecodingOptions( 39 - rejectIndeterminateLengths: rejectIndeterminateLengths, 40 - recursionDepth: recursionDepth, 41 - rejectIntKeys: rejectIntKeys, 42 - rejectUnorderedMap: rejectUnorderedMap, 43 - rejectUndefined: rejectUndefined, 44 - rejectNaN: rejectNaN, 45 - rejectInf: rejectInf, 46 - singleTopLevelItem: singleTopLevelItem 47 - ) 26 + /// - Parameter rejectIndeterminateLengths: Set to `false` to allow indeterminate length objects to be decoded. 27 + /// Defaults to *rejecting* indeterminate length items (strings, bytes, 28 + /// maps, and arrays). 29 + public init(rejectIndeterminateLengths: Bool = true) { 30 + self.options = DecodingOptions(rejectIndeterminateLengths: rejectIndeterminateLengths) 48 31 } 49 32 50 33 /// Create a new CBOR decoder ··· 78 61 } 79 62 } catch { 80 63 if let error = error as? ScanError { 81 - throw error.decodingError() 64 + try throwScanError(error) 82 65 } else { 83 66 throw error 84 67 } ··· 128 111 } 129 112 } catch { 130 113 if let error = error as? ScanError { 131 - throw error.decodingError() 114 + try throwScanError(error) 132 115 } else { 133 116 throw error 134 117 } 118 + } 119 + } 120 + 121 + private func throwScanError(_ error: ScanError) throws -> Never { 122 + switch error { 123 + case .unexpectedEndOfData: 124 + throw DecodingError.dataCorrupted( 125 + .init(codingPath: [], debugDescription: "Unexpected end of data.") 126 + ) 127 + case let .invalidMajorType(byte, offset): 128 + throw DecodingError.dataCorrupted(.init( 129 + codingPath: [], 130 + debugDescription: "Unexpected major type: \(String(byte, radix: 2)) at offset \(offset)" 131 + )) 132 + case let .invalidSize(byte, offset): 133 + throw DecodingError.dataCorrupted(.init( 134 + codingPath: [], 135 + debugDescription: "Unexpected size argument: \(String(byte, radix: 2)) at offset \(offset)" 136 + )) 137 + case let .expectedMajorType(offset): 138 + throw DecodingError.dataCorrupted(.init( 139 + codingPath: [], 140 + debugDescription: "Expected major type at offset \(offset)" 141 + )) 142 + case let .typeInIndeterminateString(type, offset): 143 + throw DecodingError.dataCorrupted(.init( 144 + codingPath: [], 145 + debugDescription: "Unexpected major type in indeterminate \(type) at offset \(offset)" 146 + )) 147 + case let .rejectedIndeterminateLength(type, offset): 148 + throw DecodingError.dataCorrupted(.init( 149 + codingPath: [], 150 + debugDescription: "Rejected indeterminate length type \(type) at offset \(offset)" 151 + )) 152 + case let .cannotRepresentInt(max, found, offset): 153 + throw DecodingError.dataCorrupted( 154 + .init( 155 + codingPath: [], 156 + debugDescription: "Failed to decode integer with maximum \(max), " 157 + + "found \(found) at \(offset)" 158 + ) 159 + ) 160 + case .noTagInformation, .invalidMajorTypeForTaggedItem: 161 + throw error // rethrow these guys 135 162 } 136 163 } 137 164 }
-12
Sources/CBOR/Decoder/Containers/DataRegion.swift
··· 107 107 } 108 108 } 109 109 110 - @inline(__always) 111 - @usableFromInline 112 - func checkIntSize<T: FixedWidthInteger, F: FixedWidthInteger>(_ value: T, previousSize: F) throws { 113 - guard value > previousSize else { 114 - throw ScanError.unnecessaryInt 115 - } 116 - } 117 - 118 110 @inlinable 119 111 func readInt<T: FixedWidthInteger>(as: T.Type, argument: UInt8, from index: Index) throws -> T { 120 112 let byteCount = argument ··· 126 118 case 24: 127 119 let intVal = try read(as: UInt8.self, from: index) 128 120 try checkIntConversion(T.self, val: intVal) 129 - try checkIntSize(intVal, previousSize: Constants.maxArgSize) 130 121 return T(intVal) 131 122 case 25: 132 123 let intVal = try read(as: UInt16.self, from: index) 133 124 try checkIntConversion(T.self, val: intVal) 134 - try checkIntSize(intVal, previousSize: UInt8.max) 135 125 return T(intVal) 136 126 case 26: 137 127 let intVal = try read(as: UInt32.self, from: index) 138 128 try checkIntConversion(T.self, val: intVal) 139 - try checkIntSize(intVal, previousSize: UInt16.max) 140 129 return T(intVal) 141 130 case 27: 142 131 let intVal = try read(as: UInt64.self, from: index) 143 132 try checkIntConversion(T.self, val: intVal) 144 - try checkIntSize(intVal, previousSize: UInt32.max) 145 133 return T(intVal) 146 134 default: 147 135 throw ScanError.invalidSize(byte: byteCount, offset: startIndex)
+1 -34
Sources/CBOR/Decoder/Containers/KeyedCBORDecodingContainer.swift
··· 12 12 #endif 13 13 14 14 struct KeyedCBORDecodingContainer<Key: CodingKey>: DecodingContextContainer, KeyedDecodingContainerProtocol { 15 - enum AnyKey: Hashable, Comparable { 15 + enum AnyKey: Hashable { 16 16 case int(Int) 17 17 case string(String) 18 18 ··· 32 32 Key(stringValue: string) 33 33 } 34 34 } 35 - 36 - static func < (_ lhs: AnyKey, _ rhs: AnyKey) -> Bool { 37 - switch (lhs, rhs) { 38 - case let (.string(lhs), .string(rhs)): 39 - if lhs.count == rhs.count { 40 - return lhs < rhs 41 - } 42 - return lhs.count < rhs.count 43 - case let (.int(lhs), .int(rhs)): 44 - return lhs < rhs 45 - default: 46 - print("Bruh") 47 - return false 48 - } 49 - } 50 35 } 51 36 52 37 let context: DecodingContext ··· 66 51 decodedKeys.reserveCapacity(childCount / 2) 67 52 68 53 var mapOffset = context.results.firstChildIndex(data.mapOffset) 69 - var lastKey: AnyKey? 70 54 for _ in 0..<childCount / 2 { 71 55 let key = context.results.load(at: mapOffset) 72 56 let decodedKey: AnyKey 73 57 switch key.type { 74 58 case .uint, .nint: 75 - guard !context.options.rejectIntKeys else { 76 - throw DecodingError.typeMismatch(String.self, context.error( 77 - "Configured to reject integer keys, found integer key in map at \(data.globalIndex)." 78 - )) 79 - } 80 59 let intVal = try SingleValueCBORDecodingContainer(context: context, data: key).decode(Int.self) 81 60 decodedKey = AnyKey.int(intVal) 82 61 case .string: ··· 93 72 let value = context.results.load(at: mapOffset) 94 73 mapOffset = context.results.siblingIndex(mapOffset) 95 74 96 - guard decodedKeys[decodedKey] == nil else { 97 - throw DecodingError.dataCorrupted(context.error("Duplicate map keys found: \(decodedKey)")) 98 - } 99 - 100 - if context.options.rejectUnorderedMap && lastKey != nil && lastKey! > decodedKey { 101 - throw DecodingError.dataCorrupted(context.error( 102 - "Configured to reject unordered map, found '\(decodedKey)' after" 103 - + "'\(String(describing: lastKey))', which is invalid." 104 - )) 105 - } 106 - 107 75 decodedKeys[decodedKey] = value 108 - lastKey = decodedKey 109 76 } 110 77 111 78 self.decodedKeys = decodedKeys
+3 -60
Sources/CBOR/Decoder/Containers/SingleValueCBORDecodingContainer.swift
··· 16 16 let data: DataRegion 17 17 } 18 18 19 - // MARK: - Decoder 20 - 21 19 extension SingleValueCBORDecodingContainer: Decoder { 22 20 func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey { 23 21 try KeyedDecodingContainer(KeyedCBORDecodingContainer(context: context, data: data)) ··· 33 31 } 34 32 35 33 extension SingleValueCBORDecodingContainer: SingleValueDecodingContainer { 36 - // MARK: - Nil 37 - 38 34 func decodeNil() -> Bool { 39 35 data.isNil() 40 36 } 41 37 42 - // MARK: - Bool 43 - 44 38 func decode(_: Bool.Type) throws -> Bool { 45 39 let argument = try checkType(.simple, arguments: 20, 21, as: Bool.self) 46 40 return argument == 21 47 41 } 48 42 49 - // MARK: - Floating Points 50 - 51 - /// Helper for checking floats against decoding flags. 52 - @inline(__always) 53 - private func checkFloatValidity<T: FloatingPoint>(_ value: T) throws -> T { 54 - if context.options.rejectNaN && value.isNaN { 55 - throw DecodingError.dataCorrupted(context.error("Found NaN \(T.self), configured to reject NaN values.")) 56 - } 57 - 58 - if context.options.rejectInf && value.isInfinite { 59 - throw DecodingError.dataCorrupted( 60 - context.error("Found \(value) \(T.self), configured to reject Infinite values.") 61 - ) 62 - } 63 - 64 - return value 65 - } 66 - 67 43 func decode(_: Float.Type) throws -> Float { 68 44 let arg = try checkType(.simple, arguments: 25, 26, as: Float.self) 69 45 if arg == 25 { ··· 73 49 context.error("Could not decode half-precision float into Swift Float.") 74 50 ) 75 51 } 76 - return try checkFloatValidity(value) 52 + return value 77 53 } 78 54 79 55 let floatRaw = try data.read(as: UInt32.self) 80 - return try checkFloatValidity(Float(bitPattern: floatRaw)) 56 + return Float(bitPattern: floatRaw) 81 57 } 82 58 83 59 func decode(_: Double.Type) throws -> Double { 84 60 try checkType(.simple, arguments: 27, as: Double.self) 85 61 let doubleRaw = try data.read(as: UInt64.self).littleEndian 86 - return try checkFloatValidity(Double(bitPattern: doubleRaw)) 62 + return Double(bitPattern: doubleRaw) 87 63 } 88 - 89 - // MARK: - Integers 90 64 91 65 func decode<T: Decodable & FixedWidthInteger>(_: T.Type) throws -> T { 92 66 try checkType(.uint, .nint, forType: T.self) ··· 103 77 return value 104 78 } 105 79 106 - @available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) 107 - func decode(_ type: Int128.Type) throws -> Int128 { 108 - try checkType(.uint, .nint, forType: Int128.self) 109 - let value = try data.readInt(as: Int128.self) 110 - if data.type == .nint { 111 - return -1 - value 112 - } 113 - return value 114 - } 115 - 116 - @available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) 117 - func decode(_ type: UInt128.Type) throws -> UInt128 { 118 - try checkType(.uint, .nint, forType: UInt128.self) 119 - let value = try data.readInt(as: UInt128.self) 120 - if data.type == .nint { 121 - throw DecodingError.typeMismatch( 122 - Int.self, 123 - context.error("Found a negative integer while attempting to decode an unsigned int \(UInt128.self).") 124 - ) 125 - } 126 - return value 127 - } 128 - 129 - // MARK: - String 130 - 131 80 func decode(_: String.Type) throws -> String { 132 81 try checkType(.string, forType: String.self) 133 82 ··· 179 128 } 180 129 return string 181 130 } 182 - 183 - // MARK: - Date 184 131 185 132 // Attempt first to decode a tagged date value, then move on and try decoding any of the following as a date: 186 133 // - Int ··· 243 190 } 244 191 } 245 192 246 - // MARK: - Data 247 - 248 193 private func _decode(_: Data.Type) throws -> Data { 249 194 try checkType(.bytes, forType: Data.self) 250 195 ··· 288 233 } 289 234 return string 290 235 } 291 - 292 - // MARK: - Decode 293 236 294 237 func decode<T: Decodable>(_ type: T.Type) throws -> T { 295 238 // Unfortunate force unwraps here, but necessary
-49
Sources/CBOR/Decoder/DAGCBORDecoder.swift
··· 1 - // 2 - // DAGCBORDecoder.swift 3 - // CBOR 4 - // 5 - // Created by Khan Winter on 10/20/25. 6 - // 7 - 8 - #if canImport(FoundationEssentials) 9 - import FoundationEssentials 10 - #else 11 - import Foundation 12 - #endif 13 - 14 - /// Decodes ``Decodable`` objects from DAG-CBOR data. 15 - /// 16 - /// This is really just a wrapper for ``CBORDecoder``, which constant configuration flags that ensure valid DAG-CBOR 17 - /// data is decoded. 18 - /// 19 - /// If this decoder is too strict, I'd suggest using ``CBORDecoder``, which will be much more flexible around decoding 20 - /// than this type. You can also loosen specific flags using the options member of that struct. For instance, this 21 - /// type will reject all unordered maps. If you find yourself needing to receive badly-defined DAG-CBOR, just 22 - /// use ``CBORDecoder``. 23 - public struct DAGCBORDecoder { 24 - /// The options that determine decoding behavior. 25 - let options: DecodingOptions 26 - 27 - /// Create a new DAG-CBOR decoder. 28 - public init() { 29 - self.options = DecodingOptions( 30 - rejectIndeterminateLengths: true, 31 - rejectIntKeys: true, 32 - rejectUnorderedMap: true, 33 - rejectUndefined: true, 34 - rejectNaN: true, 35 - rejectInf: true, 36 - singleTopLevelItem: true 37 - ) 38 - } 39 - 40 - /// Decodes the given type from DAG-CBOR binary data. 41 - /// - Parameters: 42 - /// - type: The decodable type to deserialize. 43 - /// - data: The DAG-CBOR data to decode from. 44 - /// - Returns: An instance of the decoded type. 45 - /// - Throws: A ``DecodingError`` with context and a debug description for a failed deserialization operation. 46 - public func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T { 47 - try CBORDecoder(options: options).decode(T.self, from: data) 48 - } 49 - }
+1 -40
Sources/CBOR/Decoder/DecodingOptions.swift
··· 13 13 /// For deterministic encoding, this **must** be enabled. 14 14 public var rejectIndeterminateLengths: Bool 15 15 16 - /// Maximum recursion depth. 17 - public var recursionDepth: Int = 50 18 - 19 - /// Reject maps with non-string keys.. 20 - public var rejectIntKeys: Bool 21 - 22 - /// Reject maps whose keys are out of order. 23 - public var rejectUnorderedMap: Bool 24 - 25 - /// Reject the `undefined` simple value (`23`). 26 - public var rejectUndefined: Bool 27 - 28 - /// Enable to reject decoded `NaN` floating point values. 29 - public var rejectNaN: Bool 30 - 31 - /// Enable to reject decoded infinite floating point values. 32 - public var rejectInf: Bool 33 - 34 - /// Require that CBOR data encapsulates the *entire* data object being decoded. 35 - /// When true, throws a decoding error if data is left over after scanning for valid CBOR structure, or if there 36 - /// are multiple top-level objects. 37 - public var singleTopLevelItem: Bool 38 - 39 16 /// Create a new options object. 40 - public init( 41 - rejectIndeterminateLengths: Bool = true, 42 - recursionDepth: Int = 50, 43 - rejectIntKeys: Bool = false, 44 - rejectUnorderedMap: Bool = false, 45 - rejectUndefined: Bool = false, 46 - rejectNaN: Bool = false, 47 - rejectInf: Bool = false, 48 - singleTopLevelItem: Bool = false 49 - ) { 17 + public init(rejectIndeterminateLengths: Bool = true) { 50 18 self.rejectIndeterminateLengths = rejectIndeterminateLengths 51 - self.recursionDepth = recursionDepth 52 - self.rejectIntKeys = rejectIntKeys 53 - self.rejectUnorderedMap = rejectUnorderedMap 54 - self.rejectUndefined = rejectUndefined 55 - self.rejectNaN = rejectNaN 56 - self.rejectInf = rejectInf 57 - self.singleTopLevelItem = singleTopLevelItem 58 19 } 59 20 }
+29 -33
Sources/CBOR/Decoder/Scanner/CBORScanner.swift
··· 11 11 import Foundation 12 12 #endif 13 13 14 + @usableFromInline 15 + enum ScanError: Error { 16 + case unexpectedEndOfData 17 + case invalidMajorType(byte: UInt8, offset: Int) 18 + case invalidSize(byte: UInt8, offset: Int) 19 + case expectedMajorType(offset: Int) 20 + case typeInIndeterminateString(type: MajorType, offset: Int) 21 + case rejectedIndeterminateLength(type: MajorType, offset: Int) 22 + case cannotRepresentInt(max: UInt, found: UInt, offset: Int) 23 + case noTagInformation(tag: UInt, offset: Int) 24 + case invalidMajorTypeForTaggedItem(tag: UInt, expected: Set<MajorType>, found: MajorType, offset: Int) 25 + } 26 + 14 27 /// # Why Scan? 15 28 /// I'd have loved to use a 'pop' method for decoding, where we only decode as data is requested. However, the way 16 29 /// Swift's decoding APIs work forces us to be able to be able to do random access for keys in maps, which requires ··· 39 52 40 53 consuming func scan() throws -> Results { 41 54 while !reader.isEmpty { 42 - if options.singleTopLevelItem && reader.index > 0 { 43 - throw ScanError.unreadDataAfterEnd 44 - } 45 - 46 55 let idx = reader.index 47 - try scanNext(depth: 0) 56 + try scanNext() 48 57 assert(idx < reader.index, "Scanner made no forward progress in scan") 49 58 } 50 - 51 - if options.singleTopLevelItem && !reader.isEmpty { 52 - throw ScanError.unreadDataAfterEnd 53 - } 54 - 55 59 return results 56 60 } 57 61 58 - private mutating func scanNext(depth: Int) throws { 62 + private mutating func scanNext() throws { 59 63 guard let type = reader.peekType(), let raw = reader.peek() else { 60 64 if reader.isEmpty { 61 65 throw ScanError.unexpectedEndOfData ··· 64 68 } 65 69 } 66 70 67 - guard depth < options.recursionDepth else { 68 - throw ScanError.recursionLimit 69 - } 70 - 71 - try scanType(type: type, raw: raw, depth: depth) 71 + try scanType(type: type, raw: raw) 72 72 } 73 73 74 - private mutating func scanType(type: MajorType, raw: UInt8, depth: Int) throws { 74 + private mutating func scanType(type: MajorType, raw: UInt8) throws { 75 75 switch type { 76 76 case .uint, .nint: 77 77 try scanInt(raw: raw) ··· 80 80 case .string: 81 81 try scanBytesOrString(.string, raw: raw) 82 82 case .array: 83 - try scanArray(depth: depth) 83 + try scanArray() 84 84 case .map: 85 - try scanMap(depth: depth) 85 + try scanMap() 86 86 case .simple: 87 87 try scanSimple(raw: raw) 88 88 case .tagged: 89 - try scanTagged(raw: raw, depth: depth) 89 + try scanTagged(raw: raw) 90 90 } 91 91 } 92 92 ··· 103 103 // MARK: - Scan Simple 104 104 105 105 private mutating func scanSimple(raw: UInt8) throws { 106 - guard !(options.rejectUndefined && reader.peekArgument() == 23) else { 107 - throw ScanError.rejectedUndefined 108 - } 109 - 110 106 let idx = reader.index 111 107 results.recordSimple(reader.pop(), currentByteIndex: idx) 112 108 guard reader.canRead(raw.simpleLength()) else { ··· 154 150 155 151 // MARK: - Scan Array 156 152 157 - private mutating func scanArray(depth: Int) throws { 153 + private mutating func scanArray() throws { 158 154 guard peekIsIndeterminate() else { 159 155 let size = try reader.readNextInt(as: Int.self) 160 156 let mapIdx = results.recordArrayStart(currentByteIndex: reader.index) 161 157 for _ in 0..<size { 162 - try scanNext(depth: depth + 1) 158 + try scanNext() 163 159 } 164 160 results.recordEnd(childCount: size, resultLocation: mapIdx, currentByteIndex: reader.index) 165 161 return ··· 173 169 reader.pop() // Pop type 174 170 var count = 0 175 171 while reader.peek() != Constants.breakCode { 176 - try scanNext(depth: depth + 1) 172 + try scanNext() 177 173 count += 1 178 174 } 179 175 // Pop the break byte ··· 183 179 184 180 // MARK: - Scan Map 185 181 186 - private mutating func scanMap(depth: Int) throws { 182 + private mutating func scanMap() throws { 187 183 guard peekIsIndeterminate() else { 188 184 let keyCount = try reader.readNextInt(as: Int.self) 189 185 guard keyCount < Int.max / 2 else { ··· 193 189 let size = keyCount * 2 194 190 let mapIdx = results.recordMapStart(currentByteIndex: reader.index) 195 191 for _ in 0..<size { 196 - try scanNext(depth: depth + 1) 192 + try scanNext() 197 193 } 198 194 results.recordEnd(childCount: size, resultLocation: mapIdx, currentByteIndex: reader.index) 199 195 return ··· 207 203 reader.pop() // Pop type 208 204 var count = 0 209 205 while reader.peek() != Constants.breakCode { 210 - try scanNext(depth: depth + 1) // Maps should always have a multiple of two values. 211 - try scanNext(depth: depth + 1) 206 + try scanNext() // Maps should always have a multiple of two values. 207 + try scanNext() 212 208 count += 2 213 209 } 214 210 // Pop the break byte ··· 218 214 219 215 // MARK: - Scan Tagged 220 216 221 - private mutating func scanTagged(raw: UInt8, depth: Int) throws { 217 + private mutating func scanTagged(raw: UInt8) throws { 222 218 // Scan the tag number (passing the raw value here makes it record a Tag rather than an Int) 223 219 try scanInt(raw: raw) 224 220 ··· 226 222 throw ScanError.unexpectedEndOfData 227 223 } 228 224 229 - try scanType(type: nextTag, raw: nextRaw, depth: depth) 225 + try scanType(type: nextTag, raw: nextRaw) 230 226 } 231 227 } 232 228
-103
Sources/CBOR/Decoder/Scanner/ScanError.swift
··· 1 - // 2 - // ScanError.swift 3 - // CBOR 4 - // 5 - // Created by Khan Winter on 10/21/25. 6 - // 7 - 8 - @usableFromInline 9 - enum ScanError: Error { 10 - case unexpectedEndOfData 11 - case invalidMajorType(byte: UInt8, offset: Int) 12 - case invalidSize(byte: UInt8, offset: Int) 13 - case expectedMajorType(offset: Int) 14 - case typeInIndeterminateString(type: MajorType, offset: Int) 15 - case rejectedIndeterminateLength(type: MajorType, offset: Int) 16 - case cannotRepresentInt(max: UInt, found: UInt, offset: Int) 17 - case recursionLimit 18 - case unreadDataAfterEnd 19 - case rejectedUndefined 20 - case unnecessaryInt 21 - 22 - // swiftlint:disable:next cyclomatic_complexity function_body_length 23 - func decodingError() -> DecodingError { 24 - switch self { 25 - case .unexpectedEndOfData: 26 - return DecodingError.dataCorrupted( 27 - .init(codingPath: [], debugDescription: "Unexpected end of data.", underlyingError: self) 28 - ) 29 - case let .invalidMajorType(byte, offset): 30 - return DecodingError.dataCorrupted(.init( 31 - codingPath: [], 32 - debugDescription: "Unexpected major type: \(String(byte, radix: 2)) at offset \(offset)", 33 - underlyingError: self 34 - )) 35 - case let .invalidSize(byte, offset): 36 - return DecodingError.dataCorrupted(.init( 37 - codingPath: [], 38 - debugDescription: "Unexpected size argument: \(String(byte, radix: 2)) at offset \(offset)", 39 - underlyingError: self 40 - )) 41 - case let .expectedMajorType(offset): 42 - return DecodingError.dataCorrupted(.init( 43 - codingPath: [], 44 - debugDescription: "Expected major type at offset \(offset)", 45 - underlyingError: self 46 - )) 47 - case let .typeInIndeterminateString(type, offset): 48 - return DecodingError.dataCorrupted(.init( 49 - codingPath: [], 50 - debugDescription: "Unexpected major type in indeterminate \(type) at offset \(offset)", 51 - underlyingError: self 52 - )) 53 - case let .rejectedIndeterminateLength(type, offset): 54 - return DecodingError.dataCorrupted(.init( 55 - codingPath: [], 56 - debugDescription: "Rejected indeterminate length type \(type) at offset \(offset)", 57 - underlyingError: self 58 - )) 59 - case let .cannotRepresentInt(max, found, offset): 60 - return DecodingError.dataCorrupted( 61 - .init( 62 - codingPath: [], 63 - debugDescription: "Failed to decode integer with maximum \(max), " 64 - + "found \(found) at \(offset)", 65 - underlyingError: self 66 - ) 67 - ) 68 - case .recursionLimit: 69 - return DecodingError.dataCorrupted( 70 - .init( 71 - codingPath: [], 72 - debugDescription: "Recursion depth limit exceeded (DecodingOptions.recursionDepth)", 73 - underlyingError: self 74 - ) 75 - ) 76 - case .unreadDataAfterEnd: 77 - return DecodingError.dataCorrupted( 78 - .init( 79 - codingPath: [], 80 - debugDescription: "Configured to reject CBOR data with trailing bytes (DecodingOptions." 81 - + "singleTopLevelItem)", 82 - underlyingError: self 83 - ) 84 - ) 85 - case .rejectedUndefined: 86 - return DecodingError.dataCorrupted( 87 - .init( 88 - codingPath: [], 89 - debugDescription: "Configured to reject undefined values (DecodingOptions.rejectUndefined)", 90 - underlyingError: self 91 - ) 92 - ) 93 - case .unnecessaryInt: 94 - return DecodingError.dataCorrupted( 95 - .init( 96 - codingPath: [], 97 - debugDescription: "Found integer that was encoded in a larger data format than necessary", 98 - underlyingError: self 99 - ) 100 - ) 101 - } 102 - } 103 - }
+1 -1
Sources/CBOR/Encoder/CBOREncoder.swift
··· 56 56 let tempStorage = TopLevelTemporaryEncodingStorage() 57 57 58 58 let encodingContext = EncodingContext(options: options) 59 - let encoder = try SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 59 + let encoder = SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 60 60 try encoder.encode(value) 61 61 62 62 let dataSize = tempStorage.value.size
+2 -2
Sources/CBOR/Encoder/Containers/KeyedCBOREncodingContainer.swift
··· 19 19 self.context = context 20 20 } 21 21 22 - func encoder(for key: CodingKey) throws -> SingleValueCBOREncodingContainer<KeyBuffer.Keyed> { 23 - return try .init(parent: storage.withKey(key), context: context.appending(key)) 22 + func encoder(for key: CodingKey) -> SingleValueCBOREncodingContainer<KeyBuffer.Keyed> { 23 + return .init(parent: storage.withKey(key), context: context.appending(key)) 24 24 } 25 25 26 26 func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
-15
Sources/CBOR/Encoder/Containers/SingleValueCBOREncodingContainer.swift
··· 19 19 var options: EncodingOptions { context.options } 20 20 var codingPath: [CodingKey] { context.codingPath } 21 21 22 - init(parent: Storage, context: EncodingContext) throws { 23 - self.parent = parent 24 - self.context = context 25 - } 26 - 27 22 func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey { 28 23 .init(KeyedCBOREncodingContainer(parent: parent, context: context)) 29 24 } ··· 69 64 } 70 65 71 66 func encode<T>(_ value: T) throws where T: Encodable, T: FixedWidthInteger { 72 - parent.register(IntOptimizer(value: value)) 73 - } 74 - 75 - @available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) 76 - func encode(_ value: Int128) throws { 77 - parent.register(IntOptimizer(value: value)) 78 - } 79 - 80 - @available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) 81 - func encode(_ value: UInt128) throws { 82 67 parent.register(IntOptimizer(value: value)) 83 68 } 84 69
+1 -1
Sources/CBOR/Encoder/DAGCBOREncoder.swift
··· 39 39 let tempStorage = TopLevelTemporaryEncodingStorage() 40 40 41 41 let encodingContext = EncodingContext(options: options) 42 - let encoder = try SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 42 + let encoder = SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 43 43 try encoder.encode(value) 44 44 45 45 let dataSize = tempStorage.value.size
+8 -44
Sources/CBOR/Encoder/Optimizers/KeyedOptimizer.swift
··· 8 8 @inlinable 9 9 func KeyedOptimizer(value: [String: EncodingOptimizer]) -> EncodingOptimizer { 10 10 if value.count < Constants.maxArgSize { 11 - return SmallKeyedOptimizer( 12 - value: value, 13 - orderedUsing: { lhs, rhs in 14 - if lhs.key.count == rhs.key.count { 15 - return lhs.key < rhs.key 16 - } 17 - 18 - return lhs.key.count < rhs.key.count 19 - }, 20 - optimizer: { StringOptimizer(value: $0) } 21 - ) 11 + return SmallKeyedOptimizer(value: value, optimizer: { StringOptimizer(value: $0) }) 22 12 } else { 23 - return LargeKeyedOptimizer( 24 - value: value, 25 - orderedUsing: { lhs, rhs in 26 - if lhs.key.count == rhs.key.count { 27 - return lhs.key < rhs.key 28 - } 29 - 30 - return lhs.key.count < rhs.key.count 31 - }, 32 - optimizer: { StringOptimizer(value: $0) } 33 - ) 13 + return LargeKeyedOptimizer(value: value, optimizer: { StringOptimizer(value: $0) }) 34 14 } 35 15 } 36 16 37 17 @inlinable 38 18 func KeyedOptimizer(value: [Int: EncodingOptimizer]) -> EncodingOptimizer { 39 19 if value.count < Constants.maxArgSize { 40 - return SmallKeyedOptimizer( 41 - value: value, 42 - orderedUsing: { $0.key < $1.key }, 43 - optimizer: { IntOptimizer(value: $0) } 44 - ) 20 + return SmallKeyedOptimizer(value: value, optimizer: { IntOptimizer(value: $0) }) 45 21 } else { 46 - return LargeKeyedOptimizer( 47 - value: value, 48 - orderedUsing: { $0.key < $1.key }, 49 - optimizer: { IntOptimizer(value: $0) } 50 - ) 22 + return LargeKeyedOptimizer(value: value, optimizer: { IntOptimizer(value: $0) }) 51 23 } 52 24 } 53 25 ··· 69 41 @usableFromInline var contentSize: Int 70 42 71 43 @usableFromInline 72 - init( 73 - value: [KeyType: EncodingOptimizer], 74 - orderedUsing: ((key: KeyType, value: EncodingOptimizer), (key: KeyType, value: EncodingOptimizer)) -> Bool, 75 - optimizer: (KeyType) -> EncodingOptimizer 76 - ) { 44 + init(value: [KeyType: EncodingOptimizer], optimizer: (KeyType) -> EncodingOptimizer) { 77 45 var size = 0 78 46 var array: [KeyValue] = [] 79 47 array.reserveCapacity(value.count) 80 48 81 - self.value = value.sorted(by: orderedUsing).reduce(into: array, { array, keyValue in 49 + self.value = value.sorted(by: { $0.key < $1.key }).reduce(into: array, { array, keyValue in 82 50 let optimized = KeyValue(key: optimizer(keyValue.key), value: keyValue.value) 83 51 size += optimized.size 84 52 array.append(optimized) ··· 106 74 @usableFromInline var contentSize: Int 107 75 108 76 @usableFromInline 109 - init( 110 - value: [KeyType: EncodingOptimizer], 111 - orderedUsing: ((key: KeyType, value: EncodingOptimizer), (key: KeyType, value: EncodingOptimizer)) -> Bool, 112 - optimizer: (KeyType) -> EncodingOptimizer 113 - ) { 77 + init(value: [KeyType: EncodingOptimizer], optimizer: (KeyType) -> EncodingOptimizer) { 114 78 var size = 0 115 79 var array: [KeyValue] = [] 116 80 array.reserveCapacity(value.count) 117 81 118 - self.value = value.sorted(by: orderedUsing).reduce(into: array, { array, keyValue in 82 + self.value = value.sorted(by: { $0.key < $1.key }).reduce(into: array, { array, keyValue in 119 83 let optimized = KeyValue(key: optimizer(keyValue.key), value: keyValue.value) 120 84 size += optimized.size 121 85 array.append(optimized)
+1 -1
Sources/CBOR/Encoder/Optimizers/TaggedItemOptimizer.swift
··· 22 22 23 23 init(value: TaggedCBORItem, context: EncodingContext) throws { 24 24 let storage = TopLevelTemporaryEncodingStorage() 25 - var container = try SingleValueCBOREncodingContainer(parent: storage, context: context) 25 + var container = SingleValueCBOREncodingContainer(parent: storage, context: context) 26 26 try value.encodeTaggedData(using: &container) 27 27 sizeOptimizer = IntOptimizer(value: Swift.type(of: value).tag) 28 28 optimizer = storage.value
+1 -3
Sources/ProfilingHelper/Profiling.swift
··· 15 15 func time(_ cbor: () throws -> Void, _ json: () throws -> Void) throws { 16 16 guard #available(macOS 15.0, *) else { fatalError() } 17 17 func calculateStats(_ measurements: [Duration]) -> (average: Double, stddev: Double) { 18 - let values = measurements.map { 19 - Double($0.components.seconds) + (Double($0.components.attoseconds) / 1e15) 20 - } 18 + let values = measurements.map { Double($0.attoseconds) / 1e15 } 21 19 let avg = values.reduce(0, +) / Double(values.count) 22 20 23 21 let variance = values.map { pow($0 - avg, 2) }.reduce(0, +) / Double(values.count)
-194
Tests/CBORTests/AnyDecodable.swift
··· 1 - // From https://github.com/Flight-School/AnyCodable/blob/master/Sources/AnyCodable/AnyDecodable.swift 2 - 3 - // swiftlint:disable cyclomatic_complexity 4 - // swiftlint:disable line_length 5 - // swiftlint:disable type_name 6 - 7 - #if canImport(FoundationEssentials) 8 - import FoundationEssentials 9 - #else 10 - import Foundation 11 - #endif 12 - 13 - /** 14 - A type-erased `Decodable` value. 15 - 16 - The `AnyDecodable` type forwards decoding responsibilities 17 - to an underlying value, hiding its specific underlying type. 18 - 19 - You can decode mixed-type values in dictionaries 20 - and other collections that require `Decodable` conformance 21 - by declaring their contained type to be `AnyDecodable`: 22 - 23 - let json = """ 24 - { 25 - "boolean": true, 26 - "integer": 42, 27 - "double": 3.141592653589793, 28 - "string": "string", 29 - "array": [1, 2, 3], 30 - "nested": { 31 - "a": "alpha", 32 - "b": "bravo", 33 - "c": "charlie" 34 - }, 35 - "null": null 36 - } 37 - """.data(using: .utf8)! 38 - 39 - let decoder = JSONDecoder() 40 - let dictionary = try! decoder.decode([String: AnyDecodable].self, from: json) 41 - */ 42 - struct AnyDecodable: Decodable { 43 - let value: Any 44 - 45 - init<T>(_ value: T?) { 46 - self.value = value ?? () 47 - } 48 - } 49 - 50 - @usableFromInline 51 - protocol _AnyDecodable { 52 - var value: Any { get } 53 - init<T>(_ value: T?) 54 - } 55 - 56 - extension AnyDecodable: _AnyDecodable {} 57 - 58 - extension _AnyDecodable { 59 - init(from decoder: Decoder) throws { 60 - let container = try decoder.singleValueContainer() 61 - 62 - if container.decodeNil() { 63 - self.init(Optional<Self>.none) 64 - } else if let bool = try? container.decode(Bool.self) { 65 - self.init(bool) 66 - } else if let int = try? container.decode(Int.self) { 67 - self.init(int) 68 - } else if let uint = try? container.decode(UInt.self) { 69 - self.init(uint) 70 - } else if let double = try? container.decode(Double.self) { 71 - self.init(double) 72 - } else if let string = try? container.decode(String.self) { 73 - self.init(string) 74 - } else if let array = try? container.decode([AnyDecodable].self) { 75 - self.init(array.map { $0.value }) 76 - } else if let dictionary = try? container.decode([String: AnyDecodable].self) { 77 - self.init(dictionary.mapValues { $0.value }) 78 - } else { 79 - throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyDecodable value cannot be decoded") 80 - } 81 - } 82 - } 83 - 84 - extension AnyDecodable: Equatable { 85 - static func == (lhs: AnyDecodable, rhs: AnyDecodable) -> Bool { 86 - switch (lhs.value, rhs.value) { 87 - case (Optional<Self>.none, Optional<Self>.none), is (Void, Void): 88 - return true 89 - case let (lhs as Bool, rhs as Bool): 90 - return lhs == rhs 91 - case let (lhs as Int, rhs as Int): 92 - return lhs == rhs 93 - case let (lhs as Int8, rhs as Int8): 94 - return lhs == rhs 95 - case let (lhs as Int16, rhs as Int16): 96 - return lhs == rhs 97 - case let (lhs as Int32, rhs as Int32): 98 - return lhs == rhs 99 - case let (lhs as Int64, rhs as Int64): 100 - return lhs == rhs 101 - case let (lhs as UInt, rhs as UInt): 102 - return lhs == rhs 103 - case let (lhs as UInt8, rhs as UInt8): 104 - return lhs == rhs 105 - case let (lhs as UInt16, rhs as UInt16): 106 - return lhs == rhs 107 - case let (lhs as UInt32, rhs as UInt32): 108 - return lhs == rhs 109 - case let (lhs as UInt64, rhs as UInt64): 110 - return lhs == rhs 111 - case let (lhs as Float, rhs as Float): 112 - return lhs == rhs 113 - case let (lhs as Double, rhs as Double): 114 - return lhs == rhs 115 - case let (lhs as String, rhs as String): 116 - return lhs == rhs 117 - case let (lhs as [String: AnyDecodable], rhs as [String: AnyDecodable]): 118 - return lhs == rhs 119 - case let (lhs as [AnyDecodable], rhs as [AnyDecodable]): 120 - return lhs == rhs 121 - default: 122 - return false 123 - } 124 - } 125 - } 126 - 127 - extension AnyDecodable: CustomStringConvertible { 128 - var description: String { 129 - switch value { 130 - case is Void: 131 - return String(describing: nil as Any?) 132 - case let value as CustomStringConvertible: 133 - return value.description 134 - default: 135 - return String(describing: value) 136 - } 137 - } 138 - } 139 - 140 - extension AnyDecodable: CustomDebugStringConvertible { 141 - var debugDescription: String { 142 - switch value { 143 - case let value as CustomDebugStringConvertible: 144 - return "AnyDecodable(\(value.debugDescription))" 145 - default: 146 - return "AnyDecodable(\(description))" 147 - } 148 - } 149 - } 150 - 151 - extension AnyDecodable: Hashable { 152 - func hash(into hasher: inout Hasher) { 153 - switch value { 154 - case let value as Bool: 155 - hasher.combine(value) 156 - case let value as Int: 157 - hasher.combine(value) 158 - case let value as Int8: 159 - hasher.combine(value) 160 - case let value as Int16: 161 - hasher.combine(value) 162 - case let value as Int32: 163 - hasher.combine(value) 164 - case let value as Int64: 165 - hasher.combine(value) 166 - case let value as UInt: 167 - hasher.combine(value) 168 - case let value as UInt8: 169 - hasher.combine(value) 170 - case let value as UInt16: 171 - hasher.combine(value) 172 - case let value as UInt32: 173 - hasher.combine(value) 174 - case let value as UInt64: 175 - hasher.combine(value) 176 - case let value as Float: 177 - hasher.combine(value) 178 - case let value as Double: 179 - hasher.combine(value) 180 - case let value as String: 181 - hasher.combine(value) 182 - case let value as [String: AnyDecodable]: 183 - hasher.combine(value) 184 - case let value as [AnyDecodable]: 185 - hasher.combine(value) 186 - default: 187 - break 188 - } 189 - } 190 - } 191 - 192 - // swiftlint:enable cyclomatic_complexity 193 - // swiftlint:enable line_length 194 - // swiftlint:enable type_name
-48
Tests/CBORTests/DAGCBORDecoderTests.swift
··· 1 - // 2 - // DAGCBORDecoderTests.swift 3 - // CBOR 4 - // 5 - // Created by Khan Winter on 10/21/25. 6 - // 7 - 8 - import Testing 9 - @testable import CBOR 10 - 11 - @Suite 12 - struct DAGCBORDecoderTests { 13 - @Test 14 - func shortMap() throws { 15 - let data = "a1616100".asHexData() 16 - let decoded = try DAGCBORDecoder().decode([String: Int].self, from: data) 17 - #expect(decoded == ["a": 0]) 18 - } 19 - 20 - @Test 21 - func rejectUnorderedMaps() throws { 22 - let data = "a361610162616103616202".asHexData() 23 - #expect(throws: DecodingError.self) { 24 - return try DAGCBORDecoder().decode([String: Int].self, from: data) 25 - } 26 - } 27 - 28 - @Test 29 - func rejectOtherUnorderedMaps() throws { 30 - let data = "a2616201616100".asHexData() 31 - #expect(throws: DecodingError.self) { 32 - return try DAGCBORDecoder().decode([String: Int].self, from: data) 33 - } 34 - } 35 - 36 - @Test 37 - func rejectConcatenatedItems() throws { 38 - var data = "0000".asHexData() 39 - #expect(throws: DecodingError.self) { 40 - return try DAGCBORDecoder().decode(Int.self, from: data) 41 - } 42 - 43 - data = "0000AA".asHexData() 44 - #expect(throws: DecodingError.self) { 45 - return try DAGCBORDecoder().decode(Int.self, from: data) 46 - } 47 - } 48 - }
-326
Tests/CBORTests/DAGCBOREncoderTests.swift
··· 1 - // 2 - // DAGCBORTests.swift 3 - // CBOR 4 - // 5 - // Created by Khan Winter on 10/14/25. 6 - // 7 - 8 - import Foundation 9 - import Testing 10 - @testable import CBOR 11 - 12 - // swiftlint:disable:next private_over_fileprivate 13 - fileprivate struct CID: CIDType { 14 - let data: Data 15 - 16 - init(data: Data) { 17 - self.data = data 18 - } 19 - 20 - init<Container: SingleValueDecodingContainer>(decodeTaggedDataUsing container: Container) throws { 21 - var data = try container.decode(Data.self) 22 - data.removeFirst() 23 - self.data = data 24 - } 25 - 26 - func encodeTaggedData<Container: SingleValueEncodingContainer>(using container: inout Container) throws { 27 - try container.encode(Data([0x0]) + data) 28 - } 29 - } 30 - 31 - @Suite 32 - struct DAGCBOREncoderTests { 33 - @Test 34 - func customCIDTypeEncodedCorrectly() throws { 35 - let cid = CID(data: [0, 1, 2, 3, 4, 5, 6, 7, 8]) 36 - let data = try DAGCBOREncoder().encode(cid).hexString() 37 - #expect(data == "d82a4a00000102030405060708") 38 - } 39 - 40 - @Test 41 - func knownValidBase256EncodedID() throws { 42 - let cid = CID(data: "017112209fe4ccc6de16724f3a30c7e8f254f3c6471986acb1f8d8cf8e96ce2ad7dbe7fb".asHexData()) 43 - let data = try DAGCBOREncoder().encode(cid).hexString() 44 - let expected = "d82a582500017112209FE4CCC6DE16724F3A30C7E8F254F3C6471986ACB1F8D8CF8E96CE2AD7DBE7FB".lowercased() 45 - #expect(data == expected) 46 - } 47 - 48 - // MARK: Copied from EncodableTests 49 - 50 - @Test 51 - func encodeNull() throws { 52 - let encoded = try DAGCBOREncoder().encode(String?(nil)) 53 - #expect(encoded == Data([0xf6])) 54 - } 55 - 56 - @Test 57 - func encodeBools() throws { 58 - let falseVal = try DAGCBOREncoder().encode(false) 59 - #expect(falseVal == Data([0xf4])) 60 - 61 - let trueVal = try DAGCBOREncoder().encode(true) 62 - #expect(trueVal == Data([0xf5])) 63 - } 64 - 65 - @Test 66 - func encodeInts() throws { 67 - // Less than 24 68 - let zero = try DAGCBOREncoder().encode(0) 69 - #expect(zero == Data([0x00])) 70 - 71 - let eight = try DAGCBOREncoder().encode(8) 72 - #expect(eight == Data([0x08])) 73 - 74 - let ten = try DAGCBOREncoder().encode(10) 75 - #expect(ten == Data([0x0a])) 76 - 77 - let twentyThree = try DAGCBOREncoder().encode(23) 78 - #expect(twentyThree == Data([0x17])) 79 - 80 - // Just bigger than 23 81 - let twentyFour = try DAGCBOREncoder().encode(24) 82 - #expect(twentyFour == Data([0x18, 0x18])) 83 - 84 - let twentyFive = try DAGCBOREncoder().encode(25) 85 - #expect(twentyFive == Data([0x18, 0x19])) 86 - 87 - // Bigger 88 - let hundred = try DAGCBOREncoder().encode(100) 89 - #expect(hundred == Data([0x18, 0x64])) 90 - 91 - let thousand = try DAGCBOREncoder().encode(1_000) 92 - #expect(thousand == Data([0x19, 0x03, 0xe8])) 93 - 94 - let million = try DAGCBOREncoder().encode(1_000_000) 95 - #expect(million == Data([0x1a, 0x00, 0x0f, 0x42, 0x40])) 96 - 97 - let trillion = try DAGCBOREncoder().encode(1_000_000_000_000) 98 - #expect(trillion == Data([0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00])) 99 - } 100 - 101 - @Test 102 - func encodeNegativeInts() throws { 103 - // Less than 24 104 - let minusOne = try DAGCBOREncoder().encode(-1) 105 - #expect(minusOne == Data([0x20])) 106 - 107 - let minusTen = try DAGCBOREncoder().encode(-10) 108 - #expect(minusTen == Data([0x29])) 109 - 110 - // Bigger 111 - let minusHundred = try DAGCBOREncoder().encode(-100) 112 - #expect(minusHundred == Data([0x38, 0x63])) 113 - 114 - let minusThousand = try DAGCBOREncoder().encode(-1_000) 115 - #expect(minusThousand == Data([0x39, 0x03, 0xe7])) 116 - 117 - let minusMillion = try DAGCBOREncoder().encode(-1_000_001) 118 - #expect(minusMillion == Data([0x3A, 0x00, 0x0F, 0x42, 0x40])) 119 - 120 - let minusTrillion = try DAGCBOREncoder().encode(-1_000_000_001) 121 - #expect(minusTrillion == Data([0x3A, 0x3B, 0x9A, 0xCA, 0x00])) 122 - } 123 - 124 - @Test 125 - func encodeFloat() throws { 126 - let value: Float = 100000.0 127 - // Different. 128 - let data = "fb40f86a0000000000".asHexData() 129 - let result = try DAGCBOREncoder().encode(value) 130 - #expect(data == result) 131 - } 132 - 133 - @Test 134 - func encodeDouble() throws { 135 - let value: Double = 0.10035 136 - let data = "FB3FB9B089A0275254".asHexData() 137 - let result = try DAGCBOREncoder().encode(value) 138 - #expect(data == result) 139 - } 140 - 141 - @Test(arguments: [Double.nan, Double.infinity, -Double.infinity]) 142 - func NanAndInfiniteThrowErrors(value: Double) throws { 143 - #expect(throws: EncodingError.self) { 144 - try DAGCBOREncoder().encode(value) 145 - } 146 - } 147 - 148 - @Test 149 - func encodeStrings() throws { 150 - let empty = try DAGCBOREncoder().encode("") 151 - #expect(empty == Data([0x60])) 152 - 153 - let a = try DAGCBOREncoder().encode("a") 154 - #expect(a == Data([0x61, 0x61])) 155 - 156 - let IETF = try DAGCBOREncoder().encode("IETF") 157 - #expect(IETF == Data([0x64, 0x49, 0x45, 0x54, 0x46])) 158 - 159 - let quoteSlash = try DAGCBOREncoder().encode("\"\\") 160 - #expect(quoteSlash == Data([0x62, 0x22, 0x5c])) 161 - 162 - let littleUWithDiaeresis = try DAGCBOREncoder().encode("\u{00FC}") 163 - #expect(littleUWithDiaeresis == Data([0x62, 0xc3, 0xbc])) 164 - } 165 - 166 - @Test 167 - func encodeByteStrings() throws { 168 - let fourByteByteString = try DAGCBOREncoder().encode(Data([0x01, 0x02, 0x03, 0x04])) 169 - #expect(fourByteByteString == Data([0x44, 0x01, 0x02, 0x03, 0x04])) 170 - } 171 - 172 - @Test 173 - func mixedByteArraysEncodeCorrectly() throws { 174 - // TODO: Make the container swap to mixed mode if necessary. 175 - 176 - /// See note in ``UnkeyeyedCBOREncodingContainer`` about mixed collections of ints 177 - struct Mixed: Encodable { 178 - func encode(to encoder: any Encoder) throws { 179 - var container = encoder.unkeyedContainer() 180 - try container.encode(UInt8.zero) 181 - try container.encode(UInt8.max) 182 - try container.encode("Hello World") 183 - try container.encode(1000000) 184 - } 185 - } 186 - 187 - let data = try DAGCBOREncoder().encode(Mixed()) 188 - // swiftlint:disable:next line_length 189 - #expect(data == Data([0x84, 0x00, 0x18, 0xff, 0x6b, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x00, 0x0f, 0x42, 0x40])) 190 - } 191 - 192 - @Test 193 - func encodeArrays() throws { 194 - let empty = try DAGCBOREncoder().encode([String]()) 195 - #expect(empty == Data([0x80])) 196 - 197 - let oneTwoThree = try DAGCBOREncoder().encode([1, 2, 3]) 198 - #expect(oneTwoThree == Data([0x83, 0x01, 0x02, 0x03])) 199 - 200 - // swiftlint:disable:next line_length 201 - let lotsOfInts = try DAGCBOREncoder().encode([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]) 202 - 203 - // swiftlint:disable:next line_length 204 - #expect(lotsOfInts == Data([0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19])) 205 - 206 - let nestedSimple = try DAGCBOREncoder().encode([[1], [2, 3], [4, 5]]) 207 - #expect(nestedSimple == Data([0x83, 0x81, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05])) 208 - } 209 - 210 - @Test 211 - func encodeMaps() throws { 212 - let empty = try DAGCBOREncoder().encode([String: String]()) 213 - #expect(empty == Data([0xa0])) 214 - 215 - let stringToString = try DAGCBOREncoder().encode(["a": "A", "b": "B", "c": "C", "d": "D", "e": "E"]) 216 - #expect(stringToString.first! == 0xa5) 217 - 218 - let dataMinusFirstByte = stringToString[1...] 219 - .map { $0 } 220 - .chunked(into: 4) 221 - .sorted(by: { $0.lexicographicallyPrecedes($1) }) 222 - let dataForKeyValuePairs: [[UInt8]] = [ 223 - [0x61, 0x61, 0x61, 0x41], 224 - [0x61, 0x62, 0x61, 0x42], 225 - [0x61, 0x63, 0x61, 0x43], 226 - [0x61, 0x64, 0x61, 0x44], 227 - [0x61, 0x65, 0x61, 0x45] 228 - ] 229 - #expect(dataMinusFirstByte == dataForKeyValuePairs) 230 - 231 - let encoder = DAGCBOREncoder() 232 - let encodedWithStringKeys = try encoder.encode([1: 2, 3: 4]) 233 - #expect( 234 - encodedWithStringKeys == Data([0xa2, 0x61, 0x31, 0x02, 0x61, 0x33, 0x04]) 235 - ) 236 - } 237 - 238 - @Test 239 - func encodeSimpleStructs() throws { 240 - struct MyStruct: Codable { 241 - let age: Int 242 - let name: String 243 - } 244 - 245 - let encoded = try DAGCBOREncoder().encode(MyStruct(age: 27, name: "Ham")).map { $0 } 246 - 247 - #expect( 248 - encoded == [0xa2, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1b, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x63, 0x48, 0x61, 0x6d] 249 - ) 250 - } 251 - 252 - @Test 253 - func encodeMoreComplexStructs() throws { 254 - let encoder = DAGCBOREncoder() 255 - 256 - let data = try encoder.encode(Company.mock).hexString().uppercased() 257 - // swiftlint:disable:next line_length 258 - #expect(data == "A4646E616D656941636D6520436F727067666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F746569656D706C6F796565738AA563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5") 259 - } 260 - 261 - @Test 262 - func uint16() throws { 263 - let encoder = DAGCBOREncoder() 264 - let data = try encoder.encode(1999) 265 - #expect(data == "1907CF".asHexData()) 266 - } 267 - 268 - @Test( 269 - .disabled("Disabled until we can figure out how to make Float16 compile on all platforms."), 270 - arguments: [ 271 - ("F90000", 0), 272 - ("F93C00", 1.0), 273 - ("F9BE00", -1.5), 274 - ("F97C00", .infinity), 275 - ("F93E32", 1.548828125), 276 - ("F9F021", -8456) 277 - ] 278 - ) 279 - func halfFloat(expected: String, value: Float16) throws { 280 - let expectedData = expected.asHexData() 281 - let encoder = DAGCBOREncoder() 282 - let data = try encoder.encode(value) 283 - #expect(data == expectedData) 284 - } 285 - 286 - @Test 287 - func duplicateKeysAreDeduplicatedOnEncode() throws { 288 - let encoder = DAGCBOREncoder() 289 - struct Mock: Encodable { 290 - enum CodingKeys: String, CodingKey { 291 - case one 292 - } 293 - 294 - func encode(to encoder: any Encoder) throws { 295 - var container = encoder.container(keyedBy: CodingKeys.self) 296 - try container.encode(true, forKey: .one) 297 - try container.encode(false, forKey: .one) 298 - } 299 - } 300 - 301 - let data = try encoder.encode(Mock()).hexString() 302 - #expect(data == "a1636f6e65f4") 303 - } 304 - 305 - @Test 306 - func uuid() throws { 307 - let uuid = UUID(uuidString: "A0BDA068-60AD-4111-B4C9-04746791028B") 308 - #expect(throws: EncodingError.self) { 309 - try DAGCBOREncoder().encode(uuid) 310 - } 311 - } 312 - 313 - @Test 314 - func orderedMapKeys() throws { 315 - let value = ["a": 1, "b": 2, "aa": 3] 316 - let encoded = try DAGCBOREncoder().encode(value).hexString() 317 - #expect(encoded == "a361610161620262616103") 318 - } 319 - 320 - @Test 321 - func emptyData() throws { 322 - let value = Data() 323 - let encoded = try DAGCBOREncoder().encode(value).hexString() 324 - #expect(encoded == "40") 325 - } 326 - }
+312
Tests/CBORTests/DAGCBORTests.swift
··· 1 + // 2 + // DAGCBORTests.swift 3 + // CBOR 4 + // 5 + // Created by Khan Winter on 10/14/25. 6 + // 7 + 8 + import Foundation 9 + import Testing 10 + @testable import CBOR 11 + 12 + // swiftlint:disable:next private_over_fileprivate 13 + fileprivate struct CID: CIDType { 14 + let data: Data 15 + 16 + init(data: Data) { 17 + self.data = data 18 + } 19 + 20 + init<Container: SingleValueDecodingContainer>(decodeTaggedDataUsing container: Container) throws { 21 + var data = try container.decode(Data.self) 22 + data.removeFirst() 23 + self.data = data 24 + } 25 + 26 + func encodeTaggedData<Container: SingleValueEncodingContainer>(using container: inout Container) throws { 27 + try container.encode(Data([0x0]) + data) 28 + } 29 + } 30 + 31 + @Suite 32 + struct DAGCBORTests { 33 + @Test 34 + func customCIDTypeEncodedCorrectly() throws { 35 + let cid = CID(data: [0, 1, 2, 3, 4, 5, 6, 7, 8]) 36 + let data = try DAGCBOREncoder().encode(cid).hexString() 37 + #expect(data == "d82a4a00000102030405060708") 38 + } 39 + 40 + @Test 41 + func knownValidBase256EncodedID() throws { 42 + let cid = CID(data: "017112209fe4ccc6de16724f3a30c7e8f254f3c6471986acb1f8d8cf8e96ce2ad7dbe7fb".asHexData()) 43 + let data = try DAGCBOREncoder().encode(cid).hexString() 44 + let expected = "d82a582500017112209FE4CCC6DE16724F3A30C7E8F254F3C6471986ACB1F8D8CF8E96CE2AD7DBE7FB".lowercased() 45 + #expect(data == expected) 46 + } 47 + 48 + // MARK: Copied from EncodableTests 49 + 50 + @Test 51 + func encodeNull() throws { 52 + let encoded = try DAGCBOREncoder().encode(String?(nil)) 53 + #expect(encoded == Data([0xf6])) 54 + } 55 + 56 + @Test 57 + func encodeBools() throws { 58 + let falseVal = try DAGCBOREncoder().encode(false) 59 + #expect(falseVal == Data([0xf4])) 60 + 61 + let trueVal = try DAGCBOREncoder().encode(true) 62 + #expect(trueVal == Data([0xf5])) 63 + } 64 + 65 + @Test 66 + func encodeInts() throws { 67 + // Less than 24 68 + let zero = try DAGCBOREncoder().encode(0) 69 + #expect(zero == Data([0x00])) 70 + 71 + let eight = try DAGCBOREncoder().encode(8) 72 + #expect(eight == Data([0x08])) 73 + 74 + let ten = try DAGCBOREncoder().encode(10) 75 + #expect(ten == Data([0x0a])) 76 + 77 + let twentyThree = try DAGCBOREncoder().encode(23) 78 + #expect(twentyThree == Data([0x17])) 79 + 80 + // Just bigger than 23 81 + let twentyFour = try DAGCBOREncoder().encode(24) 82 + #expect(twentyFour == Data([0x18, 0x18])) 83 + 84 + let twentyFive = try DAGCBOREncoder().encode(25) 85 + #expect(twentyFive == Data([0x18, 0x19])) 86 + 87 + // Bigger 88 + let hundred = try DAGCBOREncoder().encode(100) 89 + #expect(hundred == Data([0x18, 0x64])) 90 + 91 + let thousand = try DAGCBOREncoder().encode(1_000) 92 + #expect(thousand == Data([0x19, 0x03, 0xe8])) 93 + 94 + let million = try DAGCBOREncoder().encode(1_000_000) 95 + #expect(million == Data([0x1a, 0x00, 0x0f, 0x42, 0x40])) 96 + 97 + let trillion = try DAGCBOREncoder().encode(1_000_000_000_000) 98 + #expect(trillion == Data([0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00])) 99 + } 100 + 101 + @Test 102 + func encodeNegativeInts() throws { 103 + // Less than 24 104 + let minusOne = try DAGCBOREncoder().encode(-1) 105 + #expect(minusOne == Data([0x20])) 106 + 107 + let minusTen = try DAGCBOREncoder().encode(-10) 108 + #expect(minusTen == Data([0x29])) 109 + 110 + // Bigger 111 + let minusHundred = try DAGCBOREncoder().encode(-100) 112 + #expect(minusHundred == Data([0x38, 0x63])) 113 + 114 + let minusThousand = try DAGCBOREncoder().encode(-1_000) 115 + #expect(minusThousand == Data([0x39, 0x03, 0xe7])) 116 + 117 + let minusMillion = try DAGCBOREncoder().encode(-1_000_001) 118 + #expect(minusMillion == Data([0x3A, 0x00, 0x0F, 0x42, 0x40])) 119 + 120 + let minusTrillion = try DAGCBOREncoder().encode(-1_000_000_001) 121 + #expect(minusTrillion == Data([0x3A, 0x3B, 0x9A, 0xCA, 0x00])) 122 + } 123 + 124 + @Test 125 + func encodeFloat() throws { 126 + let value: Float = 100000.0 127 + // Different. 128 + let data = "fb40f86a0000000000".asHexData() 129 + let result = try DAGCBOREncoder().encode(value) 130 + #expect(data == result) 131 + } 132 + 133 + @Test 134 + func encodeDouble() throws { 135 + let value: Double = 0.10035 136 + let data = "FB3FB9B089A0275254".asHexData() 137 + let result = try DAGCBOREncoder().encode(value) 138 + #expect(data == result) 139 + } 140 + 141 + @Test(arguments: [Double.nan, Double.infinity, -Double.infinity]) 142 + func NanAndInfiniteThrowErrors(value: Double) throws { 143 + #expect(throws: EncodingError.self) { 144 + try DAGCBOREncoder().encode(value) 145 + } 146 + } 147 + 148 + @Test 149 + func encodeStrings() throws { 150 + let empty = try DAGCBOREncoder().encode("") 151 + #expect(empty == Data([0x60])) 152 + 153 + let a = try DAGCBOREncoder().encode("a") 154 + #expect(a == Data([0x61, 0x61])) 155 + 156 + let IETF = try DAGCBOREncoder().encode("IETF") 157 + #expect(IETF == Data([0x64, 0x49, 0x45, 0x54, 0x46])) 158 + 159 + let quoteSlash = try DAGCBOREncoder().encode("\"\\") 160 + #expect(quoteSlash == Data([0x62, 0x22, 0x5c])) 161 + 162 + let littleUWithDiaeresis = try DAGCBOREncoder().encode("\u{00FC}") 163 + #expect(littleUWithDiaeresis == Data([0x62, 0xc3, 0xbc])) 164 + } 165 + 166 + @Test 167 + func encodeByteStrings() throws { 168 + let fourByteByteString = try DAGCBOREncoder().encode(Data([0x01, 0x02, 0x03, 0x04])) 169 + #expect(fourByteByteString == Data([0x44, 0x01, 0x02, 0x03, 0x04])) 170 + } 171 + 172 + @Test 173 + func mixedByteArraysEncodeCorrectly() throws { 174 + // TODO: Make the container swap to mixed mode if necessary. 175 + 176 + /// See note in ``UnkeyeyedCBOREncodingContainer`` about mixed collections of ints 177 + struct Mixed: Encodable { 178 + func encode(to encoder: any Encoder) throws { 179 + var container = encoder.unkeyedContainer() 180 + try container.encode(UInt8.zero) 181 + try container.encode(UInt8.max) 182 + try container.encode("Hello World") 183 + try container.encode(1000000) 184 + } 185 + } 186 + 187 + let data = try DAGCBOREncoder().encode(Mixed()) 188 + // swiftlint:disable:next line_length 189 + #expect(data == Data([0x84, 0x00, 0x18, 0xff, 0x6b, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x00, 0x0f, 0x42, 0x40])) 190 + } 191 + 192 + @Test 193 + func encodeArrays() throws { 194 + let empty = try DAGCBOREncoder().encode([String]()) 195 + #expect(empty == Data([0x80])) 196 + 197 + let oneTwoThree = try DAGCBOREncoder().encode([1, 2, 3]) 198 + #expect(oneTwoThree == Data([0x83, 0x01, 0x02, 0x03])) 199 + 200 + // swiftlint:disable:next line_length 201 + let lotsOfInts = try DAGCBOREncoder().encode([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]) 202 + 203 + // swiftlint:disable:next line_length 204 + #expect(lotsOfInts == Data([0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19])) 205 + 206 + let nestedSimple = try DAGCBOREncoder().encode([[1], [2, 3], [4, 5]]) 207 + #expect(nestedSimple == Data([0x83, 0x81, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05])) 208 + } 209 + 210 + @Test 211 + func encodeMaps() throws { 212 + let empty = try DAGCBOREncoder().encode([String: String]()) 213 + #expect(empty == Data([0xa0])) 214 + 215 + let stringToString = try DAGCBOREncoder().encode(["a": "A", "b": "B", "c": "C", "d": "D", "e": "E"]) 216 + #expect(stringToString.first! == 0xa5) 217 + 218 + let dataMinusFirstByte = stringToString[1...] 219 + .map { $0 } 220 + .chunked(into: 4) 221 + .sorted(by: { $0.lexicographicallyPrecedes($1) }) 222 + let dataForKeyValuePairs: [[UInt8]] = [ 223 + [0x61, 0x61, 0x61, 0x41], 224 + [0x61, 0x62, 0x61, 0x42], 225 + [0x61, 0x63, 0x61, 0x43], 226 + [0x61, 0x64, 0x61, 0x44], 227 + [0x61, 0x65, 0x61, 0x45] 228 + ] 229 + #expect(dataMinusFirstByte == dataForKeyValuePairs) 230 + 231 + let encoder = DAGCBOREncoder() 232 + let encodedWithStringKeys = try encoder.encode([1: 2, 3: 4]) 233 + #expect( 234 + encodedWithStringKeys == Data([0xa2, 0x61, 0x31, 0x02, 0x61, 0x33, 0x04]) 235 + ) 236 + } 237 + 238 + @Test 239 + func encodeSimpleStructs() throws { 240 + struct MyStruct: Codable { 241 + let age: Int 242 + let name: String 243 + } 244 + 245 + let encoded = try DAGCBOREncoder().encode(MyStruct(age: 27, name: "Ham")).map { $0 } 246 + 247 + #expect( 248 + encoded == [0xa2, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1b, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x63, 0x48, 0x61, 0x6d] 249 + ) 250 + } 251 + 252 + @Test 253 + func encodeMoreComplexStructs() throws { 254 + let encoder = DAGCBOREncoder() 255 + 256 + let data = try encoder.encode(Company.mock) 257 + // swiftlint:disable:next line_length 258 + #expect(data == "A469656D706C6F796565738AA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B67666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F7465646E616D656941636D6520436F7270".asHexData()) 259 + } 260 + 261 + @Test 262 + func uint16() throws { 263 + let encoder = DAGCBOREncoder() 264 + let data = try encoder.encode(1999) 265 + #expect(data == "1907CF".asHexData()) 266 + } 267 + 268 + @Test( 269 + .disabled("Disabled until we can figure out how to make Float16 compile on all platforms."), 270 + arguments: [ 271 + ("F90000", 0), 272 + ("F93C00", 1.0), 273 + ("F9BE00", -1.5), 274 + ("F97C00", .infinity), 275 + ("F93E32", 1.548828125), 276 + ("F9F021", -8456) 277 + ] 278 + ) 279 + func halfFloat(expected: String, value: Float16) throws { 280 + let expectedData = expected.asHexData() 281 + let encoder = DAGCBOREncoder() 282 + let data = try encoder.encode(value) 283 + #expect(data == expectedData) 284 + } 285 + 286 + @Test 287 + func duplicateKeysAreDeduplicatedOnEncode() throws { 288 + let encoder = DAGCBOREncoder() 289 + struct Mock: Encodable { 290 + enum CodingKeys: String, CodingKey { 291 + case one 292 + } 293 + 294 + func encode(to encoder: any Encoder) throws { 295 + var container = encoder.container(keyedBy: CodingKeys.self) 296 + try container.encode(true, forKey: .one) 297 + try container.encode(false, forKey: .one) 298 + } 299 + } 300 + 301 + let data = try encoder.encode(Mock()).hexString() 302 + #expect(data == "a1636f6e65f4") 303 + } 304 + 305 + @Test 306 + func uuid() throws { 307 + let uuid = UUID(uuidString: "A0BDA068-60AD-4111-B4C9-04746791028B") 308 + #expect(throws: EncodingError.self) { 309 + try DAGCBOREncoder().encode(uuid) 310 + } 311 + } 312 + }
+34 -41
Tests/CBORTests/DecodableTests.swift
··· 25 25 value = try CBORDecoder().decode(UInt8.self, from: [23]) 26 26 #expect(value == 23) 27 27 // Just above max arg size 28 - // value = try CBORDecoder().decode(UInt8.self, from: [24, 24]) 29 - // #expect(value == 24) 28 + value = try CBORDecoder().decode(UInt8.self, from: [24, 24]) 29 + #expect(value == 24) 30 30 // Max Int 31 31 value = try CBORDecoder().decode(UInt8.self, from: [24, UInt8.max]) 32 32 #expect(value == UInt8.max) ··· 49 49 value = try CBORDecoder().decode(UInt16.self, from: [23]) 50 50 #expect(value == 23) 51 51 // Just above max arg size 52 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [24, 24]) } 52 + value = try CBORDecoder().decode(UInt16.self, from: [24, 24]) 53 + #expect(value == 24) 53 54 54 55 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [128]) } 55 56 56 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [25, 0, 1]) } 57 - 57 + value = try CBORDecoder().decode(UInt16.self, from: [25, 0, 1]) 58 + #expect(value == 1) 58 59 value = try CBORDecoder().decode(UInt16.self, from: [25, 1, 1]) 59 60 #expect(value == 257) 60 61 value = try CBORDecoder().decode(UInt16.self, from: [25, UInt8.max, UInt8.max]) ··· 73 74 // Just below max arg size 74 75 value = try CBORDecoder().decode(UInt32.self, from: [23]) 75 76 #expect(value == 23) 77 + // Just above max arg size 78 + value = try CBORDecoder().decode(UInt32.self, from: [24, 24]) 79 + #expect(value == 24) 76 80 81 + value = try CBORDecoder().decode(UInt32.self, from: [25, 0, 1]) 82 + #expect(value == 1) 77 83 value = try CBORDecoder().decode(UInt32.self, from: [25, 1, 1]) 78 84 #expect(value == 257) 79 85 value = try CBORDecoder().decode(UInt32.self, from: [25, UInt8.max, UInt8.max]) ··· 82 88 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [25]) } 83 89 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [25, 0]) } 84 90 85 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [26, 0, 0, 0, 0]) } 86 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [26, 0, 0, 0, 1]) } 87 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [26, 0, 0, 1, 1]) } 88 - 91 + value = try CBORDecoder().decode(UInt32.self, from: [26, 0, 0, 0, 0]) 92 + #expect(value == 0) 93 + value = try CBORDecoder().decode(UInt32.self, from: [26, 0, 0, 0, 1]) 94 + #expect(value == 1) 95 + value = try CBORDecoder().decode(UInt32.self, from: [26, 0, 0, 1, 1]) 96 + #expect(value == 257) 89 97 value = try CBORDecoder().decode(UInt32.self, from: [26, 0, 1, 1, 1]) 90 98 #expect(value == 65793) 91 - 92 99 value = try CBORDecoder().decode(UInt32.self, from: [26, UInt8.max, UInt8.max, UInt8.max, UInt8.max]) 93 100 #expect(value == UInt32.max) 94 101 // Missing bytes from end ··· 105 112 // Just below max arg size 106 113 value = try CBORDecoder().decode(UInt64.self, from: [23]) 107 114 #expect(value == 23) 115 + // Just above max arg size 116 + value = try CBORDecoder().decode(UInt64.self, from: [24, 24]) 117 + #expect(value == 24) 108 118 119 + value = try CBORDecoder().decode(UInt64.self, from: [25, 0, 1]) 120 + #expect(value == 1) 109 121 value = try CBORDecoder().decode(UInt64.self, from: [25, 1, 1]) 110 122 #expect(value == 257) 111 123 value = try CBORDecoder().decode(UInt64.self, from: [25, UInt8.max, UInt8.max]) ··· 114 126 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [25]) } 115 127 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [25, 0]) } 116 128 117 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 0, 0]) } 118 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 0, 1]) } 119 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 1, 1]) } 120 - 129 + value = try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 0, 0]) 130 + #expect(value == 0) 131 + value = try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 0, 1]) 132 + #expect(value == 1) 133 + value = try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 1, 1]) 134 + #expect(value == 257) 121 135 value = try CBORDecoder().decode(UInt64.self, from: [26, 0, 1, 1, 1]) 122 136 #expect(value == 65793) 123 137 value = try CBORDecoder().decode(UInt64.self, from: [26, UInt8.max, UInt8.max, UInt8.max, UInt8.max]) ··· 126 140 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26]) } 127 141 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 0]) } 128 142 129 - #expect(throws: DecodingError.self) { 130 - try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 0, 0]) 131 - } 132 - #expect(throws: DecodingError.self) { 133 - try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 0, 1]) 134 - } 135 - #expect(throws: DecodingError.self) { 136 - try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 1, 1]) 137 - } 138 - 143 + value = try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 0, 0]) 144 + #expect(value == 0) 145 + value = try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 0, 1]) 146 + #expect(value == 1) 147 + value = try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 1, 1]) 148 + #expect(value == 257) 139 149 // Missing bytes from end 140 150 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [27]) } 141 151 #expect(throws: DecodingError.self) { 142 152 try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 0]) 143 153 } 144 - } 145 - 146 - @available(macOS 15.0, *) 147 - @Test 148 - func minInt() throws { 149 - let data = "3bffffffffffffffff".asHexData() 150 - let decoded = try CBORDecoder().decode(Int128.self, from: data) 151 - #expect(decoded == -18446744073709551616) 152 154 } 153 155 154 156 @Test ··· 287 289 let data = data.asHexData() 288 290 let decoded = try CBORDecoder().decode(Float.self, from: data) 289 291 #expect(decoded == value) 290 - } 291 - 292 - @Test 293 - func recursionLimit() { 294 - #expect(throws: DecodingError.self) { 295 - // swiftlint:disable:next line_length 296 - let data = "a260818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818180652474797065726170702e62736b792e666565642e70736f74".asHexData() 297 - return try CBORDecoder().decode(AnyDecodable.self, from: data) 298 - } 299 292 } 300 293 }
+2 -2
Tests/CBORTests/DecodeMultipleTests.swift
··· 25 25 value = try CBORDecoder().decodeMultiple(UInt8.self, from: [23, 23]) 26 26 #expect(value == [23, 23]) 27 27 // Just above max arg size 28 - // value = try CBORDecoder().decodeMultiple(UInt8.self, from: [24, 24, 24, 24]) 29 - // #expect(value == [24, 24]) 28 + value = try CBORDecoder().decodeMultiple(UInt8.self, from: [24, 24, 24, 24]) 29 + #expect(value == [24, 24]) 30 30 // Max Int 31 31 value = try CBORDecoder().decodeMultiple(UInt8.self, from: [24, UInt8.max]) 32 32 #expect(value == [UInt8.max])
+2 -2
Tests/CBORTests/EncodableTests.swift
··· 219 219 func encodeMoreComplexStructs() throws { 220 220 let encoder = CBOREncoder() 221 221 222 - let data = try encoder.encode(Company.mock).hexString().uppercased() 222 + let data = try encoder.encode(Company.mock) 223 223 // swiftlint:disable:next line_length 224 - #expect(data == "A4646E616D656941636D6520436F727067666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F746569656D706C6F796565738AA563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5") 224 + #expect(data == "A469656D706C6F796565738AA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B67666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F7465646E616D656941636D6520436F7270".asHexData()) 225 225 } 226 226 227 227 @Test
-8
Tests/CBORTests/RoundTripTests.swift
··· 141 141 let decoded = try CBORDecoder().decode(UUID.self, from: encoded) 142 142 #expect(decoded == value) 143 143 } 144 - 145 - @Test 146 - func map() throws { 147 - let value = ["a": 0] 148 - let encoded = try CBOREncoder().encode(value) 149 - let decoded = try CBORDecoder().decode([String: Int].self, from: encoded) 150 - #expect(decoded == value) 151 - } 152 144 }