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
at main 326 lines 12 kB view raw
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}