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 dag-cbor 205 lines 9.3 kB view raw view rendered
1<p align="center"> 2 <img src="https://github.com/thecoolwinter/CBOR/blob/main/.github/Icon-256.png?raw=true" height="128"> 3 <h1 align="center">CBOR</h1> 4</p> 5 6<p align="center"> 7 <a aria-label="CI Status" href="" target="_blank"> 8 <img alt="" src="https://img.shields.io/github/actions/workflow/status/thecoolwinter/CBOR/ci.yml"> 9 </a> 10 <a aria-label="Swift Version Compatibility" href="https://swiftpackageindex.com/thecoolwinter/CBOR" target="_blank"> 11 <img alt="" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fthecoolwinter%2FCBOR%2Fbadge%3Ftype%3Dswift-versions"> 12 </a> 13 <a aria-label="Platform Compatibility" href="https://swiftpackageindex.com/thecoolwinter/CBOR" target="_blank"> 14 <img alt="" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fthecoolwinter%2FCBOR%2Fbadge%3Ftype%3Dplatforms"> 15 </a> 16</p> 17 18[CBOR](https://cbor.io/) is a flexible data format that emphasizes extremely small code size, small message size, and extendability. This library provides a Swift API for encoding and decoding Swift types using the CBOR serialization format. 19 20The motivation for this library over existing implementations is twofold: performance, and reliability. Existing community implementations do not make correct use of Swift features like `Optional` and memory safety. This library aims to be safe while still outperforming other implementations. To that end, this library has been extensively [fuzz tested](./Fuzz.md) to ensure your application never crashes due to malicious input. At the same time, this library boasts up to a *74% faster* encoding and up to *89% faster* decoding than existing implementations. See [benchmarks](#benchmarks) for more details. 21 22## Features 23 24- `Codable` API for familiar Swift project integration. 25- Customizable encoding options, such as date and key formatting. 26- Customizable decoding, with the ability to reject non-deterministic CBOR data. 27- Encoder creates deterministic CBOR data by default. 28 - Dictionaries are automatically sorted using a min heap for speed. 29 - Duplicate map keys are removed. 30 - Never creates non-deterministic collections (Strings, Byte Strings, Arrays, or Maps). 31- A scan pass allows for decoding only the keys you need from an encoded object. 32- Supports decoding half precision floats (Float16) as a regular Float. 33- Runs on Linux, Android, and Windows using the swift-foundation project when available. 34- Fuzz tested for reliability against crashes. 35- Supports tagged items (will expand and add ability to inject your own tags in the future): 36 - Dates 37 - UUIDs 38 - [CIDs](https://github.com/multiformats/cid) (tag 42) 39- Flexible date parsing (tags `0` or `1` with support for any numeric value representation). 40- Decoding multiple top-level objects using `decodeMultiple(_:from:)`. 41- *NEW* IPLD compatible DAG-CBOR encoder for content addressable data. 42- *NEW* Flexible date decoding for untagged date items encoded as strings, floating point values, or integers. 43 44> Note: This is not a valid CBOR/CDE encoder, it merely always outputs countable collections. CBOR/CDE should be implemented in the future as it's quite similar. 45 46## Usage 47 48### Standard CBOR 49 50This library utilizes Swift's `Codable` API for all (de)serialization operations. It intentionally doesn't support streaming CBOR blobs. After [installing](#installation) the package using Swift Package Manager, use the `CBOREncoder` and `CBORDecoder` types to encode to and decode from CBOR blobs, respectively. 51 52```swift 53// Encode any `Encodable` value. 54let encoder = CBOREncoder() 55try encoder.encode(0) // Result: 0x00 56try encoder.encode([404: "Not Found"]) // Result: 0xA1190194694E6F7420466F756E64 57 58// Decode any `Decodable` type. 59let decoder = CBORDecoder() 60let intValue = try decoder.decode(Int.self, from: Data([0])) // Result: 0 61// Result: ["AB": 1, "A": 2] 62let mapValue = try decoder.decode([String: Int].self, from: Data([162, 98, 65, 66, 1, 97, 65, 2])) 63``` 64 65The encoder and decoders can be customized via the en/decoder initializers or via a `En/DecodingOptions` struct. 66 67```swift 68// Customize encoder behavior. 69let encoder = CBOREncoder( 70 forceStringKeys: Bool = false, 71 dateEncodingStrategy: EncodingOptions.DateStrategy = .double 72) 73// or via the options struct 74let options = EncodingOptions(/* ... */) 75let encoder = CBOREncoder(options: options) 76 77// Customize the decoder behavior, such as enforcing deterministic CBOR. 78let decoder = CBORDecoder(rejectIndeterminateLengths: Bool = false) 79// or via the options struct 80let options = DecodingOptions(/* ... */) 81let decoder = CBORDecoder(options: options) 82``` 83 84### DAG-CBOR 85 86This library also offers the ability to encode and decode DAG-CBOR data. DAG-CBOR is a superset of the CBOR spec, though very similar. This library provides a `DAGCBOREncoder` type that is automatically configured to produce compatible data. The flags it sets are also available through the `EncodingOptions` struct, but using the specialized type will ensure safety. 87 88```swift 89// !! SEE NOTE !! 90let dagEncoder = DAGCBOREncoder(dateEncodingStrategy: .double) 91``` 92 93To use, conform your internal CID type to ``CIDType``. Do not conform standard types like `String` or `Data` to ``CIDType``, or the encoder will attempt to encode all of those data as tagged items. 94```swift 95struct CID: CIDType { 96 let data: Data 97 98 init(data: Data) { 99 self.data = data 100 } 101 102 init(from decoder: any Decoder) throws { 103 let container = try decoder.singleValueContainer() 104 var data = try container.decode(Data.self) 105 data.removeFirst() 106 self.data = data 107 } 108 109 func encode(to encoder: any Encoder) throws { 110 var container = encoder.singleValueContainer() 111 try container.encode(Data([0x0]) + data) 112 } 113} 114``` 115> [!WARNING] 116> 117> You **need** to prefix your data with the `NULL` byte when encoding. This library will not handle that for you. It is invalid DAG-CBOR encoding to not include the prefixed byte. 118 119Now, any time the encoder finds a `CID` type it will encode it using the correct tag. 120```swift 121let cid = CID(bytes: [0,1,2,3,4,5,6,7,8]) 122let data = try DAGCBOREncoder().encode(cid) 123 124print(data.hexString()) 125// Output: 126// D8 2A # tag(42) 127// 4A # bytes(10) 128// 00000102030405060708 # "\u0000\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b" 129``` 130> [!NOTE] 131> DAG-CBOR does not allow tagged items (besides the CID item), and thus encoding dates must be done by encoding their 'raw' value directly. This is an application specific behavior, so ensure the encoder is using the correct date encoding behavior for compatibility. By default, the encoder will encode dates as an epoch `Double` timestamp. 132 133## Documentation 134 135[Documentation](https://swiftpackageindex.com/thecoolwinter/CBOR/1.1.0/documentation/cbor) is hosted on the Swift Package Index. 136 137## Installation 138 139You can use the Swift Package Manager to download and import the library into your project: 140 141```swift 142dependencies: [ 143 .package(url: "https://github.com/thecoolwinter/CBOR.git", from: "1.0.0") 144] 145``` 146 147Then under `targets`: 148 149```swift 150targets: [ 151 .target( 152 // ... 153 dependencies: [ 154 .product(name: "CBOR", package: "CBOR") 155 ] 156 ) 157] 158``` 159 160## Benchmarks 161 162These benchmarks range from simple to complex encoding and decoding operations compared to the [SwiftCBOR](https://github.com/valpackett/SwiftCBOR) library. [Benchmarks source](./Benchmarks). Benchmarks are run on 10,000 samples and the p50 values are compared here. 163 164### Decoding (cpu time) 165 166| Benchmark | SwiftCBOR (ns, p50) | CBOR (ns, p50) | % Improvement | 167|-----------|----------------|-----------|------------| 168| Array | 23 | 7 | **70%** | 169| Complex Object | 703 μs | 75 μs | **89%** | 170| Date | 5,251 | 1,083 | **79%** | 171| Dictionary | 17 | 5 | **71%** | 172| Double | 5,295 | 1,001 | **81%** | 173| Float | 5,295 | 1,000 | **81%** | 174| Indeterminate String | 6,251 | 1,417 | **77%** | 175| Int | 5,211 | 1,125 | **78%** | 176| Int Small | 5,211 | 1,083 | **79%** | 177| Simple Object | 36 | 8 | **78%** | 178| String | 5,459 | 1,292 | **76%** | 179| String Small | 5,291 | 1,126 | **79%** | 180 181### Encoding (cpu time) 182 183| Benchmark | SwiftCBOR (ns, p50) | CBOR (ns, p50) | % Improvement | 184|-----------|----------------|-----------|------------| 185| Array | 669 μs | 471 μs | **30%** | 186| Array Small | 7,043 | 2,917 | **59%** | 187| Bool | 3,169 | 1,125 | **64%** | 188| Complex Codable Object | 124 μs | 92 μs | **26%** | 189| Data | 5,335 | 1,250 | **77%** | 190| Data Small | 3,959 | 1000 | **75%** | 191| Dictionary | 11 | 5 | **55%** | 192| Dictionary Small | 7,459 | 2,959 | **60%** | 193| Int | 4,001 | 1,292 | **68%** | 194| Int Small | 3,833 | 1,208 | **68%** | 195| Simple Codable Object | 18 | 9 | **50%** | 196| String | 5,583 | 1,291 | **77%** | 197| String Small | 4,041 | 1,125 | **72%** | 198 199## Contributing 200 201By participating in this project you agree to follow the [Contributor Code of Conduct](https://contributor-covenant.org/version/1/4/). Pull requests and issues are welcome! 202 203## Sponsor 204 205This project has been developed in my spare time. To keep me motivated to continue to maintain it into the future, consider [supporting my work](https://github.com/sponsors/thecoolwinter)!