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 name: Testing CBOR (ubuntu) 23 needs: swiftlint 24 runs-on: ubuntu-latest 25 - container: swift:6.2-noble 26 steps: 27 - name: Checkout repository 28 uses: actions/checkout@v5 29 - name: Restore .build 30 id: "restore-build" 31 uses: actions/cache/restore@v4 ··· 53 uses: actions/checkout@v5 54 - uses: swift-actions/setup-swift@next 55 with: 56 - swift-version: "6.1" 57 - name: Restore .build 58 id: "restore-build" 59 uses: actions/cache/restore@v4 ··· 62 key: "swiftpm-tests-build-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" 63 restore-keys: "swiftpm-tests-build-${{ runner.os }}-" 64 - name: Building Package 65 - run: set -o pipefail && swift build --build-tests | xcbeautify --renderer github-actions 66 # run: set -o pipefail && swift build --build-tests // --sanitize fuzzer | xcbeautify 67 - name: Cache .build 68 if: steps.restore-build.outputs.cache-hit != 'true' ··· 71 path: .build 72 key: "swiftpm-tests-build-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" 73 - name: Testing Package 74 - run: set -o pipefail && swift test --skip-build | xcbeautify --renderer github-actions 75 # TODO: The Xcode version of the swift toolchain does not come with libfuzzer 76 # What needs to happen is we install the open source toolchain and then we can fuzz here... 77 # - name: Fuzzing For 15 Seconds
··· 22 name: Testing CBOR (ubuntu) 23 needs: swiftlint 24 runs-on: ubuntu-latest 25 + container: swift:6.0-noble 26 steps: 27 - name: Checkout repository 28 uses: actions/checkout@v5 29 + - uses: swift-actions/setup-swift@next 30 + with: 31 + swift-version: "6.0" 32 - name: Restore .build 33 id: "restore-build" 34 uses: actions/cache/restore@v4 ··· 56 uses: actions/checkout@v5 57 - uses: swift-actions/setup-swift@next 58 with: 59 + swift-version: "6.0" 60 - name: Restore .build 61 id: "restore-build" 62 uses: actions/cache/restore@v4 ··· 65 key: "swiftpm-tests-build-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" 66 restore-keys: "swiftpm-tests-build-${{ runner.os }}-" 67 - name: Building Package 68 + run: set -o pipefail && swift build --build-tests | xcbeautify 69 # run: set -o pipefail && swift build --build-tests // --sanitize fuzzer | xcbeautify 70 - name: Cache .build 71 if: steps.restore-build.outputs.cache-hit != 'true' ··· 74 path: .build 75 key: "swiftpm-tests-build-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" 76 - name: Testing Package 77 + run: set -o pipefail && swift test --skip-build | xcbeautify 78 # TODO: The Xcode version of the swift toolchain does not come with libfuzzer 79 # What needs to happen is we install the open source toolchain and then we can fuzz here... 80 # - name: Fuzzing For 15 Seconds
+13 -12
README.md
··· 32 - Supports decoding half precision floats (Float16) as a regular Float. 33 - Runs on Linux, Android, and Windows using the swift-foundation project when available. 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. 38 - Flexible date parsing (tags `0` or `1` with support for any numeric value representation). 39 - 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. 42 43 ## Usage 44 ··· 87 let dagEncoder = DAGCBOREncoder(dateEncodingStrategy: .double) 88 ``` 89 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. 94 ```swift 95 struct CID: CIDType { 96 let data: Data ··· 112 } 113 } 114 ``` 115 - 116 > [!WARNING] 117 > 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). 119 120 Now, any time the encoder finds a `CID` type it will encode it using the correct tag. 121 ```swift ··· 128 // 4A # bytes(10) 129 // 00000102030405060708 # "\u0000\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b" 130 ``` 131 132 ## Documentation 133 ··· 139 140 ```swift 141 dependencies: [ 142 - .package(url: "https://github.com/thecoolwinter/CBOR.git", from: "1.1.0") 143 ] 144 ``` 145
··· 32 - Supports decoding half precision floats (Float16) as a regular Float. 33 - Runs on Linux, Android, and Windows using the swift-foundation project when available. 34 - Fuzz tested for reliability against crashes. 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) 39 - Flexible date parsing (tags `0` or `1` with support for any numeric value representation). 40 - Decoding multiple top-level objects using `decodeMultiple(_:from:)`. 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. 45 46 ## Usage 47 ··· 90 let dagEncoder = DAGCBOREncoder(dateEncodingStrategy: .double) 91 ``` 92 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 ```swift 95 struct CID: CIDType { 96 let data: Data ··· 112 } 113 } 114 ``` 115 > [!WARNING] 116 > 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. 118 119 Now, any time the encoder finds a `CID` type it will encode it using the correct tag. 120 ```swift ··· 127 // 4A # bytes(10) 128 // 00000102030405060708 # "\u0000\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b" 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. 132 133 ## Documentation 134 ··· 140 141 ```swift 142 dependencies: [ 143 + .package(url: "https://github.com/thecoolwinter/CBOR.git", from: "1.0.0") 144 ] 145 ``` 146
+51 -24
Sources/CBOR/Decoder/CBORDecoder.swift
··· 23 public var options: DecodingOptions 24 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 - ) 48 } 49 50 /// Create a new CBOR decoder ··· 78 } 79 } catch { 80 if let error = error as? ScanError { 81 - throw error.decodingError() 82 } else { 83 throw error 84 } ··· 128 } 129 } catch { 130 if let error = error as? ScanError { 131 - throw error.decodingError() 132 } else { 133 throw error 134 } 135 } 136 } 137 }
··· 23 public var options: DecodingOptions 24 25 /// Create a new CBOR decoder. 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) 31 } 32 33 /// Create a new CBOR decoder ··· 61 } 62 } catch { 63 if let error = error as? ScanError { 64 + try throwScanError(error) 65 } else { 66 throw error 67 } ··· 111 } 112 } catch { 113 if let error = error as? ScanError { 114 + try throwScanError(error) 115 } else { 116 throw error 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 162 } 163 } 164 }
-12
Sources/CBOR/Decoder/Containers/DataRegion.swift
··· 107 } 108 } 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 @inlinable 119 func readInt<T: FixedWidthInteger>(as: T.Type, argument: UInt8, from index: Index) throws -> T { 120 let byteCount = argument ··· 126 case 24: 127 let intVal = try read(as: UInt8.self, from: index) 128 try checkIntConversion(T.self, val: intVal) 129 - try checkIntSize(intVal, previousSize: Constants.maxArgSize) 130 return T(intVal) 131 case 25: 132 let intVal = try read(as: UInt16.self, from: index) 133 try checkIntConversion(T.self, val: intVal) 134 - try checkIntSize(intVal, previousSize: UInt8.max) 135 return T(intVal) 136 case 26: 137 let intVal = try read(as: UInt32.self, from: index) 138 try checkIntConversion(T.self, val: intVal) 139 - try checkIntSize(intVal, previousSize: UInt16.max) 140 return T(intVal) 141 case 27: 142 let intVal = try read(as: UInt64.self, from: index) 143 try checkIntConversion(T.self, val: intVal) 144 - try checkIntSize(intVal, previousSize: UInt32.max) 145 return T(intVal) 146 default: 147 throw ScanError.invalidSize(byte: byteCount, offset: startIndex)
··· 107 } 108 } 109 110 @inlinable 111 func readInt<T: FixedWidthInteger>(as: T.Type, argument: UInt8, from index: Index) throws -> T { 112 let byteCount = argument ··· 118 case 24: 119 let intVal = try read(as: UInt8.self, from: index) 120 try checkIntConversion(T.self, val: intVal) 121 return T(intVal) 122 case 25: 123 let intVal = try read(as: UInt16.self, from: index) 124 try checkIntConversion(T.self, val: intVal) 125 return T(intVal) 126 case 26: 127 let intVal = try read(as: UInt32.self, from: index) 128 try checkIntConversion(T.self, val: intVal) 129 return T(intVal) 130 case 27: 131 let intVal = try read(as: UInt64.self, from: index) 132 try checkIntConversion(T.self, val: intVal) 133 return T(intVal) 134 default: 135 throw ScanError.invalidSize(byte: byteCount, offset: startIndex)
+1 -34
Sources/CBOR/Decoder/Containers/KeyedCBORDecodingContainer.swift
··· 12 #endif 13 14 struct KeyedCBORDecodingContainer<Key: CodingKey>: DecodingContextContainer, KeyedDecodingContainerProtocol { 15 - enum AnyKey: Hashable, Comparable { 16 case int(Int) 17 case string(String) 18 ··· 32 Key(stringValue: string) 33 } 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 } 51 52 let context: DecodingContext ··· 66 decodedKeys.reserveCapacity(childCount / 2) 67 68 var mapOffset = context.results.firstChildIndex(data.mapOffset) 69 - var lastKey: AnyKey? 70 for _ in 0..<childCount / 2 { 71 let key = context.results.load(at: mapOffset) 72 let decodedKey: AnyKey 73 switch key.type { 74 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 let intVal = try SingleValueCBORDecodingContainer(context: context, data: key).decode(Int.self) 81 decodedKey = AnyKey.int(intVal) 82 case .string: ··· 93 let value = context.results.load(at: mapOffset) 94 mapOffset = context.results.siblingIndex(mapOffset) 95 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 decodedKeys[decodedKey] = value 108 - lastKey = decodedKey 109 } 110 111 self.decodedKeys = decodedKeys
··· 12 #endif 13 14 struct KeyedCBORDecodingContainer<Key: CodingKey>: DecodingContextContainer, KeyedDecodingContainerProtocol { 15 + enum AnyKey: Hashable { 16 case int(Int) 17 case string(String) 18 ··· 32 Key(stringValue: string) 33 } 34 } 35 } 36 37 let context: DecodingContext ··· 51 decodedKeys.reserveCapacity(childCount / 2) 52 53 var mapOffset = context.results.firstChildIndex(data.mapOffset) 54 for _ in 0..<childCount / 2 { 55 let key = context.results.load(at: mapOffset) 56 let decodedKey: AnyKey 57 switch key.type { 58 case .uint, .nint: 59 let intVal = try SingleValueCBORDecodingContainer(context: context, data: key).decode(Int.self) 60 decodedKey = AnyKey.int(intVal) 61 case .string: ··· 72 let value = context.results.load(at: mapOffset) 73 mapOffset = context.results.siblingIndex(mapOffset) 74 75 decodedKeys[decodedKey] = value 76 } 77 78 self.decodedKeys = decodedKeys
+3 -60
Sources/CBOR/Decoder/Containers/SingleValueCBORDecodingContainer.swift
··· 16 let data: DataRegion 17 } 18 19 - // MARK: - Decoder 20 - 21 extension SingleValueCBORDecodingContainer: Decoder { 22 func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey { 23 try KeyedDecodingContainer(KeyedCBORDecodingContainer(context: context, data: data)) ··· 33 } 34 35 extension SingleValueCBORDecodingContainer: SingleValueDecodingContainer { 36 - // MARK: - Nil 37 - 38 func decodeNil() -> Bool { 39 data.isNil() 40 } 41 42 - // MARK: - Bool 43 - 44 func decode(_: Bool.Type) throws -> Bool { 45 let argument = try checkType(.simple, arguments: 20, 21, as: Bool.self) 46 return argument == 21 47 } 48 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 func decode(_: Float.Type) throws -> Float { 68 let arg = try checkType(.simple, arguments: 25, 26, as: Float.self) 69 if arg == 25 { ··· 73 context.error("Could not decode half-precision float into Swift Float.") 74 ) 75 } 76 - return try checkFloatValidity(value) 77 } 78 79 let floatRaw = try data.read(as: UInt32.self) 80 - return try checkFloatValidity(Float(bitPattern: floatRaw)) 81 } 82 83 func decode(_: Double.Type) throws -> Double { 84 try checkType(.simple, arguments: 27, as: Double.self) 85 let doubleRaw = try data.read(as: UInt64.self).littleEndian 86 - return try checkFloatValidity(Double(bitPattern: doubleRaw)) 87 } 88 - 89 - // MARK: - Integers 90 91 func decode<T: Decodable & FixedWidthInteger>(_: T.Type) throws -> T { 92 try checkType(.uint, .nint, forType: T.self) ··· 103 return value 104 } 105 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 func decode(_: String.Type) throws -> String { 132 try checkType(.string, forType: String.self) 133 ··· 179 } 180 return string 181 } 182 - 183 - // MARK: - Date 184 185 // Attempt first to decode a tagged date value, then move on and try decoding any of the following as a date: 186 // - Int ··· 243 } 244 } 245 246 - // MARK: - Data 247 - 248 private func _decode(_: Data.Type) throws -> Data { 249 try checkType(.bytes, forType: Data.self) 250 ··· 288 } 289 return string 290 } 291 - 292 - // MARK: - Decode 293 294 func decode<T: Decodable>(_ type: T.Type) throws -> T { 295 // Unfortunate force unwraps here, but necessary
··· 16 let data: DataRegion 17 } 18 19 extension SingleValueCBORDecodingContainer: Decoder { 20 func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey { 21 try KeyedDecodingContainer(KeyedCBORDecodingContainer(context: context, data: data)) ··· 31 } 32 33 extension SingleValueCBORDecodingContainer: SingleValueDecodingContainer { 34 func decodeNil() -> Bool { 35 data.isNil() 36 } 37 38 func decode(_: Bool.Type) throws -> Bool { 39 let argument = try checkType(.simple, arguments: 20, 21, as: Bool.self) 40 return argument == 21 41 } 42 43 func decode(_: Float.Type) throws -> Float { 44 let arg = try checkType(.simple, arguments: 25, 26, as: Float.self) 45 if arg == 25 { ··· 49 context.error("Could not decode half-precision float into Swift Float.") 50 ) 51 } 52 + return value 53 } 54 55 let floatRaw = try data.read(as: UInt32.self) 56 + return Float(bitPattern: floatRaw) 57 } 58 59 func decode(_: Double.Type) throws -> Double { 60 try checkType(.simple, arguments: 27, as: Double.self) 61 let doubleRaw = try data.read(as: UInt64.self).littleEndian 62 + return Double(bitPattern: doubleRaw) 63 } 64 65 func decode<T: Decodable & FixedWidthInteger>(_: T.Type) throws -> T { 66 try checkType(.uint, .nint, forType: T.self) ··· 77 return value 78 } 79 80 func decode(_: String.Type) throws -> String { 81 try checkType(.string, forType: String.self) 82 ··· 128 } 129 return string 130 } 131 132 // Attempt first to decode a tagged date value, then move on and try decoding any of the following as a date: 133 // - Int ··· 190 } 191 } 192 193 private func _decode(_: Data.Type) throws -> Data { 194 try checkType(.bytes, forType: Data.self) 195 ··· 233 } 234 return string 235 } 236 237 func decode<T: Decodable>(_ type: T.Type) throws -> T { 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 /// For deterministic encoding, this **must** be enabled. 14 public var rejectIndeterminateLengths: Bool 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 /// 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 - ) { 50 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 } 59 }
··· 13 /// For deterministic encoding, this **must** be enabled. 14 public var rejectIndeterminateLengths: Bool 15 16 /// Create a new options object. 17 + public init(rejectIndeterminateLengths: Bool = true) { 18 self.rejectIndeterminateLengths = rejectIndeterminateLengths 19 } 20 }
+29 -33
Sources/CBOR/Decoder/Scanner/CBORScanner.swift
··· 11 import Foundation 12 #endif 13 14 /// # Why Scan? 15 /// I'd have loved to use a 'pop' method for decoding, where we only decode as data is requested. However, the way 16 /// Swift's decoding APIs work forces us to be able to be able to do random access for keys in maps, which requires ··· 39 40 consuming func scan() throws -> Results { 41 while !reader.isEmpty { 42 - if options.singleTopLevelItem && reader.index > 0 { 43 - throw ScanError.unreadDataAfterEnd 44 - } 45 - 46 let idx = reader.index 47 - try scanNext(depth: 0) 48 assert(idx < reader.index, "Scanner made no forward progress in scan") 49 } 50 - 51 - if options.singleTopLevelItem && !reader.isEmpty { 52 - throw ScanError.unreadDataAfterEnd 53 - } 54 - 55 return results 56 } 57 58 - private mutating func scanNext(depth: Int) throws { 59 guard let type = reader.peekType(), let raw = reader.peek() else { 60 if reader.isEmpty { 61 throw ScanError.unexpectedEndOfData ··· 64 } 65 } 66 67 - guard depth < options.recursionDepth else { 68 - throw ScanError.recursionLimit 69 - } 70 - 71 - try scanType(type: type, raw: raw, depth: depth) 72 } 73 74 - private mutating func scanType(type: MajorType, raw: UInt8, depth: Int) throws { 75 switch type { 76 case .uint, .nint: 77 try scanInt(raw: raw) ··· 80 case .string: 81 try scanBytesOrString(.string, raw: raw) 82 case .array: 83 - try scanArray(depth: depth) 84 case .map: 85 - try scanMap(depth: depth) 86 case .simple: 87 try scanSimple(raw: raw) 88 case .tagged: 89 - try scanTagged(raw: raw, depth: depth) 90 } 91 } 92 ··· 103 // MARK: - Scan Simple 104 105 private mutating func scanSimple(raw: UInt8) throws { 106 - guard !(options.rejectUndefined && reader.peekArgument() == 23) else { 107 - throw ScanError.rejectedUndefined 108 - } 109 - 110 let idx = reader.index 111 results.recordSimple(reader.pop(), currentByteIndex: idx) 112 guard reader.canRead(raw.simpleLength()) else { ··· 154 155 // MARK: - Scan Array 156 157 - private mutating func scanArray(depth: Int) throws { 158 guard peekIsIndeterminate() else { 159 let size = try reader.readNextInt(as: Int.self) 160 let mapIdx = results.recordArrayStart(currentByteIndex: reader.index) 161 for _ in 0..<size { 162 - try scanNext(depth: depth + 1) 163 } 164 results.recordEnd(childCount: size, resultLocation: mapIdx, currentByteIndex: reader.index) 165 return ··· 173 reader.pop() // Pop type 174 var count = 0 175 while reader.peek() != Constants.breakCode { 176 - try scanNext(depth: depth + 1) 177 count += 1 178 } 179 // Pop the break byte ··· 183 184 // MARK: - Scan Map 185 186 - private mutating func scanMap(depth: Int) throws { 187 guard peekIsIndeterminate() else { 188 let keyCount = try reader.readNextInt(as: Int.self) 189 guard keyCount < Int.max / 2 else { ··· 193 let size = keyCount * 2 194 let mapIdx = results.recordMapStart(currentByteIndex: reader.index) 195 for _ in 0..<size { 196 - try scanNext(depth: depth + 1) 197 } 198 results.recordEnd(childCount: size, resultLocation: mapIdx, currentByteIndex: reader.index) 199 return ··· 207 reader.pop() // Pop type 208 var count = 0 209 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) 212 count += 2 213 } 214 // Pop the break byte ··· 218 219 // MARK: - Scan Tagged 220 221 - private mutating func scanTagged(raw: UInt8, depth: Int) throws { 222 // Scan the tag number (passing the raw value here makes it record a Tag rather than an Int) 223 try scanInt(raw: raw) 224 ··· 226 throw ScanError.unexpectedEndOfData 227 } 228 229 - try scanType(type: nextTag, raw: nextRaw, depth: depth) 230 } 231 } 232
··· 11 import Foundation 12 #endif 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 + 27 /// # Why Scan? 28 /// I'd have loved to use a 'pop' method for decoding, where we only decode as data is requested. However, the way 29 /// Swift's decoding APIs work forces us to be able to be able to do random access for keys in maps, which requires ··· 52 53 consuming func scan() throws -> Results { 54 while !reader.isEmpty { 55 let idx = reader.index 56 + try scanNext() 57 assert(idx < reader.index, "Scanner made no forward progress in scan") 58 } 59 return results 60 } 61 62 + private mutating func scanNext() throws { 63 guard let type = reader.peekType(), let raw = reader.peek() else { 64 if reader.isEmpty { 65 throw ScanError.unexpectedEndOfData ··· 68 } 69 } 70 71 + try scanType(type: type, raw: raw) 72 } 73 74 + private mutating func scanType(type: MajorType, raw: UInt8) throws { 75 switch type { 76 case .uint, .nint: 77 try scanInt(raw: raw) ··· 80 case .string: 81 try scanBytesOrString(.string, raw: raw) 82 case .array: 83 + try scanArray() 84 case .map: 85 + try scanMap() 86 case .simple: 87 try scanSimple(raw: raw) 88 case .tagged: 89 + try scanTagged(raw: raw) 90 } 91 } 92 ··· 103 // MARK: - Scan Simple 104 105 private mutating func scanSimple(raw: UInt8) throws { 106 let idx = reader.index 107 results.recordSimple(reader.pop(), currentByteIndex: idx) 108 guard reader.canRead(raw.simpleLength()) else { ··· 150 151 // MARK: - Scan Array 152 153 + private mutating func scanArray() throws { 154 guard peekIsIndeterminate() else { 155 let size = try reader.readNextInt(as: Int.self) 156 let mapIdx = results.recordArrayStart(currentByteIndex: reader.index) 157 for _ in 0..<size { 158 + try scanNext() 159 } 160 results.recordEnd(childCount: size, resultLocation: mapIdx, currentByteIndex: reader.index) 161 return ··· 169 reader.pop() // Pop type 170 var count = 0 171 while reader.peek() != Constants.breakCode { 172 + try scanNext() 173 count += 1 174 } 175 // Pop the break byte ··· 179 180 // MARK: - Scan Map 181 182 + private mutating func scanMap() throws { 183 guard peekIsIndeterminate() else { 184 let keyCount = try reader.readNextInt(as: Int.self) 185 guard keyCount < Int.max / 2 else { ··· 189 let size = keyCount * 2 190 let mapIdx = results.recordMapStart(currentByteIndex: reader.index) 191 for _ in 0..<size { 192 + try scanNext() 193 } 194 results.recordEnd(childCount: size, resultLocation: mapIdx, currentByteIndex: reader.index) 195 return ··· 203 reader.pop() // Pop type 204 var count = 0 205 while reader.peek() != Constants.breakCode { 206 + try scanNext() // Maps should always have a multiple of two values. 207 + try scanNext() 208 count += 2 209 } 210 // Pop the break byte ··· 214 215 // MARK: - Scan Tagged 216 217 + private mutating func scanTagged(raw: UInt8) throws { 218 // Scan the tag number (passing the raw value here makes it record a Tag rather than an Int) 219 try scanInt(raw: raw) 220 ··· 222 throw ScanError.unexpectedEndOfData 223 } 224 225 + try scanType(type: nextTag, raw: nextRaw) 226 } 227 } 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 let tempStorage = TopLevelTemporaryEncodingStorage() 57 58 let encodingContext = EncodingContext(options: options) 59 - let encoder = try SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 60 try encoder.encode(value) 61 62 let dataSize = tempStorage.value.size
··· 56 let tempStorage = TopLevelTemporaryEncodingStorage() 57 58 let encodingContext = EncodingContext(options: options) 59 + let encoder = SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 60 try encoder.encode(value) 61 62 let dataSize = tempStorage.value.size
+2 -2
Sources/CBOR/Encoder/Containers/KeyedCBOREncodingContainer.swift
··· 19 self.context = context 20 } 21 22 - func encoder(for key: CodingKey) throws -> SingleValueCBOREncodingContainer<KeyBuffer.Keyed> { 23 - return try .init(parent: storage.withKey(key), context: context.appending(key)) 24 } 25 26 func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
··· 19 self.context = context 20 } 21 22 + func encoder(for key: CodingKey) -> SingleValueCBOREncodingContainer<KeyBuffer.Keyed> { 23 + return .init(parent: storage.withKey(key), context: context.appending(key)) 24 } 25 26 func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
-15
Sources/CBOR/Encoder/Containers/SingleValueCBOREncodingContainer.swift
··· 19 var options: EncodingOptions { context.options } 20 var codingPath: [CodingKey] { context.codingPath } 21 22 - init(parent: Storage, context: EncodingContext) throws { 23 - self.parent = parent 24 - self.context = context 25 - } 26 - 27 func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey { 28 .init(KeyedCBOREncodingContainer(parent: parent, context: context)) 29 } ··· 69 } 70 71 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 parent.register(IntOptimizer(value: value)) 83 } 84
··· 19 var options: EncodingOptions { context.options } 20 var codingPath: [CodingKey] { context.codingPath } 21 22 func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey { 23 .init(KeyedCBOREncodingContainer(parent: parent, context: context)) 24 } ··· 64 } 65 66 func encode<T>(_ value: T) throws where T: Encodable, T: FixedWidthInteger { 67 parent.register(IntOptimizer(value: value)) 68 } 69
+1 -1
Sources/CBOR/Encoder/DAGCBOREncoder.swift
··· 39 let tempStorage = TopLevelTemporaryEncodingStorage() 40 41 let encodingContext = EncodingContext(options: options) 42 - let encoder = try SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 43 try encoder.encode(value) 44 45 let dataSize = tempStorage.value.size
··· 39 let tempStorage = TopLevelTemporaryEncodingStorage() 40 41 let encodingContext = EncodingContext(options: options) 42 + let encoder = SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 43 try encoder.encode(value) 44 45 let dataSize = tempStorage.value.size
+8 -44
Sources/CBOR/Encoder/Optimizers/KeyedOptimizer.swift
··· 8 @inlinable 9 func KeyedOptimizer(value: [String: EncodingOptimizer]) -> EncodingOptimizer { 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 - ) 22 } 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 - ) 34 } 35 } 36 37 @inlinable 38 func KeyedOptimizer(value: [Int: EncodingOptimizer]) -> EncodingOptimizer { 39 if value.count < Constants.maxArgSize { 40 - return SmallKeyedOptimizer( 41 - value: value, 42 - orderedUsing: { $0.key < $1.key }, 43 - optimizer: { IntOptimizer(value: $0) } 44 - ) 45 } else { 46 - return LargeKeyedOptimizer( 47 - value: value, 48 - orderedUsing: { $0.key < $1.key }, 49 - optimizer: { IntOptimizer(value: $0) } 50 - ) 51 } 52 } 53 ··· 69 @usableFromInline var contentSize: Int 70 71 @usableFromInline 72 - init( 73 - value: [KeyType: EncodingOptimizer], 74 - orderedUsing: ((key: KeyType, value: EncodingOptimizer), (key: KeyType, value: EncodingOptimizer)) -> Bool, 75 - optimizer: (KeyType) -> EncodingOptimizer 76 - ) { 77 var size = 0 78 var array: [KeyValue] = [] 79 array.reserveCapacity(value.count) 80 81 - self.value = value.sorted(by: orderedUsing).reduce(into: array, { array, keyValue in 82 let optimized = KeyValue(key: optimizer(keyValue.key), value: keyValue.value) 83 size += optimized.size 84 array.append(optimized) ··· 106 @usableFromInline var contentSize: Int 107 108 @usableFromInline 109 - init( 110 - value: [KeyType: EncodingOptimizer], 111 - orderedUsing: ((key: KeyType, value: EncodingOptimizer), (key: KeyType, value: EncodingOptimizer)) -> Bool, 112 - optimizer: (KeyType) -> EncodingOptimizer 113 - ) { 114 var size = 0 115 var array: [KeyValue] = [] 116 array.reserveCapacity(value.count) 117 118 - self.value = value.sorted(by: orderedUsing).reduce(into: array, { array, keyValue in 119 let optimized = KeyValue(key: optimizer(keyValue.key), value: keyValue.value) 120 size += optimized.size 121 array.append(optimized)
··· 8 @inlinable 9 func KeyedOptimizer(value: [String: EncodingOptimizer]) -> EncodingOptimizer { 10 if value.count < Constants.maxArgSize { 11 + return SmallKeyedOptimizer(value: value, optimizer: { StringOptimizer(value: $0) }) 12 } else { 13 + return LargeKeyedOptimizer(value: value, optimizer: { StringOptimizer(value: $0) }) 14 } 15 } 16 17 @inlinable 18 func KeyedOptimizer(value: [Int: EncodingOptimizer]) -> EncodingOptimizer { 19 if value.count < Constants.maxArgSize { 20 + return SmallKeyedOptimizer(value: value, optimizer: { IntOptimizer(value: $0) }) 21 } else { 22 + return LargeKeyedOptimizer(value: value, optimizer: { IntOptimizer(value: $0) }) 23 } 24 } 25 ··· 41 @usableFromInline var contentSize: Int 42 43 @usableFromInline 44 + init(value: [KeyType: EncodingOptimizer], optimizer: (KeyType) -> EncodingOptimizer) { 45 var size = 0 46 var array: [KeyValue] = [] 47 array.reserveCapacity(value.count) 48 49 + self.value = value.sorted(by: { $0.key < $1.key }).reduce(into: array, { array, keyValue in 50 let optimized = KeyValue(key: optimizer(keyValue.key), value: keyValue.value) 51 size += optimized.size 52 array.append(optimized) ··· 74 @usableFromInline var contentSize: Int 75 76 @usableFromInline 77 + init(value: [KeyType: EncodingOptimizer], optimizer: (KeyType) -> EncodingOptimizer) { 78 var size = 0 79 var array: [KeyValue] = [] 80 array.reserveCapacity(value.count) 81 82 + self.value = value.sorted(by: { $0.key < $1.key }).reduce(into: array, { array, keyValue in 83 let optimized = KeyValue(key: optimizer(keyValue.key), value: keyValue.value) 84 size += optimized.size 85 array.append(optimized)
+1 -1
Sources/CBOR/Encoder/Optimizers/TaggedItemOptimizer.swift
··· 22 23 init(value: TaggedCBORItem, context: EncodingContext) throws { 24 let storage = TopLevelTemporaryEncodingStorage() 25 - var container = try SingleValueCBOREncodingContainer(parent: storage, context: context) 26 try value.encodeTaggedData(using: &container) 27 sizeOptimizer = IntOptimizer(value: Swift.type(of: value).tag) 28 optimizer = storage.value
··· 22 23 init(value: TaggedCBORItem, context: EncodingContext) throws { 24 let storage = TopLevelTemporaryEncodingStorage() 25 + var container = SingleValueCBOREncodingContainer(parent: storage, context: context) 26 try value.encodeTaggedData(using: &container) 27 sizeOptimizer = IntOptimizer(value: Swift.type(of: value).tag) 28 optimizer = storage.value
+1 -3
Sources/ProfilingHelper/Profiling.swift
··· 15 func time(_ cbor: () throws -> Void, _ json: () throws -> Void) throws { 16 guard #available(macOS 15.0, *) else { fatalError() } 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 - } 21 let avg = values.reduce(0, +) / Double(values.count) 22 23 let variance = values.map { pow($0 - avg, 2) }.reduce(0, +) / Double(values.count)
··· 15 func time(_ cbor: () throws -> Void, _ json: () throws -> Void) throws { 16 guard #available(macOS 15.0, *) else { fatalError() } 17 func calculateStats(_ measurements: [Duration]) -> (average: Double, stddev: Double) { 18 + let values = measurements.map { Double($0.attoseconds) / 1e15 } 19 let avg = values.reduce(0, +) / Double(values.count) 20 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 value = try CBORDecoder().decode(UInt8.self, from: [23]) 26 #expect(value == 23) 27 // Just above max arg size 28 - // value = try CBORDecoder().decode(UInt8.self, from: [24, 24]) 29 - // #expect(value == 24) 30 // Max Int 31 value = try CBORDecoder().decode(UInt8.self, from: [24, UInt8.max]) 32 #expect(value == UInt8.max) ··· 49 value = try CBORDecoder().decode(UInt16.self, from: [23]) 50 #expect(value == 23) 51 // Just above max arg size 52 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [24, 24]) } 53 54 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [128]) } 55 56 - #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [25, 0, 1]) } 57 - 58 value = try CBORDecoder().decode(UInt16.self, from: [25, 1, 1]) 59 #expect(value == 257) 60 value = try CBORDecoder().decode(UInt16.self, from: [25, UInt8.max, UInt8.max]) ··· 73 // Just below max arg size 74 value = try CBORDecoder().decode(UInt32.self, from: [23]) 75 #expect(value == 23) 76 77 value = try CBORDecoder().decode(UInt32.self, from: [25, 1, 1]) 78 #expect(value == 257) 79 value = try CBORDecoder().decode(UInt32.self, from: [25, UInt8.max, UInt8.max]) ··· 82 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [25]) } 83 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [25, 0]) } 84 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 - 89 value = try CBORDecoder().decode(UInt32.self, from: [26, 0, 1, 1, 1]) 90 #expect(value == 65793) 91 - 92 value = try CBORDecoder().decode(UInt32.self, from: [26, UInt8.max, UInt8.max, UInt8.max, UInt8.max]) 93 #expect(value == UInt32.max) 94 // Missing bytes from end ··· 105 // Just below max arg size 106 value = try CBORDecoder().decode(UInt64.self, from: [23]) 107 #expect(value == 23) 108 109 value = try CBORDecoder().decode(UInt64.self, from: [25, 1, 1]) 110 #expect(value == 257) 111 value = try CBORDecoder().decode(UInt64.self, from: [25, UInt8.max, UInt8.max]) ··· 114 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [25]) } 115 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [25, 0]) } 116 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 - 121 value = try CBORDecoder().decode(UInt64.self, from: [26, 0, 1, 1, 1]) 122 #expect(value == 65793) 123 value = try CBORDecoder().decode(UInt64.self, from: [26, UInt8.max, UInt8.max, UInt8.max, UInt8.max]) ··· 126 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26]) } 127 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 0]) } 128 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 - 139 // Missing bytes from end 140 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [27]) } 141 #expect(throws: DecodingError.self) { 142 try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 0]) 143 } 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 } 153 154 @Test ··· 287 let data = data.asHexData() 288 let decoded = try CBORDecoder().decode(Float.self, from: data) 289 #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 } 300 }
··· 25 value = try CBORDecoder().decode(UInt8.self, from: [23]) 26 #expect(value == 23) 27 // Just above max arg size 28 + value = try CBORDecoder().decode(UInt8.self, from: [24, 24]) 29 + #expect(value == 24) 30 // Max Int 31 value = try CBORDecoder().decode(UInt8.self, from: [24, UInt8.max]) 32 #expect(value == UInt8.max) ··· 49 value = try CBORDecoder().decode(UInt16.self, from: [23]) 50 #expect(value == 23) 51 // Just above max arg size 52 + value = try CBORDecoder().decode(UInt16.self, from: [24, 24]) 53 + #expect(value == 24) 54 55 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [128]) } 56 57 + value = try CBORDecoder().decode(UInt16.self, from: [25, 0, 1]) 58 + #expect(value == 1) 59 value = try CBORDecoder().decode(UInt16.self, from: [25, 1, 1]) 60 #expect(value == 257) 61 value = try CBORDecoder().decode(UInt16.self, from: [25, UInt8.max, UInt8.max]) ··· 74 // Just below max arg size 75 value = try CBORDecoder().decode(UInt32.self, from: [23]) 76 #expect(value == 23) 77 + // Just above max arg size 78 + value = try CBORDecoder().decode(UInt32.self, from: [24, 24]) 79 + #expect(value == 24) 80 81 + value = try CBORDecoder().decode(UInt32.self, from: [25, 0, 1]) 82 + #expect(value == 1) 83 value = try CBORDecoder().decode(UInt32.self, from: [25, 1, 1]) 84 #expect(value == 257) 85 value = try CBORDecoder().decode(UInt32.self, from: [25, UInt8.max, UInt8.max]) ··· 88 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [25]) } 89 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [25, 0]) } 90 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) 97 value = try CBORDecoder().decode(UInt32.self, from: [26, 0, 1, 1, 1]) 98 #expect(value == 65793) 99 value = try CBORDecoder().decode(UInt32.self, from: [26, UInt8.max, UInt8.max, UInt8.max, UInt8.max]) 100 #expect(value == UInt32.max) 101 // Missing bytes from end ··· 112 // Just below max arg size 113 value = try CBORDecoder().decode(UInt64.self, from: [23]) 114 #expect(value == 23) 115 + // Just above max arg size 116 + value = try CBORDecoder().decode(UInt64.self, from: [24, 24]) 117 + #expect(value == 24) 118 119 + value = try CBORDecoder().decode(UInt64.self, from: [25, 0, 1]) 120 + #expect(value == 1) 121 value = try CBORDecoder().decode(UInt64.self, from: [25, 1, 1]) 122 #expect(value == 257) 123 value = try CBORDecoder().decode(UInt64.self, from: [25, UInt8.max, UInt8.max]) ··· 126 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [25]) } 127 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [25, 0]) } 128 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) 135 value = try CBORDecoder().decode(UInt64.self, from: [26, 0, 1, 1, 1]) 136 #expect(value == 65793) 137 value = try CBORDecoder().decode(UInt64.self, from: [26, UInt8.max, UInt8.max, UInt8.max, UInt8.max]) ··· 140 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26]) } 141 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [26, 0, 0, 0]) } 142 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) 149 // Missing bytes from end 150 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt64.self, from: [27]) } 151 #expect(throws: DecodingError.self) { 152 try CBORDecoder().decode(UInt64.self, from: [27, 0, 0, 0, 0, 0, 0, 0]) 153 } 154 } 155 156 @Test ··· 289 let data = data.asHexData() 290 let decoded = try CBORDecoder().decode(Float.self, from: data) 291 #expect(decoded == value) 292 } 293 }
+2 -2
Tests/CBORTests/DecodeMultipleTests.swift
··· 25 value = try CBORDecoder().decodeMultiple(UInt8.self, from: [23, 23]) 26 #expect(value == [23, 23]) 27 // Just above max arg size 28 - // value = try CBORDecoder().decodeMultiple(UInt8.self, from: [24, 24, 24, 24]) 29 - // #expect(value == [24, 24]) 30 // Max Int 31 value = try CBORDecoder().decodeMultiple(UInt8.self, from: [24, UInt8.max]) 32 #expect(value == [UInt8.max])
··· 25 value = try CBORDecoder().decodeMultiple(UInt8.self, from: [23, 23]) 26 #expect(value == [23, 23]) 27 // Just above max arg size 28 + value = try CBORDecoder().decodeMultiple(UInt8.self, from: [24, 24, 24, 24]) 29 + #expect(value == [24, 24]) 30 // Max Int 31 value = try CBORDecoder().decodeMultiple(UInt8.self, from: [24, UInt8.max]) 32 #expect(value == [UInt8.max])
+2 -2
Tests/CBORTests/EncodableTests.swift
··· 219 func encodeMoreComplexStructs() throws { 220 let encoder = CBOREncoder() 221 222 - let data = try encoder.encode(Company.mock).hexString().uppercased() 223 // swiftlint:disable:next line_length 224 - #expect(data == "A4646E616D656941636D6520436F727067666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F746569656D706C6F796565738AA563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5") 225 } 226 227 @Test
··· 219 func encodeMoreComplexStructs() throws { 220 let encoder = CBOREncoder() 221 222 + let data = try encoder.encode(Company.mock) 223 // swiftlint:disable:next line_length 224 + #expect(data == "A469656D706C6F796565738AA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B67666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F7465646E616D656941636D6520436F7270".asHexData()) 225 } 226 227 @Test
-8
Tests/CBORTests/RoundTripTests.swift
··· 141 let decoded = try CBORDecoder().decode(UUID.self, from: encoded) 142 #expect(decoded == value) 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 }
··· 141 let decoded = try CBORDecoder().decode(UUID.self, from: encoded) 142 #expect(decoded == value) 143 } 144 }