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

linting

+140 -78
+29 -9
Sources/CBOR/Decoder/CBORDecoder.swift
··· 7 7 8 8 import Foundation 9 9 10 + /// Decodes ``Decodable`` objects from CBOR data. 11 + /// 12 + /// This type can be reused efficiently for multiple deserialization operations. Use the ``decode(_:from:)`` method 13 + /// to decode data. 14 + /// 15 + /// To configure decoding behavior, pass options to the ``init(rejectIndeterminateLengths:)`` method or modify 16 + /// the ``options`` variable. 10 17 public struct CBORDecoder { 11 - var options: DecodingOptions 18 + /// The options that determine decoding behavior. 19 + public var options: DecodingOptions 12 20 13 - public init(options: DecodingOptions = DecodingOptions()) { 14 - self.options = options 21 + /// Creates a new decoder. 22 + /// - Parameter rejectIndeterminateLengths: Set to `false` to allow indeterminate length objects to be decoded. 23 + /// Defaults to *rejecting* indeterminate length items (strings, bytes, 24 + /// maps, and arrays). 25 + public init(rejectIndeterminateLengths: Bool = true) { 26 + self.options = DecodingOptions(rejectIndeterminateLengths: rejectIndeterminateLengths) 15 27 } 16 28 29 + /// Decodes the given type from CBOR binary data. 30 + /// - Parameters: 31 + /// - type: The decodable type to deserialize. 32 + /// - data: The CBOR data to decode from. 33 + /// - Returns: An instance of the decoded type. 34 + /// - Throws: A ``DecodingError`` with context and a debug description for a failed deserialization operation. 17 35 public func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T { 18 36 do { 19 37 return try data.withUnsafeBytes { ··· 31 49 if let error = error as? CBORScanner.ScanError { 32 50 switch error { 33 51 case .unexpectedEndOfData: 34 - throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Unexpected end of data.")) 35 - case .invalidMajorType(let byte, let offset): 52 + throw DecodingError.dataCorrupted( 53 + .init(codingPath: [], debugDescription: "Unexpected end of data.") 54 + ) 55 + case let .invalidMajorType(byte, offset): 36 56 throw DecodingError.dataCorrupted(.init( 37 57 codingPath: [], 38 58 debugDescription: "Unexpected major type: \(String(byte, radix: 2)) at offset \(offset)" 39 59 )) 40 - case .invalidSize(let byte, let offset): 60 + case let .invalidSize(byte, offset): 41 61 throw DecodingError.dataCorrupted(.init( 42 62 codingPath: [], 43 63 debugDescription: "Unexpected size argument: \(String(byte, radix: 2)) at offset \(offset)" 44 64 )) 45 - case .expectedMajorType(let offset): 65 + case let .expectedMajorType(offset): 46 66 throw DecodingError.dataCorrupted(.init( 47 67 codingPath: [], 48 68 debugDescription: "Expected major type at offset \(offset)" 49 69 )) 50 - case .typeInIndeterminateString(let type, let offset): 70 + case let .typeInIndeterminateString(type, offset): 51 71 throw DecodingError.dataCorrupted(.init( 52 72 codingPath: [], 53 73 debugDescription: "Unexpected major type in indeterminate \(type) at offset \(offset)" 54 74 )) 55 - case .rejectedIndeterminateLength(let type, let offset): 75 + case let .rejectedIndeterminateLength(type, offset): 56 76 throw DecodingError.dataCorrupted(.init( 57 77 codingPath: [], 58 78 debugDescription: "Rejected indeterminate length type \(type) at offset \(offset)"
+1 -1
Sources/CBOR/Decoder/Containers/KeyedCBORDecodingContainer.swift
··· 99 99 func nestedContainer<NestedKey: CodingKey >( 100 100 keyedBy type: NestedKey.Type, 101 101 forKey key: Key 102 - ) throws -> KeyedDecodingContainer<NestedKey>{ 102 + ) throws -> KeyedDecodingContainer<NestedKey> { 103 103 let region = try getRegion(forKey: key) 104 104 let container = try KeyedCBORDecodingContainer<NestedKey>(context: context.appending(key), data: region) 105 105 return KeyedDecodingContainer(container)
+3 -1
Sources/CBOR/Decoder/Containers/SingleValueCBORDecodingContainer.swift
··· 13 13 } 14 14 15 15 extension SingleValueCBORDecodingContainer: Decoder { 16 - func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey { 16 + func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey { 17 17 try KeyedDecodingContainer(KeyedCBORDecodingContainer(context: context, data: data)) 18 18 } 19 19 ··· 215 215 } 216 216 217 217 func decode<T: Decodable>(_ type: T.Type) throws -> T { 218 + // swiftlint:disable force_cast 218 219 return if T.self == Date.self { 219 220 try _decode(Date.self) as! T // Unfortunate force unwrap, but necessary 220 221 } else if T.self == UUID.self { ··· 224 225 } else { 225 226 try T(from: self) 226 227 } 228 + // swiftlint:enable force_cast 227 229 } 228 230 }
+2 -2
Sources/CBOR/Decoder/Containers/UnkeyedCBORDecodingContainer.swift
··· 55 55 ) throws -> KeyedDecodingContainer<NestedKey> { 56 56 try consumeDecoder().container(keyedBy: type) 57 57 } 58 - 58 + 59 59 mutating func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer { 60 60 try consumeDecoder().unkeyedContainer() 61 61 } 62 - 62 + 63 63 mutating func superDecoder() throws -> Decoder { 64 64 try consumeDecoder() 65 65 }
+9 -14
Sources/CBOR/Decoder/DecodingOptions.swift
··· 5 5 // Created by Khan Winter on 8/23/25. 6 6 // 7 7 8 + /// Options that determine the behavior of ``CBORDecoder``. 8 9 public struct DecodingOptions { 9 - public var rejectIndeterminateLengthData: Bool 10 - public var rejectIndeterminateLengthStrings: Bool 11 - public var rejectIndeterminateLengthArrays: Bool 12 - public var rejectIndeterminateLengthMaps: Bool 10 + /// Set to `false` to allow indeterminate length objects to be decoded. 11 + /// `true` by default. 12 + /// 13 + /// For deterministic encoding, this **must** be enabled. 14 + public var rejectIndeterminateLengths: Bool 13 15 14 - public init( 15 - rejectIndeterminateLengthData: Bool = true, 16 - rejectIndeterminateLengthStrings: Bool = true, 17 - rejectIndeterminateLengthArrays: Bool = true, 18 - rejectIndeterminateLengthMaps: Bool = true 19 - ) { 20 - self.rejectIndeterminateLengthData = rejectIndeterminateLengthData 21 - self.rejectIndeterminateLengthStrings = rejectIndeterminateLengthStrings 22 - self.rejectIndeterminateLengthArrays = rejectIndeterminateLengthArrays 23 - self.rejectIndeterminateLengthMaps = rejectIndeterminateLengthMaps 16 + /// Create a new options object. 17 + public init(rejectIndeterminateLengths: Bool = true) { 18 + self.rejectIndeterminateLengths = rejectIndeterminateLengths 24 19 } 25 20 }
+62 -35
Sources/CBOR/Decoder/Scanner/CBORScanner.swift
··· 7 7 8 8 import Foundation 9 9 10 - /// # Why? 11 - /// I'd have loved to use a 'pop' method for this, where we only decode as data is requested. However, the way Swift's 12 - /// decoding APIs work forces us to be able to be able to do random access for keys in maps, which requires scanning. 10 + /// # Why Scan? 11 + /// I'd have loved to use a 'pop' method for decoding, where we only decode as data is requested. However, the way 12 + /// Swift's decoding APIs work forces us to be able to be able to do random access for keys in maps, which requires 13 + /// scanning. 13 14 /// 14 15 /// Here we build a map of byte offsets and types to be able to quickly scan through a CBOR blob to find specific 15 16 /// indices and keys. 17 + /// 18 + /// # Dev Notes 19 + /// 20 + /// - This is where we do any indeterminate length validation and rejection. The decoder containers themselves will 21 + /// take either indeterminate or specific lengths and decode them. 16 22 @usableFromInline 17 23 final class CBORScanner { 18 24 @usableFromInline ··· 25 31 case rejectedIndeterminateLength(type: MajorType, offset: Int) 26 32 } 27 33 28 - // enum ScanItem: Int { 29 - // case map // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 30 - // case array // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 31 - // 32 - // case int // (offset: Int, byteCount: Int) 33 - // case string 34 - // case byteString 35 - // case tagged 36 - // case simple (byteCount: Int) 37 - // } 34 + // MARK: - Results 38 35 36 + /// After the scanner scans, this contains a map that allows the CBOR data to be scanned for values at arbitrary 37 + /// positions, keys, etc. The map contents are represented literally as ints for performance but uses the 38 + /// following map: 39 + /// ``` 40 + /// enum ScanItem: Int { 41 + /// case map // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 42 + /// case array // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 43 + /// 44 + /// case int // (offset: Int, byteCount: Int) 45 + /// case string 46 + /// case byteString 47 + /// case tagged 48 + /// case simple (byteCount: Int) 49 + /// } 50 + /// ``` 39 51 struct Results { 40 52 var map: [Int] = [] 41 53 ··· 126 138 } 127 139 } 128 140 141 + // MARK: - Map Navigation 142 + 129 143 func firstChildIndex(_ mapIndex: Int) -> Int { 130 144 let byte = UInt8(results.map[mapIndex]) 131 145 guard let type = MajorType(rawValue: byte) else { ··· 173 187 } 174 188 } 175 189 176 - 177 190 switch type { 178 - case .uint: 179 - let size = try popByteCount() 180 - let offset = reader.index 181 - results.recordType(raw, currentByteIndex: offset, length: size) 182 - guard reader.canRead(size) else { throw ScanError.unexpectedEndOfData } 183 - reader.pop(size) 184 - case .nint: 185 - let size = try popByteCount() 186 - let offset = reader.index 187 - results.recordType(raw, currentByteIndex: offset, length: size) 188 - guard reader.canRead(size) else { throw ScanError.unexpectedEndOfData } 189 - reader.pop(size) 191 + case .uint, .nint: 192 + try scanInt(raw: raw) 190 193 case .bytes: 191 194 try scanBytesOrString(.bytes) 192 195 case .string: ··· 196 199 case .map: 197 200 try scanMap() 198 201 case .simple: 199 - let idx = reader.index 200 - results.recordSimple(reader.pop(), currentByteIndex: idx) 201 - reader.pop(simpleLength(raw)) 202 + scanSimple(raw: raw) 202 203 case .tagged: 203 204 fatalError() 204 205 } 205 206 } 206 207 208 + // MARK: - Scan Int 209 + 210 + private func scanInt(raw: UInt8) throws { 211 + let size = try popByteCount() 212 + let offset = reader.index 213 + results.recordType(raw, currentByteIndex: offset, length: size) 214 + guard reader.canRead(size) else { throw ScanError.unexpectedEndOfData } 215 + reader.pop(size) 216 + } 217 + 218 + // MARK: - Scan Simple 219 + 220 + private func scanSimple(raw: UInt8) { 221 + let idx = reader.index 222 + results.recordSimple(reader.pop(), currentByteIndex: idx) 223 + reader.pop(simpleLength(raw)) 224 + } 225 + 207 226 private func simpleLength(_ arg: UInt8) -> Int { 208 227 switch arg & 0b11111 { 209 228 case 25: ··· 216 235 0 // Just this byte. 217 236 } 218 237 } 238 + 239 + // MARK: - Scan String/Bytes 219 240 220 241 private func scanBytesOrString(_ type: MajorType) throws { 221 242 let raw = reader._peek() // already checked previously ··· 229 250 return 230 251 } 231 252 232 - if (type == .string && options.rejectIndeterminateLengthStrings) 233 - || (type == .bytes && options.rejectIndeterminateLengthData) { 253 + if (type == .string || type == .bytes) && options.rejectIndeterminateLengths { 234 254 throw ScanError.rejectedIndeterminateLength(type: type, offset: reader.index) 235 255 } 236 256 ··· 255 275 results.recordType(raw, currentByteIndex: start, length: reader.index - start) 256 276 } 257 277 278 + // MARK: - Scan Array 279 + 258 280 private func scanArray() throws { 259 281 guard peekIsIndeterminate() else { 260 282 let size = try reader.readNextInt(as: Int.self) ··· 266 288 return 267 289 } 268 290 269 - if options.rejectIndeterminateLengthArrays { 291 + if options.rejectIndeterminateLengths { 270 292 throw ScanError.rejectedIndeterminateLength(type: .array, offset: reader.index) 271 293 } 272 294 ··· 281 303 reader.pop() 282 304 results.recordEnd(childCount: count, resultLocation: mapIdx, currentByteIndex: reader.index) 283 305 } 306 + 307 + // MARK: - Scan Map 284 308 285 309 private func scanMap() throws { 286 310 guard peekIsIndeterminate() else { ··· 293 317 return 294 318 } 295 319 296 - if options.rejectIndeterminateLengthMaps { 320 + if options.rejectIndeterminateLengths { 297 321 throw ScanError.rejectedIndeterminateLength(type: .map, offset: reader.index) 298 322 } 299 323 ··· 311 335 } 312 336 } 313 337 338 + // MARK: - Utils 339 + 314 340 extension CBORScanner { 315 341 func popByteCount() throws -> Int { 316 342 let byteCount = reader.popArgument() ··· 330 356 } 331 357 } 332 358 359 + // MARK: - Debug Description 360 + 333 361 #if DEBUG 334 362 extension CBORScanner: CustomDebugStringConvertible { 335 - @usableFromInline 336 - var debugDescription: String { 363 + @usableFromInline var debugDescription: String { 337 364 var string = "" 338 365 func indent(_ other: String, d: Int) { string += String(repeating: " ", count: d * 2) + other + "\n" } 339 366
+5
Sources/CBOR/Decoder/Scanner/DataReader.swift
··· 7 7 8 8 import Foundation 9 9 10 + /// A mutable struct used by the `CBORScanner` to iteratively scan a CBOR blob. 11 + /// Since this isn't passed by reference, this represents the *entire* blob instead of a single value 12 + /// like `DataRegion`. 13 + /// 14 + /// This results in some duplicated code. I'd love to remove it but it works for now I suppose. 10 15 struct DataReader { 11 16 private let data: Slice<UnsafeRawBufferPointer> 12 17 private(set) var index = 0
+2 -2
Sources/CBOR/Encoder/CBOREncoder.swift
··· 11 11 import Foundation 12 12 #endif 13 13 14 - /// An object that can serialize ``Codable`` objects into the CBOR serialization format. 14 + /// Serializes ``Encodable`` objects using the CBOR serialization format. 15 15 /// 16 16 /// To perform serialization, use the ``encode(_:)-6zhmp`` method to convert a Codable object to ``Data``. To 17 17 /// configure encoding behavior, either pass customization options in with ··· 20 20 /// Options that determine the behavior of ``CBOREncoder``. 21 21 public var options: EncodingOptions 22 22 23 - /// Create a new CBOR encoder object. 23 + /// Create a new CBOR encoder. 24 24 /// - Parameters: 25 25 /// - forceStringKeys: See ``EncodingOptions/forceStringKeys``. 26 26 /// - useStringDates: See ``EncodingOptions/useStringDates``.
+1 -1
Sources/CBOR/Encoder/Optimizers/IntOptimizer.swift
··· 5 5 // Created by Khan Winter on 8/17/25. 6 6 // 7 7 8 - @inlinable 8 + @inlinable // swiftlint:disable:next cyclomatic_complexity 9 9 func IntOptimizer<IntType: FixedWidthInteger>(value: IntType) -> EncodingOptimizer { 10 10 let encodingValue: UInt 11 11 if value < 0 {
+23 -12
Tests/CBORTests/DecodableTests.swift
··· 9 9 import Foundation 10 10 @testable import CBOR 11 11 12 - //@_optimize(none) 13 - //public func blackHole(_: some Any) {} 12 + // @_optimize(none) 13 + // public func blackHole(_: some Any) {} 14 14 15 15 @Suite 16 16 struct DecodableTests { 17 - @Test func uint8() throws { 17 + @Test 18 + func uint8() throws { 18 19 var value = try CBORDecoder().decode(UInt8.self, from: [0]) 19 20 #expect(value == 0) 20 21 value = try CBORDecoder().decode(UInt8.self, from: [1]) ··· 32 33 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt8.self, from: [128]) } 33 34 } 34 35 35 - @Test func uint16() throws { 36 + @Test 37 + func uint16() throws { 36 38 var value = try CBORDecoder().decode(UInt16.self, from: [0]) 37 39 #expect(value == 0) 38 40 value = try CBORDecoder().decode(UInt16.self, from: [1]) ··· 57 59 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt16.self, from: [25, 0]) } 58 60 } 59 61 60 - @Test func uint32() throws { 62 + @Test 63 + func uint32() throws { 61 64 var value: UInt32 = try CBORDecoder().decode(UInt32.self, from: [0]) 62 65 #expect(value == 0) 63 66 value = try CBORDecoder().decode(UInt32.self, from: [1]) ··· 94 97 #expect(throws: DecodingError.self) { try CBORDecoder().decode(UInt32.self, from: [26, 0, 0, 0]) } 95 98 } 96 99 97 - @Test func uint64() throws { 100 + @Test 101 + func uint64() throws { 98 102 var value: UInt64 = try CBORDecoder().decode(UInt64.self, from: [0]) 99 103 #expect(value == 0) 100 104 value = try CBORDecoder().decode(UInt64.self, from: [1]) ··· 163 167 ("6b68656c6c6f20776f726c64", "hello world"), 164 168 ("60", ""), 165 169 ("66e29da4efb88f", "❤️"), 170 + // swiftlint:disable:next line_length 166 171 ("782F68656C6C6F20776F726C642068656C6C6F20776F726C642068656C6C6F20776F726C642068656C6C6F20776F726C64", "hello world hello world hello world hello world") 167 172 ]) 168 173 func string(data: String, expected: String) throws { ··· 182 187 #expect(string == expected) 183 188 } 184 189 185 - @Test func emptyMap() throws { 190 + @Test 191 + func emptyMap() throws { 186 192 let data = "A0".asHexData() 187 193 let dictionary = try CBORDecoder().decode([String: Int].self, from: data) 188 194 #expect(dictionary.isEmpty) 189 195 } 190 196 191 - @Test func simpleMap() throws { 197 + @Test 198 + func simpleMap() throws { 192 199 let data = "A262414201614102".asHexData() 193 200 let dictionary = try CBORDecoder().decode([String: Int].self, from: data) 194 201 #expect(dictionary == ["AB": 1, "A": 2]) 195 202 } 196 203 197 - @Test func unkeyedContainerHasCountForIndeterminate() throws { 204 + @Test 205 + func unkeyedContainerHasCountForIndeterminate() throws { 198 206 let data = "9F0203FF".asHexData() 199 207 try data.withUnsafeBytes { 200 208 let data = $0[...] ··· 223 231 } 224 232 } 225 233 226 - @Test func array() throws { 234 + @Test 235 + func array() throws { 227 236 let twentyItems = "940101010101010101010101010101010101010101".asHexData() 228 237 #expect(try CBORDecoder().decode([Int].self, from: twentyItems) == Array(repeating: 1, count: 20)) 229 238 } 230 239 231 - @Test func indeterminateArray() throws { 240 + @Test 241 + func indeterminateArray() throws { 232 242 // let array = "9F0203FF".asHexData() 233 243 let options = DecodingOptions(rejectIndeterminateLengthArrays: false) 234 244 // #expect(try CBORDecoder(options: options).decode([Int].self, from: array) == [2, 3]) ··· 246 256 #expect(result == [[2, 3], [4, 5]]) 247 257 } 248 258 249 - @Test func rejectsIndeterminateArrayWhenConfigured() throws { 259 + @Test 260 + func rejectsIndeterminateArrayWhenConfigured() throws { 250 261 let array = "9FFF".asHexData() 251 262 let options = DecodingOptions(rejectIndeterminateLengthArrays: true) 252 263 #expect(throws: DecodingError.self) {
+1
Tests/CBORTests/EncodableTests.swift
··· 218 218 let encoder = CBOREncoder() 219 219 220 220 let data = try encoder.encode(Company.mock) 221 + // swiftlint:disable:next line_length 221 222 #expect(data == "A469656D706C6F796565738AA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726BA563616765181E65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B67666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F7465646E616D656941636D6520436F7270".asHexData()) 222 223 } 223 224
+2 -1
Tests/CBORTests/ScannerTests.swift
··· 108 108 } 109 109 } 110 110 111 - @Test func indeterminateNestedArray() throws { 111 + @Test 112 + func indeterminateNestedArray() throws { 112 113 let expectedMap = [128, 2, 22, 0, 10, 128, 2, 6, 1, 4, 2, 3, 0, 3, 4, 0, 128, 2, 6, 5, 4, 4, 7, 0, 5, 8, 0] 113 114 let data = "9F9F0203FF9F0405FFFF".asHexData() 114 115 try data.withUnsafeBytes {