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
1//
2// DAGCBORTests.swift
3// CBOR
4//
5// Created by Khan Winter on 10/14/25.
6//
7
8import Foundation
9import Testing
10@testable import CBOR
11
12// swiftlint:disable:next private_over_fileprivate
13fileprivate struct CID: CIDType {
14 let data: Data
15
16 init(data: Data) {
17 self.data = data
18 }
19
20 init<Container: SingleValueDecodingContainer>(decodeTaggedDataUsing container: Container) throws {
21 var data = try container.decode(Data.self)
22 data.removeFirst()
23 self.data = data
24 }
25
26 func encodeTaggedData<Container: SingleValueEncodingContainer>(using container: inout Container) throws {
27 try container.encode(Data([0x0]) + data)
28 }
29}
30
31@Suite
32struct DAGCBOREncoderTests {
33 @Test
34 func customCIDTypeEncodedCorrectly() throws {
35 let cid = CID(data: [0, 1, 2, 3, 4, 5, 6, 7, 8])
36 let data = try DAGCBOREncoder().encode(cid).hexString()
37 #expect(data == "d82a4a00000102030405060708")
38 }
39
40 @Test
41 func knownValidBase256EncodedID() throws {
42 let cid = CID(data: "017112209fe4ccc6de16724f3a30c7e8f254f3c6471986acb1f8d8cf8e96ce2ad7dbe7fb".asHexData())
43 let data = try DAGCBOREncoder().encode(cid).hexString()
44 let expected = "d82a582500017112209FE4CCC6DE16724F3A30C7E8F254F3C6471986ACB1F8D8CF8E96CE2AD7DBE7FB".lowercased()
45 #expect(data == expected)
46 }
47
48 // MARK: Copied from EncodableTests
49
50 @Test
51 func encodeNull() throws {
52 let encoded = try DAGCBOREncoder().encode(String?(nil))
53 #expect(encoded == Data([0xf6]))
54 }
55
56 @Test
57 func encodeBools() throws {
58 let falseVal = try DAGCBOREncoder().encode(false)
59 #expect(falseVal == Data([0xf4]))
60
61 let trueVal = try DAGCBOREncoder().encode(true)
62 #expect(trueVal == Data([0xf5]))
63 }
64
65 @Test
66 func encodeInts() throws {
67 // Less than 24
68 let zero = try DAGCBOREncoder().encode(0)
69 #expect(zero == Data([0x00]))
70
71 let eight = try DAGCBOREncoder().encode(8)
72 #expect(eight == Data([0x08]))
73
74 let ten = try DAGCBOREncoder().encode(10)
75 #expect(ten == Data([0x0a]))
76
77 let twentyThree = try DAGCBOREncoder().encode(23)
78 #expect(twentyThree == Data([0x17]))
79
80 // Just bigger than 23
81 let twentyFour = try DAGCBOREncoder().encode(24)
82 #expect(twentyFour == Data([0x18, 0x18]))
83
84 let twentyFive = try DAGCBOREncoder().encode(25)
85 #expect(twentyFive == Data([0x18, 0x19]))
86
87 // Bigger
88 let hundred = try DAGCBOREncoder().encode(100)
89 #expect(hundred == Data([0x18, 0x64]))
90
91 let thousand = try DAGCBOREncoder().encode(1_000)
92 #expect(thousand == Data([0x19, 0x03, 0xe8]))
93
94 let million = try DAGCBOREncoder().encode(1_000_000)
95 #expect(million == Data([0x1a, 0x00, 0x0f, 0x42, 0x40]))
96
97 let trillion = try DAGCBOREncoder().encode(1_000_000_000_000)
98 #expect(trillion == Data([0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00]))
99 }
100
101 @Test
102 func encodeNegativeInts() throws {
103 // Less than 24
104 let minusOne = try DAGCBOREncoder().encode(-1)
105 #expect(minusOne == Data([0x20]))
106
107 let minusTen = try DAGCBOREncoder().encode(-10)
108 #expect(minusTen == Data([0x29]))
109
110 // Bigger
111 let minusHundred = try DAGCBOREncoder().encode(-100)
112 #expect(minusHundred == Data([0x38, 0x63]))
113
114 let minusThousand = try DAGCBOREncoder().encode(-1_000)
115 #expect(minusThousand == Data([0x39, 0x03, 0xe7]))
116
117 let minusMillion = try DAGCBOREncoder().encode(-1_000_001)
118 #expect(minusMillion == Data([0x3A, 0x00, 0x0F, 0x42, 0x40]))
119
120 let minusTrillion = try DAGCBOREncoder().encode(-1_000_000_001)
121 #expect(minusTrillion == Data([0x3A, 0x3B, 0x9A, 0xCA, 0x00]))
122 }
123
124 @Test
125 func encodeFloat() throws {
126 let value: Float = 100000.0
127 // Different.
128 let data = "fb40f86a0000000000".asHexData()
129 let result = try DAGCBOREncoder().encode(value)
130 #expect(data == result)
131 }
132
133 @Test
134 func encodeDouble() throws {
135 let value: Double = 0.10035
136 let data = "FB3FB9B089A0275254".asHexData()
137 let result = try DAGCBOREncoder().encode(value)
138 #expect(data == result)
139 }
140
141 @Test(arguments: [Double.nan, Double.infinity, -Double.infinity])
142 func NanAndInfiniteThrowErrors(value: Double) throws {
143 #expect(throws: EncodingError.self) {
144 try DAGCBOREncoder().encode(value)
145 }
146 }
147
148 @Test
149 func encodeStrings() throws {
150 let empty = try DAGCBOREncoder().encode("")
151 #expect(empty == Data([0x60]))
152
153 let a = try DAGCBOREncoder().encode("a")
154 #expect(a == Data([0x61, 0x61]))
155
156 let IETF = try DAGCBOREncoder().encode("IETF")
157 #expect(IETF == Data([0x64, 0x49, 0x45, 0x54, 0x46]))
158
159 let quoteSlash = try DAGCBOREncoder().encode("\"\\")
160 #expect(quoteSlash == Data([0x62, 0x22, 0x5c]))
161
162 let littleUWithDiaeresis = try DAGCBOREncoder().encode("\u{00FC}")
163 #expect(littleUWithDiaeresis == Data([0x62, 0xc3, 0xbc]))
164 }
165
166 @Test
167 func encodeByteStrings() throws {
168 let fourByteByteString = try DAGCBOREncoder().encode(Data([0x01, 0x02, 0x03, 0x04]))
169 #expect(fourByteByteString == Data([0x44, 0x01, 0x02, 0x03, 0x04]))
170 }
171
172 @Test
173 func mixedByteArraysEncodeCorrectly() throws {
174 // TODO: Make the container swap to mixed mode if necessary.
175
176 /// See note in ``UnkeyeyedCBOREncodingContainer`` about mixed collections of ints
177 struct Mixed: Encodable {
178 func encode(to encoder: any Encoder) throws {
179 var container = encoder.unkeyedContainer()
180 try container.encode(UInt8.zero)
181 try container.encode(UInt8.max)
182 try container.encode("Hello World")
183 try container.encode(1000000)
184 }
185 }
186
187 let data = try DAGCBOREncoder().encode(Mixed())
188 // swiftlint:disable:next line_length
189 #expect(data == Data([0x84, 0x00, 0x18, 0xff, 0x6b, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x00, 0x0f, 0x42, 0x40]))
190 }
191
192 @Test
193 func encodeArrays() throws {
194 let empty = try DAGCBOREncoder().encode([String]())
195 #expect(empty == Data([0x80]))
196
197 let oneTwoThree = try DAGCBOREncoder().encode([1, 2, 3])
198 #expect(oneTwoThree == Data([0x83, 0x01, 0x02, 0x03]))
199
200 // swiftlint:disable:next line_length
201 let lotsOfInts = try DAGCBOREncoder().encode([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25])
202
203 // swiftlint:disable:next line_length
204 #expect(lotsOfInts == Data([0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19]))
205
206 let nestedSimple = try DAGCBOREncoder().encode([[1], [2, 3], [4, 5]])
207 #expect(nestedSimple == Data([0x83, 0x81, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05]))
208 }
209
210 @Test
211 func encodeMaps() throws {
212 let empty = try DAGCBOREncoder().encode([String: String]())
213 #expect(empty == Data([0xa0]))
214
215 let stringToString = try DAGCBOREncoder().encode(["a": "A", "b": "B", "c": "C", "d": "D", "e": "E"])
216 #expect(stringToString.first! == 0xa5)
217
218 let dataMinusFirstByte = stringToString[1...]
219 .map { $0 }
220 .chunked(into: 4)
221 .sorted(by: { $0.lexicographicallyPrecedes($1) })
222 let dataForKeyValuePairs: [[UInt8]] = [
223 [0x61, 0x61, 0x61, 0x41],
224 [0x61, 0x62, 0x61, 0x42],
225 [0x61, 0x63, 0x61, 0x43],
226 [0x61, 0x64, 0x61, 0x44],
227 [0x61, 0x65, 0x61, 0x45]
228 ]
229 #expect(dataMinusFirstByte == dataForKeyValuePairs)
230
231 let encoder = DAGCBOREncoder()
232 let encodedWithStringKeys = try encoder.encode([1: 2, 3: 4])
233 #expect(
234 encodedWithStringKeys == Data([0xa2, 0x61, 0x31, 0x02, 0x61, 0x33, 0x04])
235 )
236 }
237
238 @Test
239 func encodeSimpleStructs() throws {
240 struct MyStruct: Codable {
241 let age: Int
242 let name: String
243 }
244
245 let encoded = try DAGCBOREncoder().encode(MyStruct(age: 27, name: "Ham")).map { $0 }
246
247 #expect(
248 encoded == [0xa2, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1b, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x63, 0x48, 0x61, 0x6d]
249 )
250 }
251
252 @Test
253 func encodeMoreComplexStructs() throws {
254 let encoder = DAGCBOREncoder()
255
256 let data = try encoder.encode(Company.mock).hexString().uppercased()
257 // swiftlint:disable:next line_length
258 #expect(data == "A4646E616D656941636D6520436F727067666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F746569656D706C6F796565738AA563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5")
259 }
260
261 @Test
262 func uint16() throws {
263 let encoder = DAGCBOREncoder()
264 let data = try encoder.encode(1999)
265 #expect(data == "1907CF".asHexData())
266 }
267
268 @Test(
269 .disabled("Disabled until we can figure out how to make Float16 compile on all platforms."),
270 arguments: [
271 ("F90000", 0),
272 ("F93C00", 1.0),
273 ("F9BE00", -1.5),
274 ("F97C00", .infinity),
275 ("F93E32", 1.548828125),
276 ("F9F021", -8456)
277 ]
278 )
279 func halfFloat(expected: String, value: Float16) throws {
280 let expectedData = expected.asHexData()
281 let encoder = DAGCBOREncoder()
282 let data = try encoder.encode(value)
283 #expect(data == expectedData)
284 }
285
286 @Test
287 func duplicateKeysAreDeduplicatedOnEncode() throws {
288 let encoder = DAGCBOREncoder()
289 struct Mock: Encodable {
290 enum CodingKeys: String, CodingKey {
291 case one
292 }
293
294 func encode(to encoder: any Encoder) throws {
295 var container = encoder.container(keyedBy: CodingKeys.self)
296 try container.encode(true, forKey: .one)
297 try container.encode(false, forKey: .one)
298 }
299 }
300
301 let data = try encoder.encode(Mock()).hexString()
302 #expect(data == "a1636f6e65f4")
303 }
304
305 @Test
306 func uuid() throws {
307 let uuid = UUID(uuidString: "A0BDA068-60AD-4111-B4C9-04746791028B")
308 #expect(throws: EncodingError.self) {
309 try DAGCBOREncoder().encode(uuid)
310 }
311 }
312
313 @Test
314 func orderedMapKeys() throws {
315 let value = ["a": 1, "b": 2, "aa": 3]
316 let encoded = try DAGCBOREncoder().encode(value).hexString()
317 #expect(encoded == "a361610161620262616103")
318 }
319
320 @Test
321 func emptyData() throws {
322 let value = Data()
323 let encoded = try DAGCBOREncoder().encode(value).hexString()
324 #expect(encoded == "40")
325 }
326}