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

First Commit - Encoder is Done

Khan Winter c02e1f7b

+1990
+28
.gitignore
··· 1 + ## Build generated 2 + build/ 3 + DerivedData 4 + 5 + ## Various settings 6 + *.pbxuser 7 + !default.pbxuser 8 + *.mode1v3 9 + !default.mode1v3 10 + *.mode2v3 11 + !default.mode2v3 12 + *.perspectivev3 13 + !default.perspectivev3 14 + xcuserdata 15 + 16 + ## Other 17 + *.xccheckout 18 + *.moved-aside 19 + *.xcuserstate 20 + *.xcscmblueprint 21 + 22 + ## Obj-C/Swift specific 23 + *.hmap 24 + *.ipa 25 + .build/ 26 + 27 + Carthage/ 28 + .swiftpm/
+42
.swiftlint.yml
··· 1 + disabled_rules: 2 + - todo 3 + - trailing_comma 4 + - nesting 5 + - identifier_name 6 + 7 + type_name: 8 + excluded: 9 + - ID 10 + 11 + function_parameter_count: 12 + warning: 5 13 + error: 8 14 + 15 + generic_type_name: 16 + max_length: 17 + warning: 40 18 + error: 100 19 + 20 + included: 21 + - Sources 22 + - Tests 23 + 24 + opt_in_rules: 25 + - attributes 26 + - empty_count 27 + - closure_spacing 28 + - contains_over_first_not_nil 29 + - missing_docs 30 + - modifier_order 31 + - convenience_type 32 + - pattern_matching_keywords 33 + - multiline_parameters_brackets 34 + - multiline_arguments_brackets 35 + 36 + custom_rules: 37 + spaces_over_tabs: 38 + included: ".*\\.swift" 39 + name: "Spaces over Tabs" 40 + regex: "\t" 41 + message: "Prefer spaces for indents over tabs. See Xcode setting: 'Text Editing' -> 'Indentation'" 42 + severity: warning
+8
Benchmarks/.gitignore
··· 1 + .DS_Store 2 + /.build 3 + /Packages 4 + xcuserdata/ 5 + DerivedData/ 6 + .swiftpm/configuration/registries.json 7 + .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 + .netrc
+24
Benchmarks/Package.resolved
··· 1 + { 2 + "originHash" : "5e1710470d8404b3bfaf7fcce400f736c9be786a1ba4c1863c2ad7530ae2fba7", 3 + "pins" : [ 4 + { 5 + "identity" : "swift-collections", 6 + "kind" : "remoteSourceControl", 7 + "location" : "https://github.com/apple/swift-collections.git", 8 + "state" : { 9 + "revision" : "8c0c0a8b49e080e54e5e328cc552821ff07cd341", 10 + "version" : "1.2.1" 11 + } 12 + }, 13 + { 14 + "identity" : "swiftcbor", 15 + "kind" : "remoteSourceControl", 16 + "location" : "https://github.com/valpackett/SwiftCBOR.git", 17 + "state" : { 18 + "branch" : "master", 19 + "revision" : "ea5ece79b0efde241495bfaa74eccceeffc382bc" 20 + } 21 + } 22 + ], 23 + "version" : 3 24 + }
+20
Benchmarks/Package.swift
··· 1 + // swift-tools-version: 6.0 2 + 3 + import PackageDescription 4 + 5 + let package = Package( 6 + name: "Benchmarks", 7 + dependencies: [ 8 + .package(path: ".."), 9 + .package(url: "https://github.com/valpackett/SwiftCBOR.git", branch: "master"), 10 + ], 11 + targets: [ 12 + .executableTarget( 13 + name: "Benchmarks", 14 + dependencies: [ 15 + "CBOR", 16 + .product(name: "SwiftCBOR", package: "SwiftCBOR"), 17 + ] 18 + ), 19 + ], 20 + )
+256
Benchmarks/Sources/Benchmarks/Benchmarks.swift
··· 1 + import Foundation 2 + import SwiftCBOR 3 + import CBOR 4 + 5 + @inline(never) 6 + func blackhole<T>(_ value: T) { } 7 + 8 + // Representative structs 9 + struct Person: Codable { 10 + let name: String 11 + let age: Int 12 + let email: String 13 + let isActive: Bool 14 + let tags: [String] 15 + } 16 + 17 + struct Company: Codable { 18 + let name: String 19 + let founded: Int 20 + let employees: [Person] 21 + let metadata: [String: String] 22 + } 23 + 24 + func measure(iterations: Int, block: () throws -> Void) throws -> (total: Double, avg: Double) { 25 + var info = mach_timebase_info() 26 + guard mach_timebase_info(&info) == KERN_SUCCESS else { fatalError() } 27 + let start = mach_absolute_time() 28 + for _ in 0..<iterations { 29 + try block() 30 + } 31 + let end = mach_absolute_time() 32 + let elapsed = end - start 33 + let nanos = elapsed * UInt64(info.numer) / UInt64(info.denom) 34 + let msec = TimeInterval(nanos) / TimeInterval(NSEC_PER_MSEC) 35 + let avg = msec / Double(iterations) 36 + return (msec, avg) 37 + } 38 + 39 + @main 40 + struct Benchmarks { 41 + // --- main logic --- 42 + static func main() throws { 43 + let person = Person(name: "Alice", age: 30, email: "alice@example.com", isActive: true, tags: ["swift", "cbor", "benchmark"]) 44 + let company = Company( 45 + name: "Acme Corp", 46 + founded: 1999, 47 + employees: Array(repeating: person, count: 100), 48 + metadata: ["industry": "tech", "location": "remote"] 49 + ) 50 + let string = String(repeating: "a", count: 1000) 51 + let stringSmall = "abc" 52 + let data = Data(repeating: 0x42, count: 1024) 53 + let dataSmall = Data([0x01, 0x02, 0x03]) 54 + let dict = ["key": "value", "foo": "bar", "baz": "qux"] 55 + let dictSmall = ["a": "b"] 56 + let array = Array(0..<1000) 57 + let arraySmall = [1, 2, 3] 58 + let intVal = 123456789 59 + let intValSmall = 42 60 + let boolVal = true 61 + 62 + let swiftCBOR = CodableCBOREncoder() 63 + let cbor = CBOREncoder() 64 + let iterations = 1000 65 + print("--- Codable Encoding Benchmarks (\(iterations) iterations each) ---\n") 66 + 67 + let allBenchmarks: [Benchmark] = [ 68 + PersonBenchmark(person: person), 69 + CompanyBenchmark(company: company), 70 + StringBenchmark(string: string), 71 + StringBenchmark(string: stringSmall, name: "String Small"), 72 + DataBenchmark(data: data), 73 + DataBenchmark(data: dataSmall, name: "Data Small"), 74 + DictionaryBenchmark(dict: dict), 75 + DictionaryBenchmark(dict: dictSmall, name: "Dictionary Small"), 76 + ArrayBenchmark(array: array), 77 + ArrayBenchmark(array: arraySmall, name: "Array Small"), 78 + IntBenchmark(intVal: intVal), 79 + IntBenchmark(intVal: intValSmall, name: "Int Small"), 80 + BoolBenchmark(boolVal: boolVal) 81 + ] 82 + 83 + let args = CommandLine.arguments 84 + var only: String? = nil 85 + if let onlyIdx = args.firstIndex(of: "--only"), onlyIdx + 1 < args.count { 86 + only = args[onlyIdx + 1].lowercased() 87 + } 88 + 89 + let benchmarksToRun: [Benchmark] 90 + if let only = only { 91 + benchmarksToRun = allBenchmarks.filter { $0.name.lowercased() == only } 92 + if benchmarksToRun.isEmpty { 93 + print("No benchmark found for --only \(only)") 94 + return 95 + } 96 + } else { 97 + benchmarksToRun = allBenchmarks 98 + } 99 + 100 + for bench in benchmarksToRun { 101 + try bench.run(swiftCBOR: swiftCBOR, cbor: cbor, iterations: iterations) 102 + } 103 + } 104 + } 105 + 106 + func printComparison(type: String, swiftCBOR: (Double, Double), cbor: (Double, Double)) { 107 + let absChange = cbor.1 - swiftCBOR.1 108 + let percentChange = swiftCBOR.1 == 0 ? 0 : (absChange / swiftCBOR.1) * 100 109 + print("Encoding: \(type)") 110 + print(String(format: " SwiftCBOR: total=%.3fms, avg=%.6fms", swiftCBOR.0, swiftCBOR.1)) 111 + print(String(format: " CBOR: total=%.3fms, avg=%.6fms", cbor.0, cbor.1)) 112 + print(String(format: " Δ avg: %.6fms (%+.2f%%)", absChange, percentChange)) 113 + print("") 114 + } 115 + 116 + 117 + protocol Benchmark { 118 + var name: String { get } 119 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws 120 + } 121 + 122 + struct PersonBenchmark: Benchmark { 123 + let name: String 124 + let person: Person 125 + init(person: Person, name: String = "Person") { self.person = person; self.name = name } 126 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 127 + let swiftCBORResult = try measure(iterations: iterations) { 128 + let encoded = try swiftCBOR.encode(person) 129 + blackhole(encoded) 130 + } 131 + let cborResult = try measure(iterations: iterations) { 132 + let encoded = try cbor.encode(person) 133 + blackhole(encoded) 134 + } 135 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 136 + } 137 + } 138 + 139 + struct CompanyBenchmark: Benchmark { 140 + let name: String 141 + let company: Company 142 + init(company: Company, name: String = "Company") { self.company = company; self.name = name } 143 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 144 + let swiftCBORResult = try measure(iterations: iterations) { 145 + let encoded = try swiftCBOR.encode(company) 146 + blackhole(encoded) 147 + } 148 + let cborResult = try measure(iterations: iterations) { 149 + let encoded = try cbor.encode(company) 150 + blackhole(encoded) 151 + } 152 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 153 + } 154 + } 155 + 156 + struct StringBenchmark: Benchmark { 157 + let name: String 158 + let string: String 159 + init(string: String, name: String = "String") { self.string = string; self.name = name } 160 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 161 + let swiftCBORResult = try measure(iterations: iterations) { 162 + let encoded = try swiftCBOR.encode(string) 163 + blackhole(encoded) 164 + } 165 + let cborResult = try measure(iterations: iterations) { 166 + let encoded = try cbor.encode(string) 167 + blackhole(encoded) 168 + } 169 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 170 + } 171 + } 172 + 173 + struct DataBenchmark: Benchmark { 174 + let name: String 175 + let data: Data 176 + init(data: Data, name: String = "Data") { self.data = data; self.name = name } 177 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 178 + let swiftCBORResult = try measure(iterations: iterations) { 179 + let encoded = try swiftCBOR.encode(data) 180 + blackhole(encoded) 181 + } 182 + let cborResult = try measure(iterations: iterations) { 183 + let encoded = try cbor.encode(data) 184 + blackhole(encoded) 185 + } 186 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 187 + } 188 + } 189 + 190 + struct DictionaryBenchmark: Benchmark { 191 + let name: String 192 + let dict: [String: String] 193 + init(dict: [String: String], name: String = "Dictionary") { self.dict = dict; self.name = name } 194 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 195 + let swiftCBORResult = try measure(iterations: iterations) { 196 + let encoded = try swiftCBOR.encode(dict) 197 + blackhole(encoded) 198 + } 199 + let cborResult = try measure(iterations: iterations) { 200 + let encoded = try cbor.encode(dict) 201 + blackhole(encoded) 202 + } 203 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 204 + } 205 + } 206 + 207 + struct ArrayBenchmark: Benchmark { 208 + let name: String 209 + let array: [Int] 210 + init(array: [Int], name: String = "Array") { self.array = array; self.name = name } 211 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 212 + let swiftCBORResult = try measure(iterations: iterations) { 213 + let encoded = try swiftCBOR.encode(array) 214 + blackhole(encoded) 215 + } 216 + let cborResult = try measure(iterations: iterations) { 217 + let encoded = try cbor.encode(array) 218 + blackhole(encoded) 219 + } 220 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 221 + } 222 + } 223 + 224 + struct IntBenchmark: Benchmark { 225 + let name: String 226 + let intVal: Int 227 + init(intVal: Int, name: String = "Int") { self.intVal = intVal; self.name = name } 228 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 229 + let swiftCBORResult = try measure(iterations: iterations) { 230 + let encoded = try swiftCBOR.encode(intVal) 231 + blackhole(encoded) 232 + } 233 + let cborResult = try measure(iterations: iterations) { 234 + let encoded = try cbor.encode(intVal) 235 + blackhole(encoded) 236 + } 237 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 238 + } 239 + } 240 + 241 + struct BoolBenchmark: Benchmark { 242 + let name: String 243 + let boolVal: Bool 244 + init(boolVal: Bool, name: String = "Bool") { self.boolVal = boolVal; self.name = name } 245 + func run(swiftCBOR: CodableCBOREncoder, cbor: CBOREncoder, iterations: Int) throws { 246 + let swiftCBORResult = try measure(iterations: iterations) { 247 + let encoded = try swiftCBOR.encode(boolVal) 248 + blackhole(encoded) 249 + } 250 + let cborResult = try measure(iterations: iterations) { 251 + let encoded = try cbor.encode(boolVal) 252 + blackhole(encoded) 253 + } 254 + printComparison(type: name, swiftCBOR: swiftCBORResult, cbor: cborResult) 255 + } 256 + }
+22
CODE_OF_CONDUCT.md
··· 1 + # Contributor Code of Conduct 2 + 3 + As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 + 5 + We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 + 7 + Examples of unacceptable behavior by participants include: 8 + 9 + * The use of sexualized language or imagery 10 + * Personal attacks 11 + * Trolling or insulting/derogatory comments 12 + * Public or private harassment 13 + * Publishing others' private information, such as physical or electronic addresses, without explicit permission 14 + * Other unethical or unprofessional conduct. 15 + 16 + Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 + 18 + This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 + 20 + Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 + 22 + This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
+15
Package.resolved
··· 1 + { 2 + "originHash" : "9e0d9facdb3a2c259bbbfeca84a01c7104404e63cb909b0dd5ac9b7b883272f1", 3 + "pins" : [ 4 + { 5 + "identity" : "swift-collections", 6 + "kind" : "remoteSourceControl", 7 + "location" : "https://github.com/apple/swift-collections.git", 8 + "state" : { 9 + "revision" : "8c0c0a8b49e080e54e5e328cc552821ff07cd341", 10 + "version" : "1.2.1" 11 + } 12 + } 13 + ], 14 + "version" : 3 15 + }
+26
Package.swift
··· 1 + // swift-tools-version:6.0 2 + 3 + import PackageDescription 4 + 5 + let package = Package( 6 + name: "CBOR", 7 + products: [ 8 + .library(name: "CBOR", targets: ["CBOR"]) 9 + ], 10 + dependencies: [ 11 + // Heap 12 + .package(url: "https://github.com/apple/swift-collections.git", from: "1.2.0"), 13 + ], 14 + targets: [ 15 + .target( 16 + name: "CBOR", 17 + dependencies: [ 18 + .product(name: "Collections", package: "swift-collections") 19 + ] 20 + ), 21 + .testTarget( 22 + name: "CBORTests", 23 + dependencies: ["CBOR"], 24 + ) 25 + ] 26 + )
+9
README.md
··· 1 + # SwiftCBOR 2 + 3 + None of that now. 4 + 5 + ## Contributing 6 + 7 + By participating in this project you agree to follow the [Contributor Code of Conduct](https://contributor-covenant.org/version/1/4/). 8 + 9 + [The list of contributors is available on GitHub](https://github.com/thecoolwinter/SwiftCBOR/graphs/contributors).
+20
Sources/CBOR/Coding/CodingPath.swift
··· 1 + // 2 + // CodingPath.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + /// Reconstructible coding path. 9 + enum CodingPath { 10 + case root 11 + indirect case child(key: CodingKey, parent: CodingPath) 12 + 13 + var expanded: [CodingKey] { 14 + switch self { 15 + case .root: return [] 16 + case let .child(key: key, parent: parent): 17 + return parent.expanded + CollectionOfOne(key) 18 + } 19 + } 20 + }
+23
Sources/CBOR/Coding/UnkeyedCodingKey.swift
··· 1 + // 2 + // UnkeyedCodingKey.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + public struct UnkeyedCodingKey: CodingKey { 9 + var index: Int 10 + 11 + public var intValue: Int? { return index } 12 + public var stringValue: String { return String(index) } 13 + 14 + public init(intValue: Int) { 15 + index = intValue 16 + } 17 + public init?(stringValue: String) { 18 + guard let value = Int(stringValue) else { 19 + return nil 20 + } 21 + index = value 22 + } 23 + }
+13
Sources/CBOR/CommonTags.swift
··· 1 + // 2 + // CommonTags.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + /// Some common tags for encoding/decoding specifically formatted data such as Dates and UUIDs. 9 + enum CommonTags: UInt { 10 + case stringDate = 0 11 + case epochDate = 1 12 + case uuid = 37 13 + }
+13
Sources/CBOR/Constants.swift
··· 1 + // 2 + // Constants.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @usableFromInline 9 + enum Constants { 10 + // Values below this value can be encoded in the argument field. 11 + @inline(__always) 12 + @usableFromInline static let maxArgSize = 24 13 + }
+76
Sources/CBOR/Encoder/CBOREncoder.swift
··· 1 + // 2 + // CBOREncoder.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + #if canImport(FoundationEssentials) 9 + import FoundationEssentials 10 + #else 11 + import Foundation 12 + #endif 13 + 14 + /// An object that can serialize ``Codable`` objects into the CBOR serialization format. 15 + /// 16 + /// To perform serialization, use the ``encode(_:)-6zhmp`` method to convert a Codable object to ``Data``. To 17 + /// configure encoding behavior, either pass customization options in with 18 + /// ``init(forceStringKeys:useStringDates:assumeUInt8IsByteString:)`` or modify ``options``. 19 + public struct CBOREncoder { 20 + /// Options that determine the behavior of ``CBOREncoder``. 21 + public var options: EncodingOptions 22 + 23 + /// Create a new CBOR encoder object. 24 + /// - Parameters: 25 + /// - forceStringKeys: See ``EncodingOptions/forceStringKeys``. 26 + /// - useStringDates: See ``EncodingOptions/useStringDates``. 27 + /// - assumeUInt8IsByteString: See ``EncodingOptions/assumeUInt8IsByteString``. 28 + public init(forceStringKeys: Bool = false, useStringDates: Bool = false, assumeUInt8IsByteString: Bool = true) { 29 + options = EncodingOptions( 30 + forceStringKeys: forceStringKeys, 31 + useStringDates: useStringDates, 32 + assumeUInt8IsByteString: assumeUInt8IsByteString 33 + ) 34 + } 35 + 36 + /// Returns a CBOR-encoded representation of the value you supply. 37 + /// - Parameter value: The value to encode as CBOR data. 38 + /// - Returns: The encoded CBOR data. 39 + public func encode<T: Encodable>(_ value: T) throws -> Data { 40 + let tempStorage = TopLevelTemporaryEncodingStorage() 41 + 42 + let encodingContext = EncodingContext(options: options) 43 + let encoder = SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 44 + try value.encode(to: encoder) 45 + 46 + let dataSize = tempStorage.value.size 47 + var data = Data(count: dataSize) 48 + data.withUnsafeMutableBytes { ptr in 49 + var slice = ptr[...] 50 + tempStorage.value.write(to: &slice) 51 + assert(slice.isEmpty) 52 + } 53 + return data 54 + } 55 + 56 + /// Returns a CBOR-encoded representation of the value you supply. 57 + /// - Note: This method is identical to ``encode(_:)-6zhmp``. This is a fast path included due to the lack of 58 + /// ability to specialize Codable containers for specific types, such as byte strings. 59 + /// - Parameter value: The value to encode as CBOR data. 60 + /// - Returns: The encoded CBOR data. 61 + public func encode(_ value: Data) throws -> Data { 62 + // Fast path for plain data objects. See comments in ``UnkekedCBOREncodingContainer`` for why this can't be done 63 + // via the real Codable APIs. Hate that we have to 'cheat' like this to get the performance I'd like for 64 + // byte strings. >:( 65 + 66 + var optimizer = ByteStringOptimizer(value: value) 67 + let dataSize = optimizer.size 68 + var data = Data(count: dataSize) 69 + data.withUnsafeMutableBytes { ptr in 70 + var slice = ptr[...] 71 + optimizer.write(to: &slice) 72 + assert(slice.isEmpty) 73 + } 74 + return data 75 + } 76 + }
+117
Sources/CBOR/Encoder/Containers/KeyedCBOREncodingContainer.swift
··· 1 + // 2 + // KeyedCBOREncodingContainer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + struct KeyedCBOREncodingContainer< 9 + Key: CodingKey, 10 + ParentStorage: TemporaryEncodingStorage 11 + >: KeyedEncodingContainerProtocol { 12 + private let storage: KeyBuffer 13 + private let context: EncodingContext 14 + 15 + var codingPath: [CodingKey] { context.codingPath } 16 + 17 + init(parent: ParentStorage, context: EncodingContext) { 18 + self.storage = KeyBuffer(parent: parent, context: context) 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 { 27 + try value.encode(to: encoder(for: key)) 28 + } 29 + 30 + mutating func encodeNil(forKey key: Key) throws { 31 + storage.withKey(key).register(NilOptimizer()) 32 + } 33 + 34 + mutating func nestedContainer<NestedKey: CodingKey>( 35 + keyedBy keyType: NestedKey.Type, 36 + forKey key: Key 37 + ) -> KeyedEncodingContainer<NestedKey> { 38 + KeyedEncodingContainer( 39 + KeyedCBOREncodingContainer<NestedKey, KeyBuffer.Keyed>( 40 + parent: storage.withKey(key), 41 + context: context.appending(key) 42 + ) 43 + ) 44 + } 45 + 46 + mutating func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer { 47 + UnkeyedCBOREncodingContainer<KeyBuffer.Keyed>( 48 + parent: storage.withKey(key), 49 + context: context.appending(key) 50 + ) 51 + } 52 + 53 + mutating func superEncoder() -> any Encoder { 54 + fatalError("Unimplemented") 55 + } 56 + 57 + mutating func superEncoder(forKey key: Key) -> any Encoder { 58 + fatalError("Unimplemented") 59 + } 60 + } 61 + 62 + // MARK: - Key Buffer 63 + 64 + extension KeyedCBOREncodingContainer { 65 + final class KeyBuffer { 66 + init(parent: ParentStorage, context: EncodingContext) { 67 + self.parent = parent 68 + self.context = context 69 + } 70 + 71 + private let parent: ParentStorage 72 + private let context: EncodingContext 73 + private var map: [String: EncodingOptimizer] = [:] 74 + private var intMap: [Int: EncodingOptimizer] = [:] 75 + 76 + func withKey<T: CodingKey>(_ key: T) -> Keyed { 77 + Keyed(storage: Unmanaged.passUnretained(self), key: key, forceStringKeys: context.options.forceStringKeys) 78 + } 79 + 80 + enum KeyType { 81 + case string(String) 82 + case int(Int) 83 + } 84 + 85 + struct Keyed: TemporaryEncodingStorage { 86 + let storage: Unmanaged<KeyBuffer> 87 + let key: KeyType 88 + 89 + init<T: CodingKey>(storage: Unmanaged<KeyBuffer>, key: T, forceStringKeys: Bool) { 90 + self.storage = storage 91 + if let intKey = key.intValue, !forceStringKeys { 92 + self.key = .int(intKey) 93 + } else { 94 + self.key = .string(key.stringValue) 95 + } 96 + } 97 + 98 + @inlinable 99 + func register(_ optimizer: EncodingOptimizer) { 100 + switch key { 101 + case .string(let key): 102 + storage.takeUnretainedValue().map[key] = optimizer 103 + case .int(let key): 104 + storage.takeUnretainedValue().intMap[key] = optimizer 105 + } 106 + } 107 + } 108 + 109 + deinit { 110 + if !intMap.isEmpty && !context.options.forceStringKeys { 111 + parent.register(KeyedOptimizer(value: intMap)) 112 + } else { 113 + parent.register(KeyedOptimizer(value: map)) 114 + } 115 + } 116 + } 117 + }
+75
Sources/CBOR/Encoder/Containers/SingleValueCBOREncodingContainer.swift
··· 1 + // 2 + // InternalEncoder.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + #if canImport(FoundationEssentials) 9 + import FoundationEssentials 10 + #else 11 + import Foundation 12 + #endif 13 + 14 + struct SingleValueCBOREncodingContainer<Storage: TemporaryEncodingStorage>: Encoder { 15 + let parent: Storage 16 + let context: EncodingContext 17 + 18 + var userInfo: [CodingUserInfoKey: Any] = [:] 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 + } 25 + 26 + func unkeyedContainer() -> UnkeyedEncodingContainer { 27 + UnkeyedCBOREncodingContainer(parent: parent, context: context) 28 + } 29 + 30 + func singleValueContainer() -> SingleValueEncodingContainer { 31 + self 32 + } 33 + } 34 + 35 + extension SingleValueCBOREncodingContainer: SingleValueEncodingContainer { 36 + func encodeNil() throws { parent.register(NilOptimizer()) } 37 + 38 + func encode(_ value: String) throws { 39 + parent.register(StringOptimizer(value: value)) 40 + } 41 + 42 + func encode(_ value: Bool) throws { 43 + parent.register(BoolOptimizer(value: value)) 44 + } 45 + 46 + func encode(_ value: Double) throws { 47 + parent.register(DoubleOptimizer(value: value)) 48 + } 49 + 50 + func encode(_ value: Float) throws { 51 + parent.register(FloatOptimizer(value: value)) 52 + } 53 + 54 + func encode<T>(_ value: T) throws where T: Encodable, T: FixedWidthInteger { 55 + parent.register(IntOptimizer(value: value)) 56 + } 57 + 58 + func encode<T>(_ value: T) throws where T: Encodable { 59 + // I hate this conditional cast, but Swift forces us to do this because Codable can't implement a specialized 60 + // function for any type, only the standard library types. This is the same method Foundation uses to detect 61 + // special encoding cases. It's still lame. 62 + 63 + if let date = value as? Date { 64 + if options.useStringDates { 65 + parent.register(StringDateOptimizer(value: date)) 66 + } else { 67 + parent.register(EpochDateOptimizer(value: date)) 68 + } 69 + } else if let uuid = value as? UUID { 70 + parent.register(UUIDOptimizer(value: uuid)) 71 + } else { 72 + try value.encode(to: self) 73 + } 74 + } 75 + }
+105
Sources/CBOR/Encoder/Containers/UnkeyedCBOREncodingContainer.swift
··· 1 + // 2 + // File.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + struct UnkeyedCBOREncodingContainer<ParentStorage: TemporaryEncodingStorage>: UnkeyedEncodingContainer { 9 + private let storage: KeyBuffer 10 + private let context: EncodingContext 11 + 12 + var codingPath: [CodingKey] { context.codingPath } 13 + var count: Int { storage.count } 14 + 15 + init(parent: ParentStorage, context: EncodingContext) { 16 + self.storage = KeyBuffer(parent: parent) 17 + self.context = context 18 + } 19 + 20 + private func nextContext() -> EncodingContext { 21 + context.appending(UnkeyedCodingKey(intValue: count)) 22 + } 23 + 24 + func encode<T: Encodable>(_ value: T) throws { 25 + try value.encode( 26 + to: SingleValueCBOREncodingContainer(parent: storage.forAppending(), context: nextContext()) 27 + ) 28 + } 29 + 30 + func encode(_ value: UInt8) throws { 31 + storage.data.append(value) 32 + if !context.options.assumeUInt8IsByteString { 33 + try value.encode( 34 + to: SingleValueCBOREncodingContainer(parent: storage.forAppending(), context: nextContext()) 35 + ) 36 + } 37 + } 38 + 39 + mutating func encodeNil() throws { 40 + storage.forAppending().register(NilOptimizer()) 41 + } 42 + 43 + mutating func nestedContainer<NestedKey: CodingKey>( 44 + keyedBy keyType: NestedKey.Type 45 + ) -> KeyedEncodingContainer<NestedKey> { 46 + KeyedEncodingContainer(KeyedCBOREncodingContainer(parent: storage.forAppending(), context: nextContext())) 47 + } 48 + 49 + mutating func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { 50 + UnkeyedCBOREncodingContainer<KeyBuffer.Indexed>( 51 + parent: storage.forAppending(), 52 + context: nextContext() 53 + ) 54 + } 55 + 56 + mutating func superEncoder() -> any Encoder { 57 + fatalError("Unimplemented") 58 + } 59 + } 60 + 61 + extension UnkeyedCBOREncodingContainer { 62 + private final class KeyBuffer { 63 + var count: Int { items.count } 64 + 65 + let parent: ParentStorage 66 + var items: [EncodingOptimizer] = [] 67 + var data: [UInt8] = [] 68 + 69 + init(parent: ParentStorage) { 70 + self.parent = parent 71 + self.items = [] 72 + } 73 + 74 + func forAppending() -> Indexed { 75 + items.append(EmptyOptimizer()) 76 + return Indexed(parent: Unmanaged.passUnretained(self), index: items.count - 1) 77 + } 78 + 79 + struct Indexed: TemporaryEncodingStorage { 80 + let parent: Unmanaged<KeyBuffer> 81 + let index: Int 82 + 83 + func register(_ optimizer: EncodingOptimizer) { 84 + parent.takeUnretainedValue().items[index] = optimizer 85 + } 86 + } 87 + 88 + deinit { 89 + // Swift doesn't give us a good way to detect a 'byte string'. So, we record both UInt8 values and 90 + // some optimizers. At this point, we can check if we're encoding either a collection of multiple 91 + // types (items.count > data.count), or a pure data collection (data.count == items.count). 92 + // This is terrible in terms of memory use, but lets us encode byte strings using the most optimal 93 + // encoding method. 94 + // CBOR also mandates that an empty collection is by default an array, so we check if this is empty. 95 + // Frankly, this blows and I wish Swift's Codable API was even a smidgen less fucked. 96 + 97 + if items.count <= data.count && !data.isEmpty { 98 + parent.register(ByteStringOptimizer(value: data)) 99 + } else { 100 + parent.register(UnkeyedOptimizer(value: items)) 101 + } 102 + } 103 + } 104 + 105 + }
+33
Sources/CBOR/Encoder/EncodingContext.swift
··· 1 + // 2 + // EncodingContext.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + /// Encoding context, shared between all internal encoders, containers, etc. during encoding process. 9 + struct EncodingContext { 10 + fileprivate class Shared { 11 + let options: EncodingOptions 12 + 13 + init(options: EncodingOptions) { 14 + self.options = options 15 + } 16 + } 17 + 18 + fileprivate let shared: Shared 19 + var path: CodingPath = .root 20 + 21 + var options: EncodingOptions { shared.options } 22 + var codingPath: [CodingKey] { path.expanded } 23 + 24 + init(options: EncodingOptions) { 25 + shared = Shared(options: options) 26 + } 27 + 28 + func appending<Key: CodingKey>(_ key: Key) -> EncodingContext { 29 + var temp = self 30 + temp.path = .child(key: key, parent: path) 31 + return temp 32 + } 33 + }
+53
Sources/CBOR/Encoder/EncodingOptimizer.swift
··· 1 + // 2 + // EncodingOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @usableFromInline 9 + protocol EncodingOptimizer { 10 + var type: MajorType { get } 11 + var argument: UInt8 { get } 12 + var headerSize: Int { get } 13 + var contentSize: Int { get } 14 + 15 + mutating func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) 16 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) 17 + } 18 + 19 + extension EncodingOptimizer { 20 + @inline(__always) 21 + @inlinable var headerSize: Int { 0 } 22 + 23 + var size: Int { 1 + headerSize + contentSize } 24 + 25 + mutating func write(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 26 + assert(data.count >= size) 27 + 28 + // Write the type & argument 29 + assert(argument & 0b11100000 == 0) 30 + assert(type.bits & 0b00011111 == 0) 31 + assert(!data.isEmpty) 32 + 33 + data[data.startIndex] = type.bits | argument 34 + data.removeFirst() 35 + 36 + writeHeader(to: &data) 37 + writePayload(to: &data) 38 + } 39 + 40 + @inlinable 41 + func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 42 + // No-op by default 43 + } 44 + } 45 + 46 + struct EmptyOptimizer: EncodingOptimizer { 47 + var type: MajorType { .uint } 48 + var argument: UInt8 { 0 } 49 + var contentSize: Int { 0 } 50 + 51 + func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) { } 52 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { } 53 + }
+31
Sources/CBOR/Encoder/EncodingOptions.swift
··· 1 + // 2 + // EncodingOptions.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + /// Options that determine the behavior of ``CBOREncoder``. 9 + public struct EncodingOptions { 10 + /// Force encoded maps to use string keys even when integer keys are available. 11 + public let forceStringKeys: Bool 12 + 13 + /// Encode dates as strings instead of epoch timestamps (Doubles) 14 + public let useStringDates: Bool 15 + 16 + /// Codable can't tell us if we're encoding a Data or [UInt8] object. By default this library assumes that if it's 17 + /// encoding an unkeyed container or UInt8 objects it's a byte string. Toggle this to false to disable this. 18 + /// **This will slow down all Data encoding operations dramatically**. 19 + public let assumeUInt8IsByteString: Bool 20 + 21 + /// Initialize new encoding options. 22 + /// - Parameters: 23 + /// - forceStringKeys: Force encoded maps to use string keys even when integer keys are available. 24 + /// - useStringDates: Encode dates as strings instead of epoch timestamps (Doubles) 25 + /// - assumeUInt8IsByteString: See ``assumeUInt8IsByteString``. 26 + public init(forceStringKeys: Bool, useStringDates: Bool, assumeUInt8IsByteString: Bool) { 27 + self.forceStringKeys = forceStringKeys 28 + self.useStringDates = useStringDates 29 + self.assumeUInt8IsByteString = assumeUInt8IsByteString 30 + } 31 + }
+16
Sources/CBOR/Encoder/Optimizers/BoolOptimizer.swift
··· 1 + // 2 + // BoolOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + struct BoolOptimizer: EncodingOptimizer { 9 + let value: Bool 10 + 11 + var type: MajorType { .simple } 12 + var argument: UInt8 { value ? 21 : 20 } 13 + var contentSize: Int { 0 } 14 + 15 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { } 16 + }
+71
Sources/CBOR/Encoder/Optimizers/ByteStringOptimizer.swift
··· 1 + // 2 + // ByteStringOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @inlinable 9 + func ByteStringOptimizer<T: Collection>(value: T) -> EncodingOptimizer where T.Element == UInt8 { 10 + if value.count < Constants.maxArgSize { 11 + return SmallByteStringOptimizer(value: value) 12 + } else { 13 + return LargeByteStringOptimizer(value: value) 14 + } 15 + } 16 + 17 + @usableFromInline 18 + struct SmallByteStringOptimizer<T: Collection>: EncodingOptimizer where T.Element == UInt8 { 19 + let value: T 20 + 21 + @usableFromInline var type: MajorType { .bytes } 22 + @usableFromInline var argument: UInt8 { UInt8(truncatingIfNeeded: contentSize) } 23 + @usableFromInline var contentSize: Int { value.count } 24 + 25 + @usableFromInline 26 + init(value: T) { 27 + assert(value.count < Constants.maxArgSize) 28 + self.value = value 29 + } 30 + 31 + @usableFromInline 32 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 33 + writeString(value, to: &data) 34 + } 35 + } 36 + 37 + @usableFromInline 38 + struct LargeByteStringOptimizer<T: Collection>: EncodingOptimizer where T.Element == UInt8 { 39 + let value: T 40 + 41 + @usableFromInline var type: MajorType { .bytes } 42 + @usableFromInline var argument: UInt8 { countToArg(contentSize) } 43 + @usableFromInline var headerSize: Int { countToHeaderSize(contentSize) } 44 + @usableFromInline var contentSize: Int { value.count } 45 + 46 + @usableFromInline 47 + init(value: T) { 48 + assert(value.count >= Constants.maxArgSize) 49 + self.value = value 50 + } 51 + 52 + @usableFromInline 53 + func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 54 + writeIntToHeader(contentSize, data: &data) 55 + } 56 + 57 + @usableFromInline 58 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 59 + writeString(value, to: &data) 60 + } 61 + } 62 + 63 + @inline(__always) 64 + private func writeString<T: Collection>( 65 + _ value: T, 66 + to data: inout Slice<UnsafeMutableRawBufferPointer> 67 + ) where T.Element == UInt8 { 68 + assert(data.count >= value.count) 69 + UnsafeMutableRawBufferPointer(rebasing: data).copyBytes(from: value) 70 + data.removeFirst(value.count) 71 + }
+44
Sources/CBOR/Encoder/Optimizers/DateOptimizer.swift
··· 1 + // 2 + // DateOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + #if canImport(FoundationEssentials) 9 + import FoundationEssentials 10 + #else 11 + import Foundation 12 + #endif 13 + 14 + struct StringDateOptimizer: EncodingOptimizer { 15 + var optimizer: SmallStringOptimizer 16 + 17 + var type: MajorType { .tagged } 18 + var argument: UInt8 { 0 } 19 + var contentSize: Int { optimizer.size } 20 + 21 + init(value: Date) { 22 + optimizer = SmallStringOptimizer(value: ISO8601DateFormatter().string(from: value).utf8) 23 + } 24 + 25 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 26 + optimizer.write(to: &data) 27 + } 28 + } 29 + 30 + struct EpochDateOptimizer: EncodingOptimizer { 31 + var optimizer: DoubleOptimizer 32 + 33 + var type: MajorType { .tagged } 34 + var argument: UInt8 { 1 } 35 + var contentSize: Int { optimizer.size } 36 + 37 + init(value: Date) { 38 + optimizer = DoubleOptimizer(value: value.timeIntervalSince1970) 39 + } 40 + 41 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 42 + optimizer.write(to: &data) 43 + } 44 + }
+22
Sources/CBOR/Encoder/Optimizers/DoubleOptimizer.swift
··· 1 + // 2 + // DoubleOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + struct DoubleOptimizer: EncodingOptimizer { 9 + let value: Double 10 + 11 + var type: MajorType { .simple } 12 + var argument: UInt8 { 27 } 13 + var contentSize: Int { 8 } 14 + 15 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 16 + assert(data.count >= 8) 17 + var bytes = value.bitPattern.bigEndian 18 + withUnsafeBytes(of: &bytes) { ptr in 19 + UnsafeMutableRawBufferPointer(rebasing: data).copyBytes(from: ptr) 20 + } 21 + } 22 + }
+22
Sources/CBOR/Encoder/Optimizers/FloatOptimizer.swift
··· 1 + // 2 + // FloatOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + struct FloatOptimizer: EncodingOptimizer { 9 + let value: Float 10 + 11 + var type: MajorType { .simple } 12 + var argument: UInt8 { 26 } 13 + var contentSize: Int { 4 } 14 + 15 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 16 + assert(data.count >= 4) 17 + var bytes = value.bitPattern.bigEndian 18 + withUnsafeBytes(of: &bytes) { ptr in 19 + UnsafeMutableRawBufferPointer(rebasing: data).copyBytes(from: ptr) 20 + } 21 + } 22 + }
+129
Sources/CBOR/Encoder/Optimizers/IntOptimizer.swift
··· 1 + // 2 + // IntOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @inlinable 9 + func IntOptimizer<IntType: FixedWidthInteger>(value: IntType) -> EncodingOptimizer { 10 + if value < 0 { 11 + let encodingValue: UInt = UInt(-1 - value) 12 + if value > -Constants.maxArgSize { 13 + return SmallNegativeIntOptimizer(value: encodingValue) 14 + } else { 15 + if (0...UInt(UInt8.max)).contains(encodingValue) { 16 + return NegativeIntOptimizer(value: UInt8(encodingValue)) 17 + } 18 + 19 + if (0...UInt(UInt16.max)).contains(encodingValue) { 20 + return NegativeIntOptimizer(value: UInt16(encodingValue)) 21 + } 22 + 23 + if (0...UInt(UInt32.max)).contains(encodingValue) { 24 + return NegativeIntOptimizer(value: UInt32(encodingValue)) 25 + } 26 + 27 + return NegativeIntOptimizer(value: encodingValue) 28 + } 29 + } else { 30 + if value < Constants.maxArgSize { 31 + return SmallPositiveIntOptimizer(value: value) 32 + } else { 33 + let encodingValue = UInt(value) 34 + if (0...UInt(UInt8.max)).contains(encodingValue) { 35 + return PositiveIntOptimizer(value: UInt8(encodingValue)) 36 + } 37 + 38 + if (0...UInt(UInt16.max)).contains(encodingValue) { 39 + return PositiveIntOptimizer(value: UInt16(encodingValue)) 40 + } 41 + 42 + if (0...UInt(UInt32.max)).contains(encodingValue) { 43 + return PositiveIntOptimizer(value: UInt32(encodingValue)) 44 + } 45 + 46 + return PositiveIntOptimizer(value: encodingValue) 47 + } 48 + } 49 + } 50 + 51 + @usableFromInline 52 + struct SmallPositiveIntOptimizer<IntType: FixedWidthInteger>: EncodingOptimizer { 53 + let value: UInt8 54 + 55 + @usableFromInline var type: MajorType { .uint } 56 + @usableFromInline var argument: UInt8 { value } 57 + @usableFromInline var contentSize: Int { 0 } 58 + 59 + @usableFromInline 60 + init(value: IntType) { 61 + assert(value < Constants.maxArgSize) 62 + assert(value >= IntType.zero) 63 + self.value = UInt8(truncatingIfNeeded: value) 64 + } 65 + 66 + @usableFromInline 67 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { } 68 + } 69 + 70 + @usableFromInline 71 + struct SmallNegativeIntOptimizer<IntType: FixedWidthInteger>: EncodingOptimizer { 72 + let value: UInt8 73 + 74 + @usableFromInline var type: MajorType { .nint } 75 + @usableFromInline var argument: UInt8 { UInt8(value) } 76 + @usableFromInline var contentSize: Int { 0 } 77 + 78 + @usableFromInline 79 + init(value: IntType) { 80 + assert(value > -Constants.maxArgSize) 81 + assert(value >= IntType.zero) // -1 is encoded as 0 82 + self.value = UInt8(truncatingIfNeeded: value) // Positive representation 83 + } 84 + 85 + @usableFromInline 86 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { } 87 + } 88 + 89 + @usableFromInline 90 + struct PositiveIntOptimizer<IntType: FixedWidthInteger>: EncodingOptimizer { 91 + let value: IntType 92 + 93 + @usableFromInline var type: MajorType { .uint } 94 + @usableFromInline var argument: UInt8 { IntType.argumentValue } 95 + @usableFromInline var contentSize: Int { IntType.byteCount } 96 + 97 + @usableFromInline 98 + init(value: IntType) { 99 + assert(value >= Constants.maxArgSize) 100 + assert(value >= IntType.zero) 101 + self.value = value 102 + } 103 + 104 + @usableFromInline 105 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 106 + value.write(to: &data) 107 + } 108 + } 109 + 110 + @usableFromInline 111 + struct NegativeIntOptimizer<IntType: FixedWidthInteger>: EncodingOptimizer { 112 + let value: IntType 113 + 114 + @usableFromInline var type: MajorType { .nint } 115 + @usableFromInline var argument: UInt8 { IntType.argumentValue } 116 + @usableFromInline var contentSize: Int { IntType.byteCount } 117 + 118 + @usableFromInline 119 + init(value: IntType) { 120 + assert(value >= -Constants.maxArgSize) 121 + assert(value >= IntType.zero) // -1 is encoded as 0 122 + self.value = value // Positive representation 123 + } 124 + 125 + @usableFromInline 126 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 127 + value.write(to: &data) 128 + } 129 + }
+117
Sources/CBOR/Encoder/Optimizers/KeyedOptimizer.swift
··· 1 + // 2 + // KeyedOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + import HeapModule 9 + 10 + @inlinable 11 + func KeyedOptimizer(value: [String: EncodingOptimizer]) -> EncodingOptimizer { 12 + if value.count < Constants.maxArgSize { 13 + return SmallKeyedOptimizer(value: value, optimizer: { StringOptimizer(value: $0) }) 14 + } else { 15 + return LargeKeyedOptimizer(value: value, optimizer: { StringOptimizer(value: $0) }) 16 + } 17 + } 18 + 19 + @inlinable 20 + func KeyedOptimizer(value: [Int: EncodingOptimizer]) -> EncodingOptimizer { 21 + if value.count < Constants.maxArgSize { 22 + return SmallKeyedOptimizer(value: value, optimizer: { IntOptimizer(value: $0) }) 23 + } else { 24 + return LargeKeyedOptimizer(value: value, optimizer: { IntOptimizer(value: $0) }) 25 + } 26 + } 27 + 28 + private struct KeyValue<KeyType: Comparable>: Comparable { 29 + let id: KeyType 30 + var key: EncodingOptimizer 31 + var value: EncodingOptimizer 32 + 33 + @usableFromInline var size: Int { 34 + key.size + value.size 35 + } 36 + 37 + static func < (lhs: KeyValue, rhs: KeyValue) -> Bool { 38 + lhs.id < rhs.id 39 + } 40 + 41 + static func == (lhs: KeyValue, rhs: KeyValue) -> Bool { 42 + lhs.id == rhs.id 43 + } 44 + } 45 + 46 + @usableFromInline 47 + struct SmallKeyedOptimizer<KeyType: Comparable & Hashable>: EncodingOptimizer { 48 + fileprivate var value: Heap<KeyValue<KeyType>> 49 + 50 + @usableFromInline var type: MajorType { .map } 51 + @usableFromInline var argument: UInt8 { UInt8(value.count) } 52 + @usableFromInline var contentSize: Int 53 + 54 + @usableFromInline 55 + init(value: [KeyType: EncodingOptimizer], optimizer: (KeyType) -> EncodingOptimizer) { 56 + (self.value, contentSize) = _makeHeap(value: value, optimizer: optimizer) 57 + } 58 + 59 + @usableFromInline 60 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 61 + _writePayload(heap: value, to: &data) 62 + } 63 + } 64 + 65 + @usableFromInline 66 + struct LargeKeyedOptimizer<KeyType: Comparable & Hashable>: EncodingOptimizer { 67 + fileprivate var value: Heap<KeyValue<KeyType>> 68 + 69 + @usableFromInline var type: MajorType { .map } 70 + @usableFromInline var argument: UInt8 { countToArg(value.count) } 71 + @usableFromInline var headerSize: Int { countToHeaderSize(value.count) } 72 + @usableFromInline var contentSize: Int 73 + 74 + @usableFromInline 75 + init(value: [KeyType: EncodingOptimizer], optimizer: (KeyType) -> EncodingOptimizer) { 76 + (self.value, contentSize) = _makeHeap(value: value, optimizer: optimizer) 77 + } 78 + 79 + @usableFromInline 80 + func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 81 + writeIntToHeader(value.count, data: &data) 82 + } 83 + 84 + @usableFromInline 85 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 86 + _writePayload(heap: value, to: &data) 87 + } 88 + } 89 + 90 + @inline(__always) 91 + private func _makeHeap<KeyType: Comparable & Hashable>( 92 + value: [KeyType: EncodingOptimizer], 93 + optimizer: (KeyType) -> EncodingOptimizer 94 + ) -> (Heap<KeyValue<KeyType>>, contentSize: Int) { 95 + var foundKeys: Set<KeyType> = [] 96 + var heap = Heap<KeyValue<KeyType>>() 97 + var size = 0 98 + for (key, value) in value { 99 + guard !foundKeys.contains(key) else { continue } 100 + foundKeys.insert(key) 101 + let optimized = KeyValue(id: key, key: optimizer(key), value: value) 102 + heap.insert(optimized) 103 + size += optimized.size 104 + } 105 + return (heap, size) 106 + } 107 + 108 + @inline(__always) 109 + private func _writePayload<KeyType: Comparable & Hashable>( 110 + heap: consuming Heap<KeyValue<KeyType>>, 111 + to data: inout Slice<UnsafeMutableRawBufferPointer> 112 + ) { 113 + while var keyValue = heap.popMin() { 114 + keyValue.key.write(to: &data) 115 + keyValue.value.write(to: &data) 116 + } 117 + }
+14
Sources/CBOR/Encoder/Optimizers/NilOptimizer.swift
··· 1 + // 2 + // Optimizers.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + struct NilOptimizer: EncodingOptimizer { 9 + var type: MajorType { .simple } 10 + var argument: UInt8 { 22 } 11 + var contentSize: Int { 0 } 12 + 13 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { } 14 + }
+69
Sources/CBOR/Encoder/Optimizers/StringOptimizer.swift
··· 1 + // 2 + // SmallStringOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @inlinable 9 + func StringOptimizer(value: String) -> EncodingOptimizer { 10 + let bytes = value.utf8 11 + if bytes.count < Constants.maxArgSize { 12 + return SmallStringOptimizer(value: bytes) 13 + } else { 14 + return LargeStringOptimizer(value: bytes) 15 + } 16 + } 17 + 18 + @usableFromInline 19 + struct SmallStringOptimizer: EncodingOptimizer { 20 + let value: String.UTF8View 21 + 22 + @usableFromInline var type: MajorType { .string } 23 + @usableFromInline var argument: UInt8 { UInt8(truncatingIfNeeded: contentSize) } 24 + @usableFromInline var contentSize: Int { value.count } 25 + 26 + @usableFromInline 27 + init(value: String.UTF8View) { 28 + assert(value.count < Constants.maxArgSize) 29 + self.value = value 30 + } 31 + 32 + @usableFromInline 33 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 34 + writeString(value, to: &data) 35 + } 36 + } 37 + 38 + @usableFromInline 39 + struct LargeStringOptimizer: EncodingOptimizer { 40 + let value: String.UTF8View 41 + 42 + @usableFromInline var type: MajorType { .string } 43 + @usableFromInline var argument: UInt8 { countToArg(contentSize) } 44 + @usableFromInline var headerSize: Int { countToHeaderSize(contentSize) } 45 + @usableFromInline var contentSize: Int { value.count } 46 + 47 + @usableFromInline 48 + init(value: String.UTF8View) { 49 + assert(value.count >= Constants.maxArgSize) 50 + self.value = value 51 + } 52 + 53 + @usableFromInline 54 + func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 55 + writeIntToHeader(contentSize, data: &data) 56 + } 57 + 58 + @usableFromInline 59 + func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 60 + writeString(value, to: &data) 61 + } 62 + } 63 + 64 + @inline(__always) 65 + private func writeString(_ value: String.UTF8View, to data: inout Slice<UnsafeMutableRawBufferPointer>) { 66 + assert(data.count >= value.count) 67 + UnsafeMutableRawBufferPointer(rebasing: data).copyBytes(from: value) 68 + data.removeFirst(value.count) 69 + }
+37
Sources/CBOR/Encoder/Optimizers/UUIDOptimizer.swift
··· 1 + // 2 + // UUIDOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + #if canImport(FoundationEssentials) 9 + import FoundationEssentials 10 + #else 11 + import Foundation 12 + #endif 13 + 14 + struct UUIDOptimizer: EncodingOptimizer { 15 + var optimizer: SmallByteStringOptimizer<[UInt8]> 16 + 17 + var type: MajorType { .tagged } 18 + var argument: UInt8 { 24 } 19 + var headerSize: Int { 1 } 20 + var contentSize: Int { optimizer.size } 21 + 22 + init(value: UUID) { 23 + optimizer = withUnsafeBytes(of: value) { ptr in 24 + SmallByteStringOptimizer(value: Array(ptr)) 25 + } 26 + } 27 + 28 + mutating func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 29 + data[data.startIndex] = UInt8(CommonTags.uuid.rawValue) 30 + data.removeFirst() 31 + } 32 + 33 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 34 + optimizer.write(to: &data) 35 + } 36 + 37 + }
+72
Sources/CBOR/Encoder/Optimizers/UnkeyedOptimizer.swift
··· 1 + // 2 + // UnkeyedOptimizer.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @inlinable 9 + func UnkeyedOptimizer(value: [EncodingOptimizer]) -> EncodingOptimizer { 10 + if value.count < Constants.maxArgSize { 11 + return SmallUnkeyedOptimizer(value: value) 12 + } else { 13 + return LargeUnkeyedOptimizer(value: value) 14 + } 15 + } 16 + 17 + @usableFromInline 18 + struct SmallUnkeyedOptimizer: EncodingOptimizer { 19 + fileprivate var value: [EncodingOptimizer] 20 + 21 + @usableFromInline var type: MajorType { .array } 22 + @usableFromInline var argument: UInt8 { UInt8(value.count) } 23 + @usableFromInline var contentSize: Int 24 + 25 + @usableFromInline 26 + init(value: [EncodingOptimizer]) { 27 + assert(value.count < Constants.maxArgSize) 28 + self.value = value 29 + var total = 0 30 + for v in value { total += v.size } 31 + self.contentSize = total 32 + } 33 + 34 + @usableFromInline 35 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 36 + for var optimizer in value { 37 + optimizer.write(to: &data) 38 + } 39 + } 40 + } 41 + 42 + @usableFromInline 43 + struct LargeUnkeyedOptimizer: EncodingOptimizer { 44 + fileprivate var value: [EncodingOptimizer] 45 + 46 + @usableFromInline var type: MajorType { .array } 47 + @usableFromInline var argument: UInt8 { countToArg(value.count) } 48 + @usableFromInline var headerSize: Int { countToHeaderSize(value.count) } 49 + @usableFromInline var contentSize: Int 50 + 51 + @usableFromInline 52 + init(value: [EncodingOptimizer]) { 53 + assert(value.count >= Constants.maxArgSize) 54 + self.value = value 55 + var total = 0 56 + for v in value { total += v.size } 57 + self.contentSize = total 58 + } 59 + 60 + @usableFromInline 61 + func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 62 + // Definite-length array count 63 + writeIntToHeader(value.count, data: &data) 64 + } 65 + 66 + @usableFromInline 67 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 68 + for var optimizer in value { 69 + optimizer.write(to: &data) 70 + } 71 + } 72 + }
+19
Sources/CBOR/Encoder/TemporaryEncodingStorage.swift
··· 1 + // 2 + // TemporaryEncodingStorage.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + /// Temporary Storage. Use while in the process of encoding data. 9 + protocol TemporaryEncodingStorage { 10 + func register(_: EncodingOptimizer) 11 + } 12 + 13 + class TopLevelTemporaryEncodingStorage: TemporaryEncodingStorage { 14 + var value: EncodingOptimizer = EmptyOptimizer() 15 + 16 + func register(_ newValue: EncodingOptimizer) { 17 + value = newValue 18 + } 19 + }
+33
Sources/CBOR/Extensions/FixedWithInteger+write.swift
··· 1 + // 2 + // FixedWidthInteger+write.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + extension FixedWidthInteger { 9 + @inline(__always) 10 + @inlinable 11 + func write(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 12 + assert(Self.byteCount <= data.count) 13 + withUnsafeBytes(of: self.bigEndian) { raw in 14 + UnsafeMutableRawBufferPointer(rebasing: data).copyMemory(from: raw) 15 + } 16 + data.removeFirst(Self.byteCount) 17 + } 18 + 19 + @inline(__always) 20 + @usableFromInline static var argumentValue: UInt8 { 21 + switch byteCount { 22 + case 1: 24 23 + case 2: 25 24 + case 4: 26 25 + default: 27 26 + } 27 + } 28 + 29 + @inline(__always) 30 + @usableFromInline static var byteCount: Int { 31 + bitWidth / 8 32 + } 33 + }
+33
Sources/CBOR/Extensions/Float32+Float16.swift
··· 1 + // 2 + // Float32+Float16.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + extension Float32 { 9 + // https://gist.github.com/martinkallman/5049614 10 + // rewritten to Swift + applied fixes from comments + added NaN/Inf checks 11 + // should be good enough, who cares about float16 12 + @inlinable 13 + static func readFloat16(x: UInt16) -> Float32 { 14 + if (x & 0x7fff) > 0x7c00 { 15 + return Float32.nan 16 + } 17 + if x == 0x7c00 { 18 + return Float32.infinity 19 + } 20 + if x == 0xfc00 { 21 + return -Float32.infinity 22 + } 23 + var t1 = UInt32(x & 0x7fff) // Non-sign bits 24 + var t2 = UInt32(x & 0x8000) // Sign bit 25 + let t3 = UInt32(x & 0x7c00) // Exponent 26 + t1 <<= 13 // Align mantissa on MSB 27 + t2 <<= 16 // Shift sign bit into position 28 + t1 += 0x38000000 // Adjust bias 29 + t1 = (t3 == 0 ? 0 : t1) // Denormals-as-zero 30 + t1 |= t2 // Re-insert sign bit 31 + return Float32(bitPattern: t1) 32 + } 33 + }
+41
Sources/CBOR/Extensions/countTo+.swift
··· 1 + // 2 + // countTo+.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @inline(__always) 9 + func countToArg(_ count: Int) -> UInt8 { 10 + assert(count <= UInt64.max) 11 + return switch count { 12 + case 0...Int(UInt8.max): 24 13 + case 0...Int(UInt16.max): 25 14 + case 0...Int(UInt32.max): 26 15 + default: 27 16 + } 17 + } 18 + 19 + @inline(__always) 20 + func countToHeaderSize(_ count: Int) -> Int { 21 + assert(count <= UInt64.max) 22 + return switch count { 23 + case 0...Int(UInt8.max): 1 24 + case 0...Int(UInt16.max): 2 25 + case 0...Int(UInt32.max): 3 26 + default: 4 27 + } 28 + } 29 + 30 + @inline(__always) 31 + func writeIntToHeader<T: FixedWidthInteger>(_ int: T, data: inout Slice<UnsafeMutableRawBufferPointer>) { 32 + assert(int >= 0) 33 + assert(int <= UInt64.max) 34 + 35 + switch int { 36 + case 0...T(UInt8.max): UInt8(int).write(to: &data) 37 + case 0...T(UInt16.max): UInt16(int).write(to: &data) 38 + case 0...T(UInt32.max): UInt32(int).write(to: &data) 39 + default: UInt64(int).write(to: &data) 40 + } 41 + }
+39
Sources/CBOR/MajorType.swift
··· 1 + // 2 + // MajorType.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + @usableFromInline 9 + enum MajorType: UInt8 { 10 + case uint = 0 11 + case nint = 1 12 + case bytes = 2 13 + case string = 3 14 + case array = 4 15 + case map = 5 16 + case tagged = 6 17 + case simple = 7 18 + 19 + @inlinable 20 + init?(rawValue: UInt8) { 21 + // We only care about the top 3 bits 22 + switch rawValue >> 5 { 23 + case MajorType.uint.rawValue: self = .uint 24 + case MajorType.nint.rawValue: self = .nint 25 + case MajorType.bytes.rawValue: self = .bytes 26 + case MajorType.string.rawValue: self = .string 27 + case MajorType.array.rawValue: self = .array 28 + case MajorType.map.rawValue: self = .map 29 + case MajorType.tagged.rawValue: self = .tagged 30 + case MajorType.simple.rawValue: self = .simple 31 + default: return nil 32 + } 33 + } 34 + 35 + @inline(__always) 36 + @inlinable var bits: UInt8 { 37 + rawValue << 5 38 + } 39 + }
+203
Tests/CBORTests/EncodableTests.swift
··· 1 + // 2 + // EncodableTests.swift 3 + // SwiftCBOR 4 + // 5 + // Created by Khan Winter on 8/17/25. 6 + // 7 + 8 + import Testing 9 + import Foundation 10 + @testable import CBOR 11 + 12 + @Suite("Encodable") 13 + struct EncodableTests { 14 + @Test 15 + func encodeNull() throws { 16 + let encoded = try CBOREncoder().encode(String?(nil)) 17 + #expect(encoded == Data([0xf6])) 18 + } 19 + 20 + @Test 21 + func encodeBools() throws { 22 + let falseVal = try CBOREncoder().encode(false) 23 + #expect(falseVal == Data([0xf4])) 24 + 25 + let trueVal = try CBOREncoder().encode(true) 26 + #expect(trueVal == Data([0xf5])) 27 + } 28 + 29 + @Test 30 + func encodeInts() throws { 31 + // Less than 24 32 + let zero = try CBOREncoder().encode(0) 33 + #expect(zero == Data([0x00])) 34 + 35 + let eight = try CBOREncoder().encode(8) 36 + #expect(eight == Data([0x08])) 37 + 38 + let ten = try CBOREncoder().encode(10) 39 + #expect(ten == Data([0x0a])) 40 + 41 + let twentyThree = try CBOREncoder().encode(23) 42 + #expect(twentyThree == Data([0x17])) 43 + 44 + // Just bigger than 23 45 + let twentyFour = try CBOREncoder().encode(24) 46 + #expect(twentyFour == Data([0x18, 0x18])) 47 + 48 + let twentyFive = try CBOREncoder().encode(25) 49 + #expect(twentyFive == Data([0x18, 0x19])) 50 + 51 + // Bigger 52 + let hundred = try CBOREncoder().encode(100) 53 + #expect(hundred == Data([0x18, 0x64])) 54 + 55 + let thousand = try CBOREncoder().encode(1_000) 56 + #expect(thousand == Data([0x19, 0x03, 0xe8])) 57 + 58 + let million = try CBOREncoder().encode(1_000_000) 59 + #expect(million == Data([0x1a, 0x00, 0x0f, 0x42, 0x40])) 60 + 61 + let trillion = try CBOREncoder().encode(1_000_000_000_000) 62 + #expect(trillion == Data([0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00])) 63 + } 64 + 65 + @Test 66 + func encodeNegativeInts() throws { 67 + // Less than 24 68 + let minusOne = try CBOREncoder().encode(-1) 69 + #expect(minusOne == Data([0x20])) 70 + 71 + let minusTen = try CBOREncoder().encode(-10) 72 + #expect(minusTen == Data([0x29])) 73 + 74 + // Bigger 75 + let minusHundred = try CBOREncoder().encode(-100) 76 + #expect(minusHundred == Data([0x38, 0x63])) 77 + 78 + let minusThousand = try CBOREncoder().encode(-1_000) 79 + #expect(minusThousand == Data([0x39, 0x03, 0xe7])) 80 + 81 + let minusMillion = try CBOREncoder().encode(-1_000_001) 82 + #expect(minusMillion == Data([0x3A, 0x00, 0x0F, 0x42, 0x40])) 83 + 84 + let minusTrillion = try CBOREncoder().encode(-1_000_000_001) 85 + #expect(minusTrillion == Data([0x3A, 0x3B, 0x9A, 0xCA, 0x00])) 86 + } 87 + 88 + @Test 89 + func encodeStrings() throws { 90 + let empty = try CBOREncoder().encode("") 91 + #expect(empty == Data([0x60])) 92 + 93 + let a = try CBOREncoder().encode("a") 94 + #expect(a == Data([0x61, 0x61])) 95 + 96 + let IETF = try CBOREncoder().encode("IETF") 97 + #expect(IETF == Data([0x64, 0x49, 0x45, 0x54, 0x46])) 98 + 99 + let quoteSlash = try CBOREncoder().encode("\"\\") 100 + #expect(quoteSlash == Data([0x62, 0x22, 0x5c])) 101 + 102 + let littleUWithDiaeresis = try CBOREncoder().encode("\u{00FC}") 103 + #expect(littleUWithDiaeresis == Data([0x62, 0xc3, 0xbc])) 104 + } 105 + 106 + @Test 107 + func encodeByteStrings() throws { 108 + let fourByteByteString = try CBOREncoder().encode(Data([0x01, 0x02, 0x03, 0x04])) 109 + #expect(fourByteByteString == Data([0x44, 0x01, 0x02, 0x03, 0x04])) 110 + } 111 + 112 + @Test 113 + func mixedByteArraysEncodeCorrectly() throws { 114 + /// See note in ``UnkeyeyedCBOREncodingContainer`` about mixed collections of ints 115 + struct Mixed: Encodable { 116 + func encode(to encoder: any Encoder) throws { 117 + var container = encoder.unkeyedContainer() 118 + try container.encode(UInt8.zero) 119 + try container.encode(UInt8.max) 120 + try container.encode("Hello World") 121 + try container.encode(1000000) 122 + } 123 + } 124 + 125 + let data = try CBOREncoder().encode(Mixed()) 126 + // swiftlint:disable:next line_length 127 + #expect(data == Data([0x84, 0x00, 0x18, 0xff, 0x6b, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x00, 0x0f, 0x42, 0x40])) 128 + } 129 + 130 + @Test 131 + func encodeArrays() throws { 132 + let empty = try CBOREncoder().encode([String]()) 133 + #expect(empty == Data([0x80])) 134 + 135 + let oneTwoThree = try CBOREncoder().encode([1, 2, 3]) 136 + #expect(oneTwoThree == Data([0x83, 0x01, 0x02, 0x03])) 137 + 138 + // swiftlint:disable:next line_length 139 + let lotsOfInts = try CBOREncoder().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]) 140 + 141 + // swiftlint:disable:next line_length 142 + #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])) 143 + 144 + let nestedSimple = try CBOREncoder().encode([[1], [2, 3], [4, 5]]) 145 + #expect(nestedSimple == Data([0x83, 0x81, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05])) 146 + } 147 + 148 + @Test 149 + func encodeMaps() throws { 150 + let empty = try CBOREncoder().encode([String: String]()) 151 + #expect(empty == Data([0xa0])) 152 + 153 + let stringToString = try CBOREncoder().encode(["a": "A", "b": "B", "c": "C", "d": "D", "e": "E"]) 154 + #expect(stringToString.first! == 0xa5) 155 + 156 + let dataMinusFirstByte = stringToString[1...] 157 + .map { $0 } 158 + .chunked(into: 4) 159 + .sorted(by: { $0.lexicographicallyPrecedes($1) }) 160 + let dataForKeyValuePairs: [[UInt8]] = [ 161 + [0x61, 0x61, 0x61, 0x41], 162 + [0x61, 0x62, 0x61, 0x42], 163 + [0x61, 0x63, 0x61, 0x43], 164 + [0x61, 0x64, 0x61, 0x44], 165 + [0x61, 0x65, 0x61, 0x45] 166 + ] 167 + #expect(dataMinusFirstByte == dataForKeyValuePairs) 168 + 169 + let oneTwoThreeFour = try CBOREncoder().encode([1: 2, 3: 4]) 170 + #expect( 171 + oneTwoThreeFour == Data([0xa2, 0x01, 0x02, 0x03, 0x04]) || 172 + oneTwoThreeFour == Data([0xa2, 0x03, 0x04, 0x01, 0x02]) 173 + ) 174 + 175 + let encoder = CBOREncoder(forceStringKeys: true) 176 + let encodedWithStringKeys = try encoder.encode([1: 2, 3: 4]) 177 + #expect( 178 + encodedWithStringKeys == Data([0xa2, 0x61, 0x31, 0x02, 0x61, 0x33, 0x04]) 179 + ) 180 + } 181 + 182 + @Test 183 + func encodeSimpleStructs() throws { 184 + struct MyStruct: Codable { 185 + let age: Int 186 + let name: String 187 + } 188 + 189 + let encoded = try CBOREncoder().encode(MyStruct(age: 27, name: "Ham")).map { $0 } 190 + 191 + #expect( 192 + encoded == [0xa2, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1b, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x63, 0x48, 0x61, 0x6d] 193 + ) 194 + } 195 + } 196 + 197 + extension Array { 198 + func chunked(into size: Int) -> [[Element]] { 199 + stride(from: 0, to: count, by: size).map { 200 + Array(self[$0 ..< Swift.min($0 + size, count)]) 201 + } 202 + } 203 + }