···11+#!/usr/bin/env python3
22+"""
33+Parse Swift Benchmark markdown (produced by `swift package benchmark ... --format markdown`) and produce compact summary
44+markdown tables for Decoding and Encoding using the Time (total CPU) p50 values.
55+66+Usage:
77+ - Read from stdin:
88+ swift package benchmark baseline compare swiftcbor --format markdown --no-progress | python3 bench_compare.py
99+ - Or read from file:
1010+ python3 bench_compare.py benchmark.md
1111+1212+The script prints two markdown tables (Decoding and Encoding) to stdout.
1313+"""
1414+1515+import sys
1616+import re
1717+from pathlib import Path
1818+1919+2020+def parse_markdown(text):
2121+ lines = text.splitlines()
2222+ results = {"Decoding": {}, "Encoding": {}}
2323+ mode = None
2424+ i = 0
2525+ while i < len(lines):
2626+ line = lines[i]
2727+ if line.strip().startswith('## Decoding'):
2828+ mode = 'Decoding'
2929+ i += 1
3030+ continue
3131+ if line.strip().startswith('## Encoding'):
3232+ mode = 'Encoding'
3333+ i += 1
3434+ continue
3535+3636+ m = re.match(r"###\s+(.+?)\s+metrics", line)
3737+ if m and mode:
3838+ bench = m.group(1).strip()
3939+ # look ahead for Time (total CPU) table
4040+ j = i + 1
4141+ while j < len(lines) and not lines[j].strip().startswith('###') and not lines[j].strip().startswith('## '):
4242+ lh = lines[j]
4343+ # find the section header line containing "Time (total CPU)"
4444+ m2 = re.search(r"Time\s*\(total CPU\)\s*(?:\(([^)]+)\))?", lh)
4545+ if m2:
4646+ unit = m2.group(1) if m2.group(1) else ''
4747+ # find the header row that contains p0/p25/p50 etc.
4848+ k = j + 1
4949+ while k < len(lines) and lines[k].strip() == '':
5050+ k += 1
5151+ header_line_index = None
5252+ p50_idx = None
5353+ for t in range(k, min(k + 60, len(lines))):
5454+ if 'p50' in lines[t]:
5555+ cols = [c.strip() for c in lines[t].split('|')][1:-1]
5656+ # locate p50 column
5757+ try:
5858+ p50_idx = next(idx for idx, c in enumerate(cols) if c.startswith('p50'))
5959+ except StopIteration:
6060+ p50_idx = None
6161+ header_line_index = t
6262+ break
6363+ swift_val = None
6464+ curr_val = None
6565+ if header_line_index is not None and p50_idx is not None:
6666+ # parse following rows to find swiftcbor and Current_run
6767+ for t in range(header_line_index + 1, header_line_index + 60):
6868+ if t >= len(lines):
6969+ break
7070+ row = lines[t]
7171+ if not row.strip().startswith('|'):
7272+ continue
7373+ cols = [c.strip() for c in row.split('|')][1:-1]
7474+ if not cols:
7575+ continue
7676+ name = cols[0]
7777+ # defensive check
7878+ if p50_idx < len(cols):
7979+ if 'swiftcbor' in name:
8080+ swift_val = cols[p50_idx]
8181+ if 'Current_run' in name:
8282+ curr_val = cols[p50_idx]
8383+ if swift_val and curr_val:
8484+ break
8585+ results[mode][bench] = (swift_val, curr_val, unit)
8686+ break
8787+ j += 1
8888+ i += 1
8989+ return results
9090+9191+9292+def clean_num(s):
9393+ if s is None:
9494+ return None
9595+ s = s.strip().replace(',', '')
9696+ # find first numeric token
9797+ m = re.search(r"([0-9]+(?:\.[0-9]+)?)", s)
9898+ if not m:
9999+ return None
100100+ try:
101101+ return float(m.group(1))
102102+ except:
103103+ return None
104104+105105+106106+def fmt(n):
107107+ if n is None:
108108+ return ''
109109+ if n >= 1000:
110110+ return f"{int(round(n)):,}"
111111+ if n == int(n):
112112+ return str(int(n))
113113+ return f"{n:.0f}"
114114+115115+116116+def render_table_section(title, rows, preferred_order=None):
117117+ print(f"### {title} (cpu time)\n")
118118+ print("| Benchmark | SwiftCBOR (p50) | CBOR (p50) | % Improvement |")
119119+ print("|---|---:|---:|---:|")
120120+ keys = []
121121+ if preferred_order:
122122+ for k in preferred_order:
123123+ if k in rows:
124124+ keys.append(k)
125125+ # append remaining in alphabetical order
126126+ for k in sorted(rows.keys()):
127127+ if k not in keys:
128128+ keys.append(k)
129129+ for b in keys:
130130+ s_p, c_p, unit = rows.get(b, (None, None, ''))
131131+ sval = clean_num(s_p)
132132+ cval = clean_num(c_p)
133133+ s_str = (fmt(sval) + (' ' + unit if unit else '')) if sval is not None else (s_p or '')
134134+ c_str = (fmt(cval) + (' ' + unit if unit else '')) if cval is not None else (c_p or '')
135135+ perc = ''
136136+ if sval is not None and cval is not None and sval != 0:
137137+ pct = round((sval - cval) / sval * 100)
138138+ perc = f"**{pct}%**"
139139+ print(f"| {b} | {s_str} | {c_str} | {perc} |")
140140+ print("\n")
141141+142142+143143+def main(argv):
144144+ parser = __import__('argparse').ArgumentParser(description='Parse Swift benchmark markdown and print compact p50 tables')
145145+ parser.add_argument('file', nargs='?', default='-', help='Path to markdown file, or - for stdin (default)')
146146+ args = parser.parse_args(argv)
147147+ if args.file == '-':
148148+ text = sys.stdin.read()
149149+ else:
150150+ p = Path(args.file)
151151+ text = p.read_text()
152152+153153+ results = parse_markdown(text)
154154+155155+ # preferred orders to match your example (best-effort)
156156+ dec_order = ["Array","Complex Object","Date","Dictionary","Double","Float","Indeterminate String","Int","Int Small","Simple Object","String","String Small"]
157157+ enc_order = ["Array","Array Small","Bool","Complex Codable Object","Data","Data Small","Dictionary","Dictionary Small","Int","Int Small","Simple Codable Object","String","String Small"]
158158+159159+ render_table_section('Decoding', results.get('Decoding', {}), preferred_order=dec_order)
160160+ render_table_section('Encoding', results.get('Encoding', {}), preferred_order=enc_order)
161161+162162+163163+if __name__ == '__main__':
164164+ main(sys.argv[1:])
+13
Benchmarks/bench_compare.sh
···11+#!/usr/bin/env bash
22+# Run the Swift benchmark compare command and pipe its markdown output into the Python parser.
33+# Usage: run this from the repository root.
44+55+set -euo pipefail
66+77+# If you want to pass additional args to the swift command, set SWIFT_ARGS environment variable.
88+SWIFT_ARGS=${SWIFT_ARGS:-"baseline compare swiftcbor --format markdown --no-progress"}
99+PY_SCRIPT="$(dirname "$0")/bench_compare.py"
1010+1111+swift -version
1212+# Run the swift command and pipe
1313+swift package benchmark $SWIFT_ARGS | python3 "$PY_SCRIPT"
···11-# I'd love for this to work but it's not quite right right now. The table in the README is copied manually.
22-33-import re
44-55-def to_ns(val, unit):
66- v = int(val)
77- return v if unit == "ns" else v * 1000
88-99-files = [
1010- ("comparison-decoding", "Decoding"),
1111- ("comparison-encoding", "Encoding")
1212-]
1313-1414-for file in files:
1515- with open(file[0] + ".md", "r") as f:
1616- markdown = f.read()
1717-1818- # Split into sections by headings
1919- sections = re.split(r"(?=^### )", markdown, flags=re.MULTILINE)
2020-2121- benchmarks = []
2222- for section in sections:
2323- header_match = re.match(r"### (.*?) metrics", section)
2424- if not header_match:
2525- continue
2626- name = header_match.group(1).strip()
2727-2828- # Grab the unit (ns / ยตs / us) from the table header
2929- unit_match = re.search(r"\((ns|ฮผs|us)\)", section)
3030- if not unit_match:
3131- continue
3232- unit = unit_match.group(1)
3333-3434- # Extract p50 columns from rows like: | swiftcbor | 123 | 456 | ...
3535- row_pattern = r"\|\s*{}\s*\|\s*([0-9]+)\s*\|\s*([0-9]+)"
3636- swift_match = re.search(row_pattern.format("swiftcbor"), section, re.IGNORECASE)
3737- current_match = re.search(row_pattern.format("Current_run"), section, re.IGNORECASE)
3838-3939- if swift_match and current_match:
4040- swift_p50 = to_ns(swift_match.group(2), unit)
4141- current_p50 = to_ns(current_match.group(2), unit)
4242- ratio = current_p50 / swift_p50 if swift_p50 > 0 else float("inf")
4343- benchmarks.append((name, swift_p50, current_p50, ratio))
4444-4545- print("### " + file[1])
4646- print("")
4747- print("| Benchmark | SwiftCBOR (ns, p50) | CBOR (ns, p50) | % Improvement |")
4848- print("|-----------|----------------|-----------|------------|")
4949- for bench in benchmarks:
5050- improvement = (bench[1] - bench[2]) / bench[1] * 100
5151- print(f"| {bench[0]} | {bench[1]:,} | {bench[2]:,} | **{improvement:.2f}%** |")
5252- print("")
+1-1
FUZZ.md
···55First build the package in release mode with the fuzzer and address checkers on:
6677```bash
88-swift build -c release --sanitize fuzzer,address
88+swift build -c release --sanitize fuzzer
99```
10101111Then run it:
···2525- Customizable encoding options, such as date and key formatting.
2626- Customizable decoding, with the ability to reject non-deterministic CBOR data.
2727- Encoder creates deterministic CBOR data by default.
2828+ - Dictionaries are automatically sorted using a min heap for speed.
2929+ - Duplicate map keys are removed.
3030+ - Never creates non-deterministic collections (Strings, Byte Strings, Arrays, or Maps).
2831- A scan pass allows for decoding only the keys you need from an encoded object.
2932- Supports decoding half precision floats (Float16) as a regular Float.
3033- Runs on Linux, Android, and Windows using the swift-foundation project when available.
···3235- Supports tagged items (will expand and add ability to inject your own tags in the future):
3336 - Dates
3437 - UUIDs
3838+ - [CIDs](https://github.com/multiformats/cid) (tag 42)
3539- Flexible date parsing (tags `0` or `1` with support for any numeric value representation).
4040+- Decoding multiple top-level objects using `decodeMultiple(_:from:)`.
4141+- *NEW* IPLD compatible DAG-CBOR encoder for content addressable data.
4242+- *NEW* Flexible date decoding for untagged date items encoded as strings, floating point values, or integers.
4343+4444+> 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.
36453746## Usage
38474848+### Standard CBOR
4949+3950This 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.
40514152```swift
···4859let decoder = CBORDecoder()
4960let intValue = try decoder.decode(Int.self, from: Data([0])) // Result: 0
5061// Result: ["AB": 1, "A": 2]
5151-let mapValue = try decoder.decode([String: Int].self.self, from: Data([162, 98, 65, 66, 1, 97, 65, 2]))
6262+let mapValue = try decoder.decode([String: Int].self, from: Data([162, 98, 65, 66, 1, 97, 65, 2]))
5263```
53645465The encoder and decoders can be customized via the en/decoder initializers or via a `En/DecodingOptions` struct.
···7081let decoder = CBORDecoder(options: options)
7182```
72837373-[Documentation]() is hosted on the Swift Package Index.
8484+### DAG-CBOR
8585+8686+This 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.
8787+8888+```swift
8989+// !! SEE NOTE !!
9090+let dagEncoder = DAGCBOREncoder(dateEncodingStrategy: .double)
9191+```
9292+9393+To 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.
9494+```swift
9595+struct CID: CIDType {
9696+ let data: Data
9797+9898+ init(data: Data) {
9999+ self.data = data
100100+ }
101101+102102+ init(from decoder: any Decoder) throws {
103103+ let container = try decoder.singleValueContainer()
104104+ var data = try container.decode(Data.self)
105105+ data.removeFirst()
106106+ self.data = data
107107+ }
108108+109109+ func encode(to encoder: any Encoder) throws {
110110+ var container = encoder.singleValueContainer()
111111+ try container.encode(Data([0x0]) + data)
112112+ }
113113+}
114114+```
115115+> [!WARNING]
116116+>
117117+> 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.
118118+119119+Now, any time the encoder finds a `CID` type it will encode it using the correct tag.
120120+```swift
121121+let cid = CID(bytes: [0,1,2,3,4,5,6,7,8])
122122+let data = try DAGCBOREncoder().encode(cid)
123123+124124+print(data.hexString())
125125+// Output:
126126+// D8 2A # tag(42)
127127+// 4A # bytes(10)
128128+// 00000102030405060708 # "\u0000\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b"
129129+```
130130+> [!NOTE]
131131+> 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.
132132+133133+## Documentation
134134+135135+[Documentation](https://swiftpackageindex.com/thecoolwinter/CBOR/1.1.0/documentation/cbor) is hosted on the Swift Package Index.
7413675137## Installation
76138
+1
Sources/CBOR/CommonTags.swift
···1010 case stringDate = 0
1111 case epochDate = 1
1212 case uuid = 37
1313+ case cid = 42
1314}
+27
Sources/CBOR/CustomTags/TaggedCBORItem.swift
···11+//
22+// TaggedCBORItem.swift
33+// CBOR
44+//
55+// Created by Khan Winter on 10/15/25.
66+//
77+88+/// Protocol for tagged item encoding. Conform types to this to have them encoded in a tag container.
99+///
1010+/// Types that conform to this protocol must be `Codable` but are required to implement the method and initializer
1111+/// required by this protocol to ensure extra containers are not created between the tag container and the real data.
1212+/// This library implements this for Foundations ``Foundation/UUID`` type, Dates are handled as a special case.
1313+///
1414+/// For an example conformance, see ``CIDType``.
1515+public protocol TaggedCBORItem: Codable {
1616+ static var tag: UInt { get }
1717+1818+ init<Container: SingleValueDecodingContainer>(decodeTaggedDataUsing container: Container) throws
1919+ func encodeTaggedData<Container: SingleValueEncodingContainer>(using container: inout Container) throws
2020+}
2121+2222+/// Helper for getting the static member from an instance of the protocol. Otherwise sometimes has a crash at runtime
2323+/// when trying to do `T as! TaggedCBORItem.self` even if `T` is a `TaggedCBORItem`. Probably due to some fuckery in
2424+/// Foundation's stuff with UUID in particular. This fixes that though.
2525+extension TaggedCBORItem {
2626+ var __staticTagLookup: UInt { Self.tag }
2727+}
+33
Sources/CBOR/CustomTags/UUID+TaggedCBORItem.swift
···11+//
22+// UUID+TaggedCBORItem.swift
33+// CBOR
44+//
55+// Created by Khan Winter on 10/15/25.
66+//
77+88+#if canImport(FoundationEssentials)
99+import FoundationEssentials
1010+#else
1111+import Foundation
1212+#endif
1313+1414+extension UUID: TaggedCBORItem {
1515+ public static var tag: UInt { CommonTags.uuid.rawValue }
1616+1717+ public init<Container: SingleValueDecodingContainer>(decodeTaggedDataUsing container: Container) throws {
1818+ let data = try container.decode(Data.self)
1919+ guard data.count == 16 else { // UUID size
2020+ throw DecodingError.dataCorruptedError(
2121+ in: container,
2222+ debugDescription: "Data decoded for UUID tag is not 16 bytes long."
2323+ )
2424+ }
2525+ self = data.withUnsafeBytes { ptr in ptr.load(as: UUID.self) }
2626+ }
2727+2828+ public func encodeTaggedData<Container: SingleValueEncodingContainer>(using encoder: inout Container) throws {
2929+ try withUnsafeBytes(of: self) { ptr in
3030+ try encoder.encode(Data(ptr))
3131+ }
3232+ }
3333+}
+50
Sources/CBOR/Decoder/CBORDecoder.swift
···6868 }
6969 }
70707171+ /// Decodes multiple instances of the given type from CBOR binary data.
7272+ ///
7373+ /// Some BLOBs are made up of multiple CBOR-encoded datas concatenated without valid CBOR dividers (eg in an array
7474+ /// container). This method decodes that kind of data. It will attempt to decode an instance of the given type,
7575+ /// once done, if there's more data, it will continue to attempt to decode more instances.
7676+ ///
7777+ /// - Parameters:
7878+ /// - type: The decodable type to deserialize.
7979+ /// - data: The CBOR data to decode from.
8080+ /// - Returns: An instance of the decoded type.
8181+ /// - Throws: A ``DecodingError`` with context and a debug description for a failed deserialization operation.
8282+ public func decodeMultiple<T: Decodable>(_ type: T.Type, from data: Data) throws -> [T] {
8383+ do {
8484+ return try data.withUnsafeBytes {
8585+ let data = $0[...]
8686+ let reader = DataReader(data: data)
8787+ let scanner = CBORScanner(data: reader, options: options)
8888+ let results = try scanner.scan()
8989+9090+ guard !results.isEmpty else {
9191+ throw ScanError.unexpectedEndOfData
9292+ }
9393+9494+ let context = DecodingContext(options: options, results: results)
9595+ var nextRegion: DataRegion? = results.load(at: 0)
9696+9797+ var accumulator: [T] = []
9898+9999+ while let region = nextRegion {
100100+ let value = try SingleValueCBORDecodingContainer(context: context, data: region).decode(T.self)
101101+ accumulator.append(value)
102102+ let nextMapIndex = results.siblingIndex(region.mapOffset)
103103+ if nextMapIndex < results.count {
104104+ nextRegion = results.load(at: results.siblingIndex(region.mapOffset))
105105+ } else {
106106+ nextRegion = nil
107107+ }
108108+ }
109109+110110+ return accumulator
111111+ }
112112+ } catch {
113113+ if let error = error as? ScanError {
114114+ try throwScanError(error)
115115+ } else {
116116+ throw error
117117+ }
118118+ }
119119+ }
120120+71121 private func throwScanError(_ error: ScanError) throws -> Never {
72122 switch error {
73123 case .unexpectedEndOfData:
+1-1
Sources/CBOR/Decoder/Containers/DataRegion.swift
···66666767 func isNil() -> Bool {
6868 // This shouldn't modify the data stack. We just peek here.
6969- type == .simple && argument == 22
6969+ type == .simple && (argument == 22 || argument == 23)
7070 }
71717272 /// Reads the next variable-sized integer off the data stack.
···37373838 /// Check the next type on the data stack.
3939 /// - Parameter type: The type to check for.
4040- func checkType(_ types: MajorType...) throws {
4040+ func checkType<T>(_ types: MajorType..., forType: T.Type) throws {
4141 guard types.contains(data.type) else {
4242- throw DecodingError.typeMismatch(Bool.self, context.error("Unexpected type found: \(data.type)."))
4242+ throw DecodingError.typeMismatch(T.self, context.error("Unexpected type found: \(data.type)."))
4343+ }
4444+ }
4545+4646+ /// Check an available tag value by reading either the argument or the next int value.
4747+ func checkTag<T>(_ tag: UInt, forType: T.Type) throws {
4848+ let tagValue = try data.readInt(as: UInt.self)
4949+ guard tagValue == tag else {
5050+ throw DecodingError.typeMismatch(
5151+ T.self,
5252+ context.error("Unexpected tag found: \(tagValue), expected \(tag) for \(T.self)")
5353+ )
4354 }
4455 }
4556}
···11+//
22+// CIDType.swift
33+// CBOR
44+//
55+// Created by Khan Winter on 10/14/25.
66+//
77+88+/// A type that represents a [CID](https://github.com/multiformats/cid). When encoded using ``CBOREncoder`` or
99+/// ``DAGCBOREncoder``, uses the tag `42`. This is the only allowed tagged data type when using ``DAGCBOREncoder``.
1010+///
1111+/// To use, conform your internal CID type to ``CIDType``. Do not conform standard types like `String` or `Data` to
1212+/// ``CIDType``, or the encoder will attempt to encode all of those data as tagged items.
1313+/// ```swift
1414+/// struct CID: CIDType {
1515+/// let data: Data
1616+///
1717+/// init(data: Data) {
1818+/// self.data = data
1919+/// }
2020+///
2121+/// init<Container: SingleValueDecodingContainer>(decodeTaggedDataUsing container: Container) throws {
2222+/// var data = try container.decode(Data.self)
2323+/// data.removeFirst()
2424+/// self.data = data
2525+/// }
2626+///
2727+/// func encodeTaggedData<Container: SingleValueEncodingContainer>(using container: inout Container) throws {
2828+/// try container.encode(Data([0x0]) + data)
2929+/// }
3030+/// }
3131+/// ```
3232+/// Note that you **need** to prefix your data with the `NULL` character once encoded. This library will
3333+/// not handle that for you. It is invalid DAG-CBOR encoding to not include the prefixed byte.
3434+public protocol CIDType: TaggedCBORItem {}
3535+3636+extension CIDType {
3737+ /// The tag for all CID types is `42`.
3838+ public static var tag: UInt { 42 }
3939+}
···5151 private final class KeyBuffer {
5252 var count: Int { items.count }
53535454- let parent: ParentStorage
5555- var items: [EncodingOptimizer] = []
5454+ private let parent: ParentStorage
5555+ private var items: [EncodingOptimizer] = []
56565757 init(parent: ParentStorage) {
5858 self.parent = parent
5959 self.items = []
6060+ items.reserveCapacity(10)
6061 }
61626263 func forAppending() -> Indexed {
+54
Sources/CBOR/Encoder/DAGCBOREncoder.swift
···11+//
22+// DAGCBOREncoder.swift
33+// CBOR
44+//
55+// Created by Khan Winter on 10/14/25.
66+//
77+88+import Foundation
99+1010+/// Serializes ``Encodable`` objects using the DAG-CBOR serialization format.
1111+///
1212+/// To perform serialization, use the ``encode`` method to convert a Codable object to ``Data``. To
1313+/// configure encoding behavior, either pass customization options in with
1414+/// ``init(dateEncodingStrategy:)`` or modify ``dateEncodingStrategy``.
1515+///
1616+/// This type has no performance differences from ``CBOREncoder``. Instead, it automatically configures the private
1717+/// encoding options flags to generate always-valid DAG-CBOR encoded data.
1818+///
1919+/// - Warning: DAG-CBOR requires that implementations ***do not*** use tags for values such as dates. Because of this,
2020+/// depending on the ``DAGCBOREncoder/dateEncodingStrategy``, date values will
2121+/// be encoded without tag information as a different type.
2222+public struct DAGCBOREncoder {
2323+ /// Options that determine the behavior of ``DAGCBOREncoder``.
2424+ public var dateEncodingStrategy: EncodingOptions.DateStrategy
2525+2626+ /// Create a new CBOR encoder.
2727+ /// - Parameter dateEncodingStrategy: See ``EncodingOptions/dateEncodingStrategy``.
2828+ public init(dateEncodingStrategy: EncodingOptions.DateStrategy = .double) {
2929+ self.dateEncodingStrategy = dateEncodingStrategy
3030+ }
3131+3232+ /// Returns a DAG-CBOR-encoded representation of the value you supply.
3333+ /// - Parameter value: The value to encode as CBOR data.
3434+ /// - Returns: The encoded CBOR data.
3535+ public func encode<T: Encodable>(_ value: T) throws -> Data {
3636+ // Required overrides for valid DAG-CBOR encoding
3737+ let options = EncodingOptions.dag(dateEncodingStrategy: dateEncodingStrategy)
3838+3939+ let tempStorage = TopLevelTemporaryEncodingStorage()
4040+4141+ let encodingContext = EncodingContext(options: options)
4242+ let encoder = SingleValueCBOREncodingContainer(parent: tempStorage, context: encodingContext)
4343+ try encoder.encode(value)
4444+4545+ let dataSize = tempStorage.value.size
4646+ var data = Data(count: dataSize)
4747+ data.withUnsafeMutableBytes { ptr in
4848+ var slice = ptr[...]
4949+ tempStorage.value.write(to: &slice)
5050+ assert(slice.isEmpty)
5151+ }
5252+ return data
5353+ }
5454+}
···1212 var headerSize: Int { get }
1313 var contentSize: Int { get }
14141515- mutating func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>)
1616- mutating func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>)
1515+ func writeHeader(to data: inout Slice<UnsafeMutableRawBufferPointer>)
1616+ func writePayload(to data: inout Slice<UnsafeMutableRawBufferPointer>)
1717}
18181919extension EncodingOptimizer {
···22222323 var size: Int { 1 + headerSize + contentSize }
24242525- mutating func write(to data: inout Slice<UnsafeMutableRawBufferPointer>) {
2525+ func write(to data: inout Slice<UnsafeMutableRawBufferPointer>) {
2626 assert(data.count >= size)
27272828 // Write the type & argument
+42-1
Sources/CBOR/Encoder/EncodingOptions.swift
···2525 /// Determine how to encode dates.
2626 public let dateEncodingStrategy: DateStrategy
27272828+ /// Different strategies for encoding tagged items.
2929+ public enum TagStrategy {
3030+ /// Encode all tagged items. Default.
3131+ case accept
3232+ /// DAG mode option. Reject all tagged items (UUIDs). If possible, encoder uses an alternative encoding
3333+ /// method. Otherwise, throws an encoding error.
3434+ case dagMode
3535+ }
3636+3737+ /// Determine how to encode tagged items.
3838+ public let taggedItemsStrategy: TagStrategy
3939+4040+ /// Force the encoder to encode all floating point numbers as 64-bit double values.
4141+ public let forceDoubleLengthEncoding: Bool
4242+4343+ /// DAG mode option. Reject all Infinity and NaN values for floating-point numbers (`Double`, `Float`).
4444+ public let rejectInfAndNan: Bool
4545+2846 /// Initialize new encoding options.
2947 /// - Parameters:
3048 /// - forceStringKeys: Force encoded maps to use string keys even when integer keys are available.
3149 /// - useStringDates: See ``dateEncodingStrategy`` and ``DateStrategy``.
3232- public init(forceStringKeys: Bool, dateEncodingStrategy: DateStrategy) {
5050+ /// - taggedItemsStrategy: See ``taggedItemsStrategy`` and ``TagStrategy``.
5151+ /// - forceDoubleLengthEncoding: Encode all floating point numbers as 64-bit double values.
5252+ /// - rejectInfAndNan: DAG mode option. Reject all Infinity and NaN values for floating-point numbers (`Double`,
5353+ /// `Float`).
5454+ public init(
5555+ forceStringKeys: Bool,
5656+ dateEncodingStrategy: DateStrategy,
5757+ taggedItemsStrategy: TagStrategy,
5858+ forceDoubleLengthEncoding: Bool,
5959+ rejectInfAndNan: Bool
6060+ ) {
3361 self.forceStringKeys = forceStringKeys
3462 self.dateEncodingStrategy = dateEncodingStrategy
6363+ self.taggedItemsStrategy = taggedItemsStrategy
6464+ self.forceDoubleLengthEncoding = forceDoubleLengthEncoding
6565+ self.rejectInfAndNan = rejectInfAndNan
6666+ }
6767+6868+ static func dag(dateEncodingStrategy: DateStrategy) -> EncodingOptions {
6969+ EncodingOptions(
7070+ forceStringKeys: true,
7171+ dateEncodingStrategy: dateEncodingStrategy,
7272+ taggedItemsStrategy: .dagMode,
7373+ forceDoubleLengthEncoding: true,
7474+ rejectInfAndNan: true
7575+ )
3576 }
3677}