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

Finished Dates and Tags, Fixed encoder and non-stdlib types

+397 -287
+3 -1
Sources/CBOR/Decoder/CBORDecoder.swift
··· 45 } 46 47 let context = DecodingContext(scanner: scanner) 48 - let region = scanner.load(at: 0) 49 50 return try SingleValueCBORDecodingContainer(context: context, data: region).decode(T.self) 51 } ··· 97 + "found \(found) at \(offset)" 98 ) 99 ) 100 } 101 } 102 }
··· 45 } 46 47 let context = DecodingContext(scanner: scanner) 48 + let region = scanner.results.load(at: 0, reader: scanner.reader) 49 50 return try SingleValueCBORDecodingContainer(context: context, data: region).decode(T.self) 51 } ··· 97 + "found \(found) at \(offset)" 98 ) 99 ) 100 + case .noTagInformation, .invalidMajorTypeForTaggedItem: 101 + throw error // rethrow these guys 102 } 103 } 104 }
-19
Sources/CBOR/Decoder/Containers/DataRegion.swift
··· 154 return F(val) 155 } 156 } 157 - 158 - internal extension UInt8 { 159 - func byteCount() -> UInt8? { 160 - switch self { 161 - case let value where value < Constants.maxArgSize: 162 - 0 163 - case 24: 164 - 1 165 - case 25: 166 - 2 167 - case 26: 168 - 4 169 - case 27: 170 - 8 171 - default: 172 - nil 173 - } 174 - } 175 - }
··· 154 return F(val) 155 } 156 }
+1 -1
Sources/CBOR/Decoder/Containers/DecodingContextContainer.swift
··· 21 @discardableResult 22 func checkType<T>( _ types: MajorType..., arguments: UInt8..., as: T.Type) throws -> UInt8 { 23 guard types.contains(data.type) else { 24 - throw DecodingError.typeMismatch(Bool.self, context.error("Unexpected type found: \(data.type).")) 25 } 26 27 let argument = data.argument // Already checked with type()
··· 21 @discardableResult 22 func checkType<T>( _ types: MajorType..., arguments: UInt8..., as: T.Type) throws -> UInt8 { 23 guard types.contains(data.type) else { 24 + throw DecodingError.typeMismatch(T.self, context.error("Unexpected type found: \(data.type).")) 25 } 26 27 let argument = data.argument // Already checked with type()
+5 -5
Sources/CBOR/Decoder/Containers/KeyedCBORDecodingContainer.swift
··· 46 guard let childCount = data.childCount else { fatalError("Map scanned but no child count recorded.") } 47 decodedKeys.reserveCapacity(childCount / 2) 48 49 - var mapOffset = context.scanner.firstChildIndex(data.mapOffset) 50 for _ in 0..<childCount / 2 { 51 - let key = context.scanner.load(at: mapOffset) 52 let decodedKey: AnyKey 53 switch key.type { 54 case .uint, .nint: ··· 63 context.error("Invalid key type found in map. Found \(key.type), expected an integer or string.") 64 ) 65 } 66 - mapOffset = context.scanner.siblingIndex(mapOffset) 67 68 - let value = context.scanner.load(at: mapOffset) 69 - mapOffset = context.scanner.siblingIndex(mapOffset) 70 71 decodedKeys[decodedKey] = value 72 }
··· 46 guard let childCount = data.childCount else { fatalError("Map scanned but no child count recorded.") } 47 decodedKeys.reserveCapacity(childCount / 2) 48 49 + var mapOffset = context.scanner.results.firstChildIndex(data.mapOffset) 50 for _ in 0..<childCount / 2 { 51 + let key = context.scanner.results.load(at: mapOffset, reader: context.scanner.reader) 52 let decodedKey: AnyKey 53 switch key.type { 54 case .uint, .nint: ··· 63 context.error("Invalid key type found in map. Found \(key.type), expected an integer or string.") 64 ) 65 } 66 + mapOffset = context.scanner.results.siblingIndex(mapOffset) 67 68 + let value = context.scanner.results.load(at: mapOffset, reader: context.scanner.reader) 69 + mapOffset = context.scanner.results.siblingIndex(mapOffset) 70 71 decodedKeys[decodedKey] = value 72 }
+44 -28
Sources/CBOR/Decoder/Containers/SingleValueCBORDecodingContainer.swift
··· 37 } 38 39 func decode(_: Float.Type) throws -> Float { 40 - try checkType(.simple, arguments: 26, as: Float.self) 41 let floatRaw = try data.read(as: UInt32.self) 42 return Float(bitPattern: floatRaw) 43 } ··· 116 } 117 118 private func _decode(_: Date.Type) throws -> Date { 119 - // Will pop this first byte. 120 let argument = try checkType(.tagged, arguments: 0, 1, as: Date.self) 121 if argument == 0 { 122 // String ··· 127 } 128 129 private func decodeStringDate() throws -> Date { 130 - let string = try decode(String.self) 131 guard let date = ISO8601DateFormatter().date(from: string) else { 132 throw DecodingError.dataCorrupted(context.error("Failed to decode date from \"\(string)\"")) 133 } ··· 136 137 private func decodeEpochDate() throws -> Date { 138 // Epoch Timestamp, can be a floating point or positive/negative integer value 139 - switch data.type { 140 - case .uint: 141 - let int = try data.readInt(as: UInt.self) 142 return Date(timeIntervalSince1970: Double(int)) 143 - case .nint: 144 - let int = -1 - (try data.readInt(as: Int.self)) 145 return Date(timeIntervalSince1970: Double(int)) 146 - case .simple: 147 - switch data.argument { 148 - case 26: 149 - // Float 150 - let float = try decode(Float.self) 151 - return Date(timeIntervalSince1970: Double(float)) 152 - case 27: 153 - // Double 154 - let double = try decode(Double.self) 155 - return Date(timeIntervalSince1970: double) 156 - default: 157 - throw DecodingError.typeMismatch( 158 - Date.self, 159 - context.error("Could not find valid data type for Epoch Date.") 160 - ) 161 - } 162 default: 163 throw DecodingError.typeMismatch( 164 Date.self, 165 - context.error("Could not find valid data type for Epoch Date.") 166 ) 167 } 168 } 169 170 private func _decode(_: UUID.Type) throws -> UUID { 171 try checkType(.tagged, arguments: 24, as: UUID.self) 172 - guard data.canRead(17) else { // UUID size + tag 173 - throw DecodingError.valueNotFound(Bool.self, context.error("Unexpected end of data")) 174 } 175 - let data = data[1..<17] 176 return data.withUnsafeBytes { ptr in ptr.load(as: UUID.self) } 177 } 178
··· 37 } 38 39 func decode(_: Float.Type) throws -> Float { 40 + let arg = try checkType(.simple, arguments: 25, 26, as: Float.self) 41 + if arg == 25 { 42 + let floatRaw = try data.read(as: UInt16.self) 43 + guard let value = Float(halfPrecision: floatRaw) else { 44 + throw DecodingError.dataCorrupted( 45 + context.error("Could not decode half-precision float into Swift Float.") 46 + ) 47 + } 48 + return value 49 + } 50 + 51 let floatRaw = try data.read(as: UInt32.self) 52 return Float(bitPattern: floatRaw) 53 } ··· 126 } 127 128 private func _decode(_: Date.Type) throws -> Date { 129 let argument = try checkType(.tagged, arguments: 0, 1, as: Date.self) 130 if argument == 0 { 131 // String ··· 136 } 137 138 private func decodeStringDate() throws -> Date { 139 + let taggedData = context.scanner.results.loadTagData( 140 + tagMapIndex: data.mapOffset, 141 + reader: context.scanner.reader 142 + ) 143 + let string = try SingleValueCBORDecodingContainer(context: context, data: taggedData).decode(String.self) 144 guard let date = ISO8601DateFormatter().date(from: string) else { 145 throw DecodingError.dataCorrupted(context.error("Failed to decode date from \"\(string)\"")) 146 } ··· 149 150 private func decodeEpochDate() throws -> Date { 151 // Epoch Timestamp, can be a floating point or positive/negative integer value 152 + let taggedData = context.scanner.results.loadTagData( 153 + tagMapIndex: data.mapOffset, 154 + reader: context.scanner.reader 155 + ) 156 + switch (taggedData.type, taggedData.argument) { 157 + case (.uint, _): 158 + let int = try taggedData.readInt(as: Int.self) 159 return Date(timeIntervalSince1970: Double(int)) 160 + case (.nint, _): 161 + let int = -1 - (try taggedData.readInt(as: Int.self)) 162 return Date(timeIntervalSince1970: Double(int)) 163 + case (.simple, 25), (.simple, 26): 164 + // Float 165 + let float = try SingleValueCBORDecodingContainer(context: context, data: taggedData).decode(Float.self) 166 + return Date(timeIntervalSince1970: Double(float)) 167 + case (.simple, 27): 168 + // Double 169 + let double = try SingleValueCBORDecodingContainer(context: context, data: taggedData).decode(Double.self) 170 + return Date(timeIntervalSince1970: double) 171 default: 172 throw DecodingError.typeMismatch( 173 Date.self, 174 + context.error("Invalid type found for epoch date: \(taggedData.type) at \(taggedData.globalIndex)") 175 ) 176 } 177 } 178 179 private func _decode(_: UUID.Type) throws -> UUID { 180 try checkType(.tagged, arguments: 24, as: UUID.self) 181 + let taggedData = context.scanner.results.loadTagData( 182 + tagMapIndex: data.mapOffset, 183 + reader: context.scanner.reader 184 + ) 185 + let data = try SingleValueCBORDecodingContainer(context: context, data: taggedData).decode(Data.self) 186 + guard data.count == 16 else { // UUID size + tag 187 + throw DecodingError.dataCorruptedError( 188 + in: self, 189 + debugDescription: "Data decoded for UUID tag is not 16 bytes long." 190 + ) 191 } 192 return data.withUnsafeBytes { ptr in ptr.load(as: UUID.self) } 193 } 194
+4 -4
Sources/CBOR/Decoder/Containers/UnkeyedCBORDecodingContainer.swift
··· 25 try checkType(.array) 26 guard let childCount = data.childCount else { fatalError("Array scanned but no child count recorded.") } 27 self.count = childCount 28 - self.currentMapIndex = context.scanner.firstChildIndex(data.mapOffset) 29 } 30 31 /// Consumes one decoder off the scanned array. 32 private mutating func consumeDecoder() throws -> SingleValueCBORDecodingContainer { 33 defer { 34 currentIndex += 1 35 - currentMapIndex = context.scanner.siblingIndex(currentMapIndex) 36 } 37 38 - let region = context.scanner.load(at: currentMapIndex) 39 return SingleValueCBORDecodingContainer( 40 context: context.appending(UnkeyedCodingKey(intValue: currentIndex)), 41 data: region ··· 43 } 44 45 mutating func decodeNil() throws -> Bool { 46 - context.scanner.load(at: currentMapIndex).isNil() 47 } 48 49 mutating func decode<T: Decodable>(_ type: T.Type) throws -> T {
··· 25 try checkType(.array) 26 guard let childCount = data.childCount else { fatalError("Array scanned but no child count recorded.") } 27 self.count = childCount 28 + self.currentMapIndex = context.scanner.results.firstChildIndex(data.mapOffset) 29 } 30 31 /// Consumes one decoder off the scanned array. 32 private mutating func consumeDecoder() throws -> SingleValueCBORDecodingContainer { 33 defer { 34 currentIndex += 1 35 + currentMapIndex = context.scanner.results.siblingIndex(currentMapIndex) 36 } 37 38 + let region = context.scanner.results.load(at: currentMapIndex, reader: context.scanner.reader) 39 return SingleValueCBORDecodingContainer( 40 context: context.appending(UnkeyedCodingKey(intValue: currentIndex)), 41 data: region ··· 43 } 44 45 mutating func decodeNil() throws -> Bool { 46 + context.scanner.results.load(at: currentMapIndex, reader: context.scanner.reader).isNil() 47 } 48 49 mutating func decode<T: Decodable>(_ type: T.Type) throws -> T {
+3 -3
Sources/CBOR/Decoder/Scanner/CBORScanner+DebugDescription.swift
··· 12 func indent(_ other: String, d: Int) { string += String(repeating: " ", count: d * 2) + other + "\n" } 13 14 func gen(_ idx: Int, depth: Int) { 15 - let value = load(at: idx) 16 switch value.type { 17 case .map, .array: 18 indent( 19 "\(value.type), mapIdx: \(value.mapOffset), children: \(value.childCount!), bytes: \(value.count)", 20 d: depth 21 ) 22 - var idx = firstChildIndex(idx) 23 for _ in 0..<value.childCount! { 24 gen(idx, depth: depth + 1) 25 - idx = siblingIndex(idx) 26 } 27 default: 28 indent("\(value.type), mapIdx: \(value.mapOffset), arg: \(value.argument)", d: depth)
··· 12 func indent(_ other: String, d: Int) { string += String(repeating: " ", count: d * 2) + other + "\n" } 13 14 func gen(_ idx: Int, depth: Int) { 15 + let value = results.load(at: idx, reader: reader) 16 switch value.type { 17 case .map, .array: 18 indent( 19 "\(value.type), mapIdx: \(value.mapOffset), children: \(value.childCount!), bytes: \(value.count)", 20 d: depth 21 ) 22 + var idx = results.firstChildIndex(idx) 23 for _ in 0..<value.childCount! { 24 gen(idx, depth: depth + 1) 25 + idx = results.siblingIndex(idx) 26 } 27 default: 28 indent("\(value.type), mapIdx: \(value.mapOffset), arg: \(value.argument)", d: depth)
+140
Sources/CBOR/Decoder/Scanner/CBORScanner+Results.swift
···
··· 1 + // 2 + // ScanItem.swift 3 + // CBOR 4 + // 5 + // Created by Khan Winter on 9/1/25. 6 + // 7 + 8 + extension CBORScanner { 9 + /// After the scanner scans, this contains a map that allows the CBOR data to be scanned for values at arbitrary 10 + /// positions, keys, etc. The map contents are represented literally as ints for performance but uses the 11 + /// following map: 12 + /// ``` 13 + /// enum ScanItem: Int { 14 + /// case map // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 15 + /// case array // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 16 + /// 17 + /// case int // (offset: Int, byteCount: Int) 18 + /// case string 19 + /// case byteString 20 + /// case simple // (offset: Int) 21 + /// case tagged 22 + /// } 23 + /// ``` 24 + struct Results { 25 + var map: [Int] = [] 26 + 27 + init(dataCount: Int) { 28 + self.map = [] 29 + map.reserveCapacity(dataCount * 4) 30 + } 31 + 32 + mutating func recordMapStart(currentByteIndex: Int) -> Int { 33 + map.append(MajorType.map.intValue) 34 + map.append(0) 35 + map.append(map.count + 3) 36 + map.append(currentByteIndex) 37 + map.append(currentByteIndex) 38 + return map.count - 5 39 + } 40 + 41 + mutating func recordArrayStart(currentByteIndex: Int) -> Int { 42 + map.append(MajorType.array.intValue) 43 + map.append(0) // child count 44 + map.append(map.count + 3) // map count 45 + map.append(currentByteIndex) // start byte 46 + map.append(currentByteIndex) // byte count 47 + return map.count - 5 48 + } 49 + 50 + mutating func recordEnd(childCount: Int, resultLocation: Int, currentByteIndex: Int) { 51 + map[resultLocation + 1] = childCount 52 + map[resultLocation + 2] = map.count - map[resultLocation + 2] 53 + map[resultLocation + 4] = currentByteIndex - map[resultLocation + 4] 54 + } 55 + 56 + mutating func recordType(_ type: UInt8, currentByteIndex: Int, length: Int) { 57 + map.append(Int(type)) 58 + map.append(currentByteIndex) 59 + map.append(length) 60 + } 61 + 62 + mutating func recordSimple(_ type: UInt8, currentByteIndex: Int) { 63 + map.append(Int(type)) 64 + map.append(currentByteIndex) 65 + } 66 + 67 + // MARK: - Load Values 68 + 69 + func load(at mapIndex: Int, reader: DataReader) -> DataRegion { 70 + assert(mapIndex < map.count) 71 + let byte = UInt8(map[mapIndex]) 72 + let argument = byte & 0b1_1111 73 + guard let type = MajorType(rawValue: byte) else { 74 + fatalError("Invalid type found in map: \(map[mapIndex]) at index: \(mapIndex)") 75 + } 76 + switch type { 77 + case .uint, .nint, .bytes, .string, .tagged: 78 + assert(mapIndex + 1 < map.count) 79 + let location = map[mapIndex + 1] 80 + let length = map[mapIndex + 2] 81 + let slice = reader.slice(location..<(location + length)) 82 + return DataRegion(type: type, argument: argument, childCount: nil, mapOffset: mapIndex, data: slice) 83 + case .simple: 84 + let length = UInt8(map[mapIndex]).simpleLength() 85 + let slice: Slice<UnsafeRawBufferPointer> 86 + if length == 0 { 87 + slice = reader.slice(0..<0) 88 + } else { 89 + let location = map[mapIndex + 1] + 1 // skip type & arg byte. 90 + slice = reader.slice(location..<(location + length)) 91 + } 92 + return DataRegion(type: type, argument: argument, childCount: nil, mapOffset: mapIndex, data: slice) 93 + case .array, .map: 94 + // Map determines the slice size 95 + let childCount = map[mapIndex + 1] 96 + let location = map[mapIndex + 3] 97 + let length = map[mapIndex + 4] 98 + let slice = reader.slice(location..<(location + length)) 99 + return DataRegion(type: type, argument: argument, childCount: childCount, mapOffset: mapIndex, data: slice) 100 + } 101 + } 102 + 103 + // MARK: - Map Navigation 104 + 105 + func firstChildIndex(_ mapIndex: Int) -> Int { 106 + let byte = UInt8(map[mapIndex]) 107 + guard let type = MajorType(rawValue: byte) else { 108 + fatalError("Invalid type found in map: \(map[mapIndex]) at index: \(mapIndex)") 109 + } 110 + switch type { 111 + case .uint, .nint, .bytes, .string, .tagged, .simple: 112 + fatalError("Can't find child index for non-container type.") 113 + case .array, .map: // type byte + 4 map values 114 + return mapIndex + 5 115 + } 116 + } 117 + 118 + func siblingIndex(_ mapIndex: Int) -> Int { 119 + let byte = UInt8(map[mapIndex]) 120 + guard let type = MajorType(rawValue: byte) else { 121 + fatalError("Invalid type found in map: \(map[mapIndex]) at index: \(mapIndex)") 122 + } 123 + switch type { 124 + case .uint, .nint, .bytes, .string: 125 + return mapIndex + 3 126 + case .simple: 127 + return mapIndex + 2 128 + case .array, .map: // Map contains the map/array count 129 + return mapIndex + 5 + map[mapIndex + 2] 130 + case .tagged: 131 + // Find the next index after the scanned data for the tagged item. 132 + return siblingIndex(mapIndex + 3) 133 + } 134 + } 135 + 136 + func loadTagData(tagMapIndex mapIndex: Int, reader: DataReader) -> DataRegion { 137 + return load(at: mapIndex + 3, reader: reader) 138 + } 139 + } 140 + }
+47 -148
Sources/CBOR/Decoder/Scanner/CBORScanner.swift
··· 16 case typeInIndeterminateString(type: MajorType, offset: Int) 17 case rejectedIndeterminateLength(type: MajorType, offset: Int) 18 case cannotRepresentInt(max: UInt, found: UInt, offset: Int) 19 } 20 21 /// # Why Scan? ··· 32 /// take either indeterminate or specific lengths and decode them. 33 @usableFromInline 34 final class CBORScanner { 35 - // MARK: - Results 36 - 37 - /// After the scanner scans, this contains a map that allows the CBOR data to be scanned for values at arbitrary 38 - /// positions, keys, etc. The map contents are represented literally as ints for performance but uses the 39 - /// following map: 40 - /// ``` 41 - /// enum ScanItem: Int { 42 - /// case map // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 43 - /// case array // (childCount: Int, mapCount: Int, offset: Int, byteCount: Int) 44 - /// 45 - /// case int // (offset: Int, byteCount: Int) 46 - /// case string 47 - /// case byteString 48 - /// case tagged 49 - /// case simple (byteCount: Int) 50 - /// } 51 - /// ``` 52 - struct Results { 53 - var map: [Int] = [] 54 - 55 - init(dataCount: Int) { 56 - self.map = [] 57 - map.reserveCapacity(dataCount * 4) 58 - } 59 - 60 - mutating func recordMapStart(currentByteIndex: Int) -> Int { 61 - map.append(MajorType.map.intValue) 62 - map.append(0) 63 - map.append(map.count + 3) 64 - map.append(currentByteIndex) 65 - map.append(currentByteIndex) 66 - return map.count - 5 67 - } 68 - 69 - mutating func recordArrayStart(currentByteIndex: Int) -> Int { 70 - map.append(MajorType.array.intValue) 71 - map.append(0) // child count 72 - map.append(map.count + 3) // map count 73 - map.append(currentByteIndex) // start byte 74 - map.append(currentByteIndex) // byte count 75 - return map.count - 5 76 - } 77 - 78 - mutating func recordEnd(childCount: Int, resultLocation: Int, currentByteIndex: Int) { 79 - map[resultLocation + 1] = childCount 80 - map[resultLocation + 2] = map.count - map[resultLocation + 2] 81 - map[resultLocation + 4] = currentByteIndex - map[resultLocation + 4] 82 - } 83 - 84 - mutating func recordType(_ type: UInt8, currentByteIndex: Int, length: Int) { 85 - map.append(Int(type)) 86 - map.append(currentByteIndex) 87 - map.append(length) 88 - } 89 - 90 - mutating func recordSimple(_ type: UInt8, currentByteIndex: Int) { 91 - map.append(Int(type)) 92 - map.append(currentByteIndex) 93 - } 94 - } 95 - 96 var reader: DataReader 97 var results: Results 98 let options: DecodingOptions 99 100 var isEmpty: Bool { 101 results.map.isEmpty ··· 107 self.options = options 108 } 109 110 - // MARK: - Load Values 111 - 112 - func load(at mapIndex: Int) -> DataRegion { 113 - assert(mapIndex < results.map.count) 114 - let byte = UInt8(results.map[mapIndex]) 115 - let argument = byte & 0b1_1111 116 - guard let type = MajorType(rawValue: byte) else { 117 - fatalError("Invalid type found in map: \(results.map[mapIndex]) at index: \(mapIndex)") 118 - } 119 - switch type { 120 - case .uint, .nint, .bytes, .string, .tagged: 121 - assert(mapIndex + 1 < results.map.count) 122 - let location = results.map[mapIndex + 1] 123 - let length = results.map[mapIndex + 2] 124 - let slice = reader.slice(location..<(location + length)) 125 - return DataRegion(type: type, argument: argument, childCount: nil, mapOffset: mapIndex, data: slice) 126 - case .simple: 127 - let length = simpleLength(UInt8(results.map[mapIndex])) 128 - let slice: Slice<UnsafeRawBufferPointer> 129 - if length == 0 { 130 - slice = reader.slice(0..<0) 131 - } else { 132 - let location = results.map[mapIndex + 1] + 1 // skip type & arg byte. 133 - slice = reader.slice(location..<(location + length)) 134 - } 135 - return DataRegion(type: type, argument: argument, childCount: nil, mapOffset: mapIndex, data: slice) 136 - case .array, .map: 137 - // Map determines the slice size 138 - let childCount = results.map[mapIndex + 1] 139 - let location = results.map[mapIndex + 3] 140 - let length = results.map[mapIndex + 4] 141 - let slice = reader.slice(location..<(location + length)) 142 - return DataRegion(type: type, argument: argument, childCount: childCount, mapOffset: mapIndex, data: slice) 143 - } 144 - } 145 - 146 - // MARK: - Map Navigation 147 - 148 - func firstChildIndex(_ mapIndex: Int) -> Int { 149 - let byte = UInt8(results.map[mapIndex]) 150 - guard let type = MajorType(rawValue: byte) else { 151 - fatalError("Invalid type found in map: \(results.map[mapIndex]) at index: \(mapIndex)") 152 - } 153 - switch type { 154 - case .uint, .nint, .bytes, .string, .tagged, .simple: 155 - fatalError("Can't find child index for non-container type.") 156 - case .array, .map: // type byte + 4 map values 157 - return mapIndex + 5 158 - } 159 - } 160 - 161 - func siblingIndex(_ mapIndex: Int) -> Int { 162 - let byte = UInt8(results.map[mapIndex]) 163 - guard let type = MajorType(rawValue: byte) else { 164 - fatalError("Invalid type found in map: \(results.map[mapIndex]) at index: \(mapIndex)") 165 - } 166 - switch type { 167 - case .uint, .nint, .bytes, .string, .tagged: 168 - return mapIndex + 3 169 - case .simple: 170 - return mapIndex + 2 171 - case .array, .map: // Map contains the map/array count 172 - return mapIndex + 5 + results.map[mapIndex + 2] 173 - } 174 - } 175 - 176 // MARK: - Scan 177 178 func scan() throws { ··· 192 } 193 } 194 195 switch type { 196 case .uint, .nint: 197 try scanInt(raw: raw) 198 case .bytes: 199 - try scanBytesOrString(.bytes) 200 case .string: 201 - try scanBytesOrString(.string) 202 case .array: 203 try scanArray() 204 case .map: ··· 206 case .simple: 207 try scanSimple(raw: raw) 208 case .tagged: 209 - throw ScanError.expectedMajorType(offset: 0) 210 } 211 } 212 ··· 225 private func scanSimple(raw: UInt8) throws { 226 let idx = reader.index 227 results.recordSimple(reader.pop(), currentByteIndex: idx) 228 - guard reader.canRead(simpleLength(raw)) else { 229 throw ScanError.unexpectedEndOfData 230 } 231 - reader.pop(simpleLength(raw)) 232 - } 233 - 234 - private func simpleLength(_ arg: UInt8) -> Int { 235 - switch arg & 0b11111 { 236 - case 25: 237 - 2 // Half-float 238 - case 26: 239 - 4 // Float 240 - case 27: 241 - 8 // Double 242 - default: 243 - 0 // Just this byte. 244 - } 245 } 246 247 // MARK: - Scan String/Bytes 248 249 - private func scanBytesOrString(_ type: MajorType) throws { 250 - let raw = reader._peek() // already checked previously 251 - 252 guard peekIsIndeterminate() else { 253 let size = try reader.readNextInt(as: Int.self) 254 let offset = reader.index ··· 345 // Pop the break byte 346 reader.pop() 347 results.recordEnd(childCount: count, resultLocation: mapIdx, currentByteIndex: reader.index) 348 } 349 }
··· 16 case typeInIndeterminateString(type: MajorType, offset: Int) 17 case rejectedIndeterminateLength(type: MajorType, offset: Int) 18 case cannotRepresentInt(max: UInt, found: UInt, offset: Int) 19 + case noTagInformation(tag: UInt, offset: Int) 20 + case invalidMajorTypeForTaggedItem(tag: UInt, expected: Set<MajorType>, found: MajorType, offset: Int) 21 } 22 23 /// # Why Scan? ··· 34 /// take either indeterminate or specific lengths and decode them. 35 @usableFromInline 36 final class CBORScanner { 37 var reader: DataReader 38 var results: Results 39 let options: DecodingOptions 40 + 41 + let taggedScanMap: [UInt: Set<MajorType>] = [ 42 + 0: [.string], // Date (string) 43 + 1: [.uint, .nint, .simple], // Date (epoch) 44 + 37: [.bytes], // UUID 45 + ] 46 47 var isEmpty: Bool { 48 results.map.isEmpty ··· 54 self.options = options 55 } 56 57 // MARK: - Scan 58 59 func scan() throws { ··· 73 } 74 } 75 76 + try scanType(type: type, raw: raw) 77 + } 78 + 79 + private func scanType(type: MajorType, raw: UInt8) throws { 80 switch type { 81 case .uint, .nint: 82 try scanInt(raw: raw) 83 case .bytes: 84 + try scanBytesOrString(.bytes, raw: raw) 85 case .string: 86 + try scanBytesOrString(.string, raw: raw) 87 case .array: 88 try scanArray() 89 case .map: ··· 91 case .simple: 92 try scanSimple(raw: raw) 93 case .tagged: 94 + try scanTagged(raw: raw) 95 } 96 } 97 ··· 110 private func scanSimple(raw: UInt8) throws { 111 let idx = reader.index 112 results.recordSimple(reader.pop(), currentByteIndex: idx) 113 + guard reader.canRead(raw.simpleLength()) else { 114 throw ScanError.unexpectedEndOfData 115 } 116 + reader.pop(raw.simpleLength()) 117 } 118 119 // MARK: - Scan String/Bytes 120 121 + private func scanBytesOrString(_ type: MajorType, raw: UInt8) throws { 122 guard peekIsIndeterminate() else { 123 let size = try reader.readNextInt(as: Int.self) 124 let offset = reader.index ··· 215 // Pop the break byte 216 reader.pop() 217 results.recordEnd(childCount: count, resultLocation: mapIdx, currentByteIndex: reader.index) 218 + } 219 + 220 + private func scanTagged(raw: UInt8) throws { 221 + guard let size = reader.peekArgument()?.byteCount() else { 222 + throw ScanError.invalidSize(byte: reader.peekArgument() ?? .max, offset: reader.index) 223 + } 224 + let offset = reader.index 225 + results.recordType(raw, currentByteIndex: offset, length: Int(size)) 226 + 227 + let tag = try reader.readNextInt(as: UInt.self) 228 + 229 + guard let validMajorTypes = taggedScanMap[tag] else { 230 + throw ScanError.noTagInformation(tag: tag, offset: reader.index) 231 + } 232 + 233 + guard let nextRaw = reader.peek(), let nextTag = MajorType(rawValue: nextRaw) else { 234 + throw ScanError.unexpectedEndOfData 235 + } 236 + 237 + guard validMajorTypes.contains(nextTag) else { 238 + throw ScanError.invalidMajorTypeForTaggedItem( 239 + tag: tag, 240 + expected: validMajorTypes, 241 + found: nextTag, 242 + offset: offset 243 + ) 244 + } 245 + 246 + try scanType(type: nextTag, raw: nextRaw) 247 } 248 }
+8 -25
Sources/CBOR/Encoder/CBOREncoder.swift
··· 23 /// Create a new CBOR encoder. 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 } ··· 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
··· 23 /// Create a new CBOR encoder. 24 /// - Parameters: 25 /// - forceStringKeys: See ``EncodingOptions/forceStringKeys``. 26 + /// - dateEncodingStrategy: See ``EncodingOptions/dateEncodingStrategy``. 27 /// - assumeUInt8IsByteString: See ``EncodingOptions/assumeUInt8IsByteString``. 28 + public init( 29 + forceStringKeys: Bool = false, 30 + dateEncodingStrategy: EncodingOptions.DateStrategy = .double, 31 + assumeUInt8IsByteString: Bool = true 32 + ) { 33 options = EncodingOptions( 34 forceStringKeys: forceStringKeys, 35 + dateEncodingStrategy: dateEncodingStrategy, 36 assumeUInt8IsByteString: assumeUInt8IsByteString 37 ) 38 } ··· 45 46 let encodingContext = EncodingContext(options: options) 47 let encoder = SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext) 48 + try encoder.encode(value) 49 50 let dataSize = tempStorage.value.size 51 var data = Data(count: dataSize) 52 data.withUnsafeMutableBytes { ptr in 53 var slice = ptr[...] 54 tempStorage.value.write(to: &slice) 55 assert(slice.isEmpty) 56 } 57 return data
+1 -1
Sources/CBOR/Encoder/Containers/KeyedCBOREncodingContainer.swift
··· 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 {
··· 24 } 25 26 func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable { 27 + try encoder(for: key).encode(value) 28 } 29 30 mutating func encodeNil(forKey key: Key) throws {
+6 -3
Sources/CBOR/Encoder/Containers/SingleValueCBOREncodingContainer.swift
··· 63 // special encoding cases. It's still lame. 64 65 if let date = value as? Date { 66 - if options.useStringDates { 67 parent.register(StringDateOptimizer(value: date)) 68 - } else { 69 - parent.register(EpochDateOptimizer(value: date)) 70 } 71 } else if let uuid = value as? UUID { 72 parent.register(UUIDOptimizer(value: uuid))
··· 63 // special encoding cases. It's still lame. 64 65 if let date = value as? Date { 66 + switch options.dateEncodingStrategy { 67 + case .string: 68 parent.register(StringDateOptimizer(value: date)) 69 + case .float: 70 + parent.register(EpochFloatDateOptimizer(value: date)) 71 + case .double: 72 + parent.register(EpochDoubleDateOptimizer(value: date)) 73 } 74 } else if let uuid = value as? UUID { 75 parent.register(UUIDOptimizer(value: uuid))
+2 -26
Sources/CBOR/Encoder/Containers/UnkeyedCBOREncodingContainer.swift
··· 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 { ··· 64 65 let parent: ParentStorage 66 var items: [EncodingOptimizer] = [] 67 - var data: [UInt8] = [] 68 69 init(parent: ParentStorage) { 70 self.parent = parent ··· 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
··· 22 } 23 24 func encode<T: Encodable>(_ value: T) throws { 25 + try SingleValueCBOREncodingContainer(parent: storage.forAppending(), context: nextContext()).encode(value) 26 } 27 28 mutating func encodeNil() throws { ··· 53 54 let parent: ParentStorage 55 var items: [EncodingOptimizer] = [] 56 57 init(parent: ParentStorage) { 58 self.parent = parent ··· 74 } 75 76 deinit { 77 + parent.register(UnkeyedOptimizer(value: items)) 78 } 79 } 80
+17 -5
Sources/CBOR/Encoder/EncodingOptions.swift
··· 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. ··· 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 }
··· 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 + /// Methods for encoding dates. 14 + public enum DateStrategy { 15 + /// Encodes dates as `ISO8601` date strings under tag `0`. 16 + case string 17 + /// Encodes dates as an epoch date using a Float value. Loses precision at the benefit of half the size 18 + /// of a double. 19 + case float 20 + /// Encodes dates as an epoch date using a Double value. 21 + /// Highest precision. 22 + case double 23 + } 24 25 + /// Determine how to encode dates. 26 + public let dateEncodingStrategy: DateStrategy 27 28 /// Codable can't tell us if we're encoding a Data or [UInt8] object. By default this library assumes that if it's 29 /// encoding an unkeyed container or UInt8 objects it's a byte string. Toggle this to false to disable this. ··· 33 /// Initialize new encoding options. 34 /// - Parameters: 35 /// - forceStringKeys: Force encoded maps to use string keys even when integer keys are available. 36 + /// - useStringDates: See ``dateEncodingStrategy`` and ``DateStrategy``. 37 /// - assumeUInt8IsByteString: See ``assumeUInt8IsByteString``. 38 + public init(forceStringKeys: Bool, dateEncodingStrategy: DateStrategy, assumeUInt8IsByteString: Bool) { 39 self.forceStringKeys = forceStringKeys 40 + self.dateEncodingStrategy = dateEncodingStrategy 41 self.assumeUInt8IsByteString = assumeUInt8IsByteString 42 } 43 }
+17 -1
Sources/CBOR/Encoder/Optimizers/DateOptimizer.swift
··· 27 } 28 } 29 30 - struct EpochDateOptimizer: EncodingOptimizer { 31 var optimizer: DoubleOptimizer 32 33 var type: MajorType { .tagged } ··· 42 optimizer.write(to: &data) 43 } 44 }
··· 27 } 28 } 29 30 + struct EpochDoubleDateOptimizer: EncodingOptimizer { 31 var optimizer: DoubleOptimizer 32 33 var type: MajorType { .tagged } ··· 42 optimizer.write(to: &data) 43 } 44 } 45 + 46 + struct EpochFloatDateOptimizer: EncodingOptimizer { 47 + var optimizer: FloatOptimizer 48 + 49 + var type: MajorType { .tagged } 50 + var argument: UInt8 { 1 } 51 + var contentSize: Int { optimizer.size } 52 + 53 + init(value: Date) { 54 + optimizer = FloatOptimizer(value: Float(value.timeIntervalSince1970)) 55 + } 56 + 57 + mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>) { 58 + optimizer.write(to: &data) 59 + } 60 + }
Sources/CBOR/Extensions/FixedWithInteger+write.swift Sources/CBOR/Extensions/FixedWidthIndeger/FixedWidthIndeger+write.swift
+10 -8
Sources/CBOR/Extensions/Float32+Float16.swift Sources/CBOR/Extensions/Float/Float+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 ··· 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 }
··· 1 // 2 + // Float+Float16.swift 3 + // CBOR 4 // 5 // Created by Khan Winter on 8/17/25. 6 // 7 8 + // Copied with modifications from: https://github.com/valpackett/SwiftCBOR 9 + 10 + extension Float { 11 // https://gist.github.com/martinkallman/5049614 12 // rewritten to Swift + applied fixes from comments + added NaN/Inf checks 13 // should be good enough, who cares about float16 14 @inlinable 15 + init?(halfPrecision x: UInt16) { 16 if (x & 0x7fff) > 0x7c00 { 17 + self = .nan 18 } 19 if x == 0x7c00 { 20 + self = .infinity 21 } 22 if x == 0xfc00 { 23 + self = -.infinity 24 } 25 var t1 = UInt32(x & 0x7fff) // Non-sign bits 26 var t2 = UInt32(x & 0x8000) // Sign bit ··· 30 t1 += 0x38000000 // Adjust bias 31 t1 = (t3 == 0 ? 0 : t1) // Denormals-as-zero 32 t1 |= t2 // Re-insert sign bit 33 + self = Float(bitPattern: t1) 34 } 35 }
+25
Sources/CBOR/Extensions/UInt8/UInt8+byteCount.swift
···
··· 1 + // 2 + // UInt8+byteCount.swift 3 + // CBOR 4 + // 5 + // Created by Khan Winter on 9/1/25. 6 + // 7 + 8 + internal extension UInt8 { 9 + func byteCount() -> UInt8? { 10 + switch self { 11 + case let value where value < Constants.maxArgSize: 12 + 0 13 + case 24: 14 + 1 15 + case 25: 16 + 2 17 + case 26: 18 + 4 19 + case 27: 20 + 8 21 + default: 22 + nil 23 + } 24 + } 25 + }
+22
Sources/CBOR/Extensions/UInt8/UInt8+simpleLength.swift
···
··· 1 + // 2 + // UInt8+simpleLength.swift 3 + // CBOR 4 + // 5 + // Created by Khan Winter on 9/1/25. 6 + // 7 + 8 + extension UInt8 { 9 + @inlinable 10 + func simpleLength() -> Int { 11 + switch self & 0b11111 { 12 + case 25: 13 + 2 // Half-float 14 + case 26: 15 + 4 // Float 16 + case 27: 17 + 8 // Double 18 + default: 19 + 0 // Just this byte. 20 + } 21 + } 22 + }
+6 -5
Sources/CBOR/MajorType.swift
··· 5 // Created by Khan Winter on 8/17/25. 6 // 7 8 - @usableFromInline 9 - enum MajorType: UInt8, Sendable, Hashable, Equatable { 10 case uint = 0 11 case nint = 1 12 case bytes = 2 ··· 16 case tagged = 6 17 case simple = 7 18 19 @inline(__always) 20 - @usableFromInline 21 - init?(rawValue: UInt8) { 22 // We only care about the top 3 bits 23 switch rawValue >> 5 { 24 case MajorType.uint.rawValue: self = .uint ··· 34 } 35 36 @inline(__always) 37 - @inlinable var bits: UInt8 { 38 rawValue << 5 39 } 40 41 var intValue: Int { 42 Int(bits) 43 }
··· 5 // Created by Khan Winter on 8/17/25. 6 // 7 8 + /// Represents a major type as described by the CBOR specification. 9 + @frozen public enum MajorType: UInt8, Sendable, Hashable, Equatable { 10 case uint = 0 11 case nint = 1 12 case bytes = 2 ··· 16 case tagged = 6 17 case simple = 7 18 19 + /// Create a major type using a raw integer (only uses the highest 3 bits). 20 @inline(__always) 21 + public init?(rawValue: UInt8) { 22 // We only care about the top 3 bits 23 switch rawValue >> 5 { 24 case MajorType.uint.rawValue: self = .uint ··· 34 } 35 36 @inline(__always) 37 + var bits: UInt8 { 38 rawValue << 5 39 } 40 41 + @inline(__always) 42 var intValue: Int { 43 Int(bits) 44 }
+9 -1
Tests/CBORTests/DecodableTests.swift
··· 218 let context = DecodingContext(scanner: scanner) 219 let container = SingleValueCBORDecodingContainer( 220 context: context, 221 - data: scanner.load(at: 0) 222 ) 223 224 var unkeyedContainer = try container.unkeyedContainer() ··· 265 func emptyData() throws { 266 let data = Data() 267 #expect(throws: DecodingError.self) { try CBORDecoder().decode(Data.self, from: data) } 268 } 269 }
··· 218 let context = DecodingContext(scanner: scanner) 219 let container = SingleValueCBORDecodingContainer( 220 context: context, 221 + data: scanner.results.load(at: 0, reader: scanner.reader) 222 ) 223 224 var unkeyedContainer = try container.unkeyedContainer() ··· 265 func emptyData() throws { 266 let data = Data() 267 #expect(throws: DecodingError.self) { try CBORDecoder().decode(Data.self, from: data) } 268 + } 269 + 270 + @Test 271 + func date() throws { 272 + let data = "C11A5C295C00".asHexData() 273 + let expected = Date(timeIntervalSince1970: 1546214400.0) 274 + let value = try CBORDecoder().decode(Date.self, from: data) 275 + #expect(value == expected) 276 } 277 }
+24
Tests/CBORTests/RoundTripTests.swift
··· 113 #expect(first.name == Person.mock.name) 114 } 115 } 116 }
··· 113 #expect(first.name == Person.mock.name) 114 } 115 } 116 + 117 + @Test 118 + func dateEpoch() throws { 119 + let value = Date() 120 + let encoded = try CBOREncoder().encode(value) 121 + let decoded = try CBORDecoder().decode(Date.self, from: encoded) 122 + #expect(decoded.timeIntervalSince1970.rounded(.down) == value.timeIntervalSince1970.rounded(.down)) 123 + } 124 + 125 + @Test 126 + func dateString() throws { 127 + let value = Date() 128 + let encoded = try CBOREncoder(dateEncodingStrategy: .string).encode(value) 129 + let decoded = try CBORDecoder().decode(Date.self, from: encoded) 130 + #expect(decoded.timeIntervalSince1970.rounded(.down) == value.timeIntervalSince1970.rounded(.down)) 131 + } 132 + 133 + @Test 134 + func uuid() throws { 135 + let value = UUID() 136 + let encoded = try CBOREncoder().encode(value) 137 + let decoded = try CBORDecoder().decode(UUID.self, from: encoded) 138 + #expect(decoded == value) 139 + } 140 }
+3 -3
Tests/CBORTests/ScannerTests.swift
··· 22 let map = scanner.results.map 23 #expect(map == [Int(MajorType.uint.bits | argument), 1, byteCount]) 24 25 - #expect(scanner.load(at: 0).type == .uint) 26 - #expect(scanner.load(at: 0).argument == argument) 27 - #expect(scanner.load(at: 0).count == byteCount) 28 } 29 } 30
··· 22 let map = scanner.results.map 23 #expect(map == [Int(MajorType.uint.bits | argument), 1, byteCount]) 24 25 + #expect(scanner.results.load(at: 0, reader: scanner.reader).type == .uint) 26 + #expect(scanner.results.load(at: 0, reader: scanner.reader).argument == argument) 27 + #expect(scanner.results.load(at: 0, reader: scanner.reader).count == byteCount) 28 } 29 } 30