···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
00029 - 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.
038- 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.
004243## Usage
44···87let dagEncoder = DAGCBOREncoder(dateEncodingStrategy: .double)
88```
8990-> [!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
95struct 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).
119120Now, 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```
00131132## Documentation
133···139140```swift
141dependencies: [
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.
4546## Usage
47···90let dagEncoder = DAGCBOREncoder(dateEncodingStrategy: .double)
91```
9293+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.
00094```swift
95struct CID: CIDType {
96 let data: Data
···112 }
113}
114```
0115> [!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.
118119Now, 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.
132133## Documentation
134···140141```swift
142dependencies: [
143+ .package(url: "https://github.com/thecoolwinter/CBOR.git", from: "1.0.0")
144]
145```
146
···16 let data: DataRegion
17}
1819-// MARK: - Decoder
20-21extension 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}
3435extension SingleValueCBORDecodingContainer: SingleValueDecodingContainer {
36- // MARK: - Nil
37-38 func decodeNil() -> Bool {
39 data.isNil()
40 }
4142- // 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 }
4849- // 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 }
7879 let floatRaw = try data.read(as: UInt32.self)
80- return try checkFloatValidity(Float(bitPattern: floatRaw))
81 }
8283 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
9091 func decode<T: Decodable & FixedWidthInteger>(_: T.Type) throws -> T {
92 try checkType(.uint, .nint, forType: T.self)
···103 return value
104 }
105106- @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
184185 // 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 }
245246- // 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
293294 func decode<T: Decodable>(_ type: T.Type) throws -> T {
295 // Unfortunate force unwraps here, but necessary
···16 let data: DataRegion
17}
180019extension 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}
3233extension SingleValueCBORDecodingContainer: SingleValueDecodingContainer {
0034 func decodeNil() -> Bool {
35 data.isNil()
36 }
370038 func decode(_: Bool.Type) throws -> Bool {
39 let argument = try checkType(.simple, arguments: 20, 21, as: Bool.self)
40 return argument == 21
41 }
4200000000000000000043 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 }
5455 let floatRaw = try data.read(as: UInt32.self)
56+ return Float(bitPattern: floatRaw)
57 }
5859 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 }
006465 func decode<T: Decodable & FixedWidthInteger>(_: T.Type) throws -> T {
66 try checkType(.uint, .nint, forType: T.self)
···77 return value
78 }
79000000000000000000000000080 func decode(_: String.Type) throws -> String {
81 try checkType(.string, forType: String.self)
82···128 }
129 return string
130 }
00131132 // 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 }
19200193 private func _decode(_: Data.Type) throws -> Data {
194 try checkType(.bytes, forType: Data.self)
195···233 }
234 return string
235 }
00236237 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-}
···13 /// For deterministic encoding, this **must** be enabled.
14 public var rejectIndeterminateLengths: Bool
1516- /// 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
150000000000000000000000016 /// Create a new options object.
17+ public init(rejectIndeterminateLengths: Bool = true) {
00000000018 self.rejectIndeterminateLengths = rejectIndeterminateLengths
000000019 }
20}
+29-33
Sources/CBOR/Decoder/Scanner/CBORScanner.swift
···11import Foundation
12#endif
13000000000000014/// # 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
···3940 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 }
5758- 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 }
6667- guard depth < options.recursionDepth else {
68- throw ScanError.recursionLimit
69- }
70-71- try scanType(type: type, raw: raw, depth: depth)
72 }
7374- 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
104105 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 {
···154155 // MARK: - Scan Array
156157- 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
···183184 // MARK: - Scan Map
185186- 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
···218219 // MARK: - Scan Tagged
220221- 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 }
228229- try scanType(type: nextTag, raw: nextRaw, depth: depth)
230 }
231}
232
···11import Foundation
12#endif
1314+@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
···5253 consuming func scan() throws -> Results {
54 while !reader.isEmpty {
000055 let idx = reader.index
56+ try scanNext()
57 assert(idx < reader.index, "Scanner made no forward progress in scan")
58 }
0000059 return results
60 }
6162+ 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 }
7071+ try scanType(type: type, raw: raw)
000072 }
7374+ 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
104105 private mutating func scanSimple(raw: UInt8) throws {
0000106 let idx = reader.index
107 results.recordSimple(reader.pop(), currentByteIndex: idx)
108 guard reader.canRead(raw.simpleLength()) else {
···150151 // MARK: - Scan Array
152153+ 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
···179180 // MARK: - Scan Map
181182+ 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
···214215 // MARK: - Scan Tagged
216217+ 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 }
224225+ 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-// 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