Skip to content

Commit dc4c2a6

Browse files
authored
Merge pull request #3 from Joannis/jo/optimisations
Optimise CBOR parsing + serialization through raw APIs and Codable
2 parents e71e466 + 8120f66 commit dc4c2a6

8 files changed

Lines changed: 132 additions & 175 deletions

File tree

Sources/CBOR/CBOR.swift

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import ucrt
1111
// MARK: - CBOR Type
1212

1313
/// A CBOR value
14-
public indirect enum CBOR: Equatable, Sendable {
14+
public enum CBOR: Equatable, Sendable {
1515
/// A positive unsigned integer
1616
case unsignedInt(UInt64)
1717
/// A negative integer
@@ -25,7 +25,7 @@ public indirect enum CBOR: Equatable, Sendable {
2525
/// A map of CBOR key-value pairs
2626
case map([CBORMapPair])
2727
/// A tagged CBOR value
28-
case tagged(UInt64, CBOR)
28+
indirect case tagged(UInt64, CBOR)
2929
/// A simple value
3030
case simple(UInt8)
3131
/// A boolean value
@@ -38,9 +38,13 @@ public indirect enum CBOR: Equatable, Sendable {
3838
case float(Double)
3939

4040
/// Encodes the CBOR value to bytes
41+
public func encode(into output: inout [UInt8]) {
42+
_encode(self, into: &output)
43+
}
44+
4145
public func encode() -> [UInt8] {
4246
var output: [UInt8] = []
43-
_encode(self, into: &output)
47+
encode(into: &output)
4448
return output
4549
}
4650

@@ -80,6 +84,7 @@ public struct CBORMapPair: Equatable, Sendable {
8084
/// - Parameters:
8185
/// - value: The CBOR value to encode
8286
/// - output: The output buffer to write the encoded bytes to
87+
@inline(__always)
8388
private func _encode(_ value: CBOR, into output: inout [UInt8]) {
8489
switch value {
8590
case .unsignedInt(let u):
@@ -207,16 +212,16 @@ private func _decode(reader: inout CBORReader) throws(CBORError) -> CBOR {
207212

208213
case 2: // byte string
209214
let length = try readUIntValue(additional: additional, reader: &reader)
210-
guard length <= UInt64(Int.max) else {
211-
throw CBORError.lengthTooLarge(length)
215+
guard length <= reader.maximumStringLength else {
216+
throw CBORError.lengthTooLarge(length, maximum: reader.maximumStringLength)
212217
}
213218

214-
return .byteString(try reader.readBytes(Int(length)))
219+
return .byteString(Array(try reader.readBytes(Int(length))))
215220

216221
case 3: // text string
217222
let length = try readUIntValue(additional: additional, reader: &reader)
218-
guard length <= UInt64(Int.max) else {
219-
throw CBORError.lengthTooLarge(length)
223+
guard length <= reader.maximumStringLength else {
224+
throw CBORError.lengthTooLarge(length, maximum: reader.maximumStringLength)
220225
}
221226

222227
let bytes = try reader.readBytes(Int(length))
@@ -229,25 +234,25 @@ private func _decode(reader: inout CBORReader) throws(CBORError) -> CBOR {
229234

230235
case 4: // array
231236
let count = try readUIntValue(additional: additional, reader: &reader)
232-
guard count <= UInt64(Int.max) else {
233-
throw CBORError.lengthTooLarge(count)
237+
guard count <= reader.maximumElementCount else {
238+
throw CBORError.lengthTooLarge(count, maximum: reader.maximumElementCount)
234239
}
235240

236241
var items: [CBOR] = []
237-
for _ in 0..<Int(count) {
242+
for _ in 0..<count {
238243
items.append(try _decode(reader: &reader))
239244
}
240245

241246
return .array(items)
242247

243248
case 5: // map
244249
let count = try readUIntValue(additional: additional, reader: &reader)
245-
guard count <= UInt64(Int.max) else {
246-
throw CBORError.lengthTooLarge(count)
250+
guard count <= reader.maximumElementCount else {
251+
throw CBORError.lengthTooLarge(count, maximum: reader.maximumElementCount)
247252
}
248253

249254
var pairs: [CBORMapPair] = []
250-
for _ in 0..<Int(count) {
255+
for _ in 0..<count {
251256
let key = try _decode(reader: &reader)
252257
let value = try _decode(reader: &reader)
253258
pairs.append(CBORMapPair(key: key, value: value))
@@ -270,8 +275,7 @@ private func _decode(reader: inout CBORReader) throws(CBORError) -> CBOR {
270275
let simple = try reader.readByte()
271276
return .simple(simple)
272277
case 25: // IEEE 754 Half-Precision Float (16 bits)
273-
let bytes = try reader.readBytes(2)
274-
let bits = UInt16(bytes[0]) << 8 | UInt16(bytes[1])
278+
let bits = try reader.readBigEndianInteger(UInt16.self)
275279
// Convert half-precision to double
276280
let sign = (bits & 0x8000) != 0
277281
let exponent = Int((bits & 0x7C00) >> 10)
@@ -289,15 +293,12 @@ private func _decode(reader: inout CBORReader) throws(CBORError) -> CBOR {
289293
return .float(sign ? -value : value)
290294

291295
case 26: // IEEE 754 Single-Precision Float (32 bits)
292-
let bytes = try reader.readBytes(4)
293-
let bits = UInt32(bytes[0]) << 24 | UInt32(bytes[1]) << 16 | UInt32(bytes[2]) << 8 | UInt32(bytes[3])
296+
let bits = try reader.readBigEndianInteger(UInt32.self)
294297
let float = Float(bitPattern: bits)
295298
return .float(Double(float))
296299

297300
case 27: // IEEE 754 Double-Precision Float (64 bits)
298-
let bytes = try reader.readBytes(8)
299-
let bits = UInt64(bytes[0]) << 56 | UInt64(bytes[1]) << 48 | UInt64(bytes[2]) << 40 | UInt64(bytes[3]) << 32 |
300-
UInt64(bytes[4]) << 24 | UInt64(bytes[5]) << 16 | UInt64(bytes[6]) << 8 | UInt64(bytes[7])
301+
let bits = try reader.readBigEndianInteger(UInt64.self)
301302
let double = Double(bitPattern: bits)
302303
return .float(double)
303304

@@ -315,26 +316,20 @@ private func _decode(reader: inout CBORReader) throws(CBORError) -> CBOR {
315316

316317
/// Reads an unsigned integer value based on the additional information.
317318
private func readUIntValue(additional: UInt8, reader: inout CBORReader) throws(CBORError) -> UInt64 {
318-
// Check for indefinite length first
319-
if additional == 31 {
320-
throw CBORError.indefiniteLengthNotSupported
321-
}
322-
323-
if additional < 24 {
319+
switch additional {
320+
case 0...23:
324321
return UInt64(additional)
325-
} else if additional == 24 {
322+
case 24:
326323
return UInt64(try reader.readByte())
327-
} else if additional == 25 {
328-
let bytes = try reader.readBytes(2)
329-
return UInt64(bytes[0]) << 8 | UInt64(bytes[1])
330-
} else if additional == 26 {
331-
let bytes = try reader.readBytes(4)
332-
return UInt64(bytes[0]) << 24 | UInt64(bytes[1]) << 16 | UInt64(bytes[2]) << 8 | UInt64(bytes[3])
333-
} else if additional == 27 {
334-
let bytes = try reader.readBytes(8)
335-
return UInt64(bytes[0]) << 56 | UInt64(bytes[1]) << 48 | UInt64(bytes[2]) << 40 | UInt64(bytes[3]) << 32 |
336-
UInt64(bytes[4]) << 24 | UInt64(bytes[5]) << 16 | UInt64(bytes[6]) << 8 | UInt64(bytes[7])
337-
} else {
324+
case 25:
325+
return try UInt64(reader.readBigEndianInteger(UInt16.self))
326+
case 26:
327+
return try UInt64(reader.readBigEndianInteger(UInt32.self))
328+
case 27:
329+
return try reader.readBigEndianInteger(UInt64.self)
330+
case 31:
331+
throw CBORError.indefiniteLengthNotSupported
332+
default:
338333
throw CBORError.invalidInitialByte(additional)
339334
}
340335
}

Sources/CBOR/CBORError.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public enum CBORError: Error, Equatable, Sendable {
3838
/// This error occurs when a CBOR string, array, or map has a length that is too large
3939
/// to be processed by the current implementation, typically due to memory constraints.
4040
/// - Parameter length: The length value that exceeded the implementation's limits
41-
case lengthTooLarge(UInt64)
41+
case lengthTooLarge(UInt64, maximum: UInt64)
4242

4343
/// Indefinite length encoding is not supported for this type.
4444
///
@@ -67,8 +67,8 @@ extension CBORError: CustomStringConvertible {
6767
return "Unexpected end of data: reached the end of input before completing the CBOR value"
6868
case .invalidInitialByte(let byte):
6969
return "Invalid initial byte: 0x\(String(byte, radix: 16, uppercase: true)) is not a valid CBOR initial byte"
70-
case .lengthTooLarge(let length):
71-
return "Length too large: the specified length \(length) exceeds the implementation's limits"
70+
case .lengthTooLarge(let length, let maximum):
71+
return "Length too large: the specified length \(length) exceeds the implementation's limits of \(maximum)"
7272
case .indefiniteLengthNotSupported:
7373
return "Indefinite length encoding not supported: this implementation does not support indefinite length encoding for this type"
7474
case .extraDataFound:

Sources/CBOR/CBORReader.swift

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
21
/// A helper struct for reading CBOR data byte by byte
3-
struct CBORReader {
2+
struct CBORReader: ~Copyable {
43
private let data: [UInt8]
54
private(set) var index: Int
5+
internal var maximumStringLength: UInt64 = 65_536
6+
internal var maximumElementCount: UInt64 = 16_384
67

78
init(data: [UInt8]) {
89
self.data = data
@@ -20,14 +21,25 @@ struct CBORReader {
2021
}
2122

2223
/// Read a specified number of bytes from the input
23-
mutating func readBytes(_ count: Int) throws(CBORError) -> [UInt8] {
24+
mutating func readBytes(_ count: Int) throws(CBORError) -> ArraySlice<UInt8> {
2425
guard index + count <= data.count else {
2526
throw CBORError.prematureEnd
2627
}
27-
let result = Array(data[index..<index + count])
28+
let result = data[index..<index + count]
2829
index += count
2930
return result
3031
}
32+
33+
mutating func readBigEndianInteger<F: FixedWidthInteger>(_ type: F.Type) throws(CBORError) -> F {
34+
let bytes = try readBytes(MemoryLayout<F>.size)
35+
var value: F = 0
36+
return bytes.withUnsafeBytes { buffer in
37+
withUnsafeMutableBytes(of: &value) { valuePtr in
38+
valuePtr.copyMemory(from: buffer)
39+
}
40+
return value.bigEndian
41+
}
42+
}
3143

3244
/// Check if there are more bytes to read
3345
var hasMoreBytes: Bool {
@@ -43,12 +55,4 @@ struct CBORReader {
4355
var totalBytes: Int {
4456
return data.count
4557
}
46-
47-
/// Skip a specified number of bytes
48-
mutating func skip(_ count: Int) throws(CBORError) {
49-
guard index + count <= data.count else {
50-
throw CBORError.prematureEnd
51-
}
52-
index += count
53-
}
5458
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import Foundation
1010
extension CBOR: Encodable {
1111
public func encode(to encoder: Encoder) throws {
1212
var container = encoder.singleValueContainer()
13+
14+
if let container = container as? CBOREncoderSingleValueContainer {
15+
container.encoder.push(self)
16+
return
17+
}
1318

1419
switch self {
1520
case .unsignedInt(let value):
@@ -63,6 +68,11 @@ extension CBOR: Encodable {
6368
extension CBOR: Decodable {
6469
public init(from decoder: Decoder) throws {
6570
let container = try decoder.singleValueContainer()
71+
72+
if let container = container as? CBORSingleValueDecodingContainer {
73+
self = container.cbor
74+
return
75+
}
6676

6777
if container.decodeNil() {
6878
self = .null
Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Foundation
88
// MARK: - CBOR Decoder
99

1010
/// A decoder that converts CBOR data to Swift values
11-
public class CBORDecoder: Decoder {
11+
public final class CBORDecoder: Decoder {
1212
private var cbor: CBOR
1313
public var codingPath: [CodingKey]
1414
public var userInfo: [CodingUserInfoKey: Any] = [:]
@@ -18,18 +18,16 @@ public class CBORDecoder: Decoder {
1818
self.codingPath = []
1919
}
2020

21-
public func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable {
21+
public func decode<T>(_ type: T.Type, from data: [UInt8]) throws -> T where T: Decodable {
2222
// First decode the CBOR value from the data
23-
let cbor = try CBOR.decode([UInt8](data))
23+
let cbor = try CBOR.decode(data)
2424

2525
// Special case for arrays
26-
if type == [Data].self {
27-
if case .byteString = cbor {
28-
// If we're trying to decode a byteString as an array of Data,
29-
// wrap it in an array with a single element
30-
let dataArray = [Data([UInt8](data))]
31-
return dataArray as! T
32-
}
26+
if type == [Data].self, case .byteString = cbor {
27+
// If we're trying to decode a byteString as an array of Data,
28+
// wrap it in an array with a single element
29+
let dataArray = [Data(data)]
30+
return dataArray as! T
3331
}
3432

3533
// Then use the regular decoder to decode the value
@@ -1538,9 +1536,9 @@ private struct CBORUnkeyedDecodingContainer: UnkeyedDecodingContainer {
15381536

15391537
// MARK: - CBOR Single Value Decoding Container
15401538

1541-
private struct CBORSingleValueDecodingContainer: SingleValueDecodingContainer {
1539+
internal struct CBORSingleValueDecodingContainer: SingleValueDecodingContainer {
15421540
var codingPath: [CodingKey]
1543-
private let cbor: CBOR
1541+
internal let cbor: CBOR
15441542

15451543
init(cbor: CBOR, codingPath: [CodingKey]) {
15461544
self.cbor = cbor

0 commit comments

Comments
 (0)