| 1 | // Package cbor implements RFC 8949 (Concise Binary Object Representation). |
| 2 | // |
| 3 | // Three layers of API are available: |
| 4 | // |
| 5 | // * `encode[T]` / `decode[T]` — comptime-driven generic API. Works on |
| 6 | // primitives, strings, arrays, maps with any scalar key, structs |
| 7 | // (with `@[cbor: 'name']` and `@[skip]` attributes), enums, |
| 8 | // `time.Time` (auto-tagged), and any type implementing |
| 9 | // `Marshaler` / `Unmarshaler`. |
| 10 | // |
| 11 | // * `Packer` / `Unpacker` — manual streaming API. Use when the schema |
| 12 | // isn't known at compile time, or when you need full control over |
| 13 | // tags, indefinite-length items and simple values. |
| 14 | // |
| 15 | // * `Value` sumtype — dynamic representation for round-tripping |
| 16 | // unknown payloads or inspecting tagged data. |
| 17 | // |
| 18 | // Defaults follow RFC 8949 *preferred serialisation* (§4.2.2): floats |
| 19 | // shrink to the shortest IEEE 754 width that preserves their value, and |
| 20 | // every length argument uses the shortest encoding. Set |
| 21 | // `EncodeOpts.canonical = true` to additionally sort map keys for |
| 22 | // hash/signature stability (§4.2.1, deterministic encoding). |
| 23 | module cbor |
| 24 | |
| 25 | // encode serialises any V value into CBOR bytes. The returned slice |
| 26 | // owns its backing buffer (V's GC tracks it) — no copy, so the returned |
| 27 | // bytes are safe to keep across calls and to pass to other modules. |
| 28 | pub fn encode[T](val T, opts EncodeOpts) ![]u8 { |
| 29 | mut p := new_packer(opts) |
| 30 | p.pack[T](val)! |
| 31 | return p.bytes() |
| 32 | } |
| 33 | |
| 34 | // decode parses CBOR bytes into a value of type T. Rejects extra bytes |
| 35 | // after the top-level item by default — callers feeding a buffer that |
| 36 | // holds multiple concatenated items (or that may carry an unrelated |
| 37 | // suffix) must opt in via `DecodeOpts.allow_trailing_bytes = true` and |
| 38 | // drive an `Unpacker` themselves. |
| 39 | // |
| 40 | // A leading self-describe tag (`d9 d9 f7`, RFC 8949 §3.4.6) is stripped |
| 41 | // transparently so payloads encoded with `EncodeOpts.self_describe` |
| 42 | // round-trip through `decode[T]` without the caller having to peel it. |
| 43 | pub fn decode[T](data []u8, opts DecodeOpts) !T { |
| 44 | mut u := new_unpacker(data, opts) |
| 45 | if u.data.len - u.pos >= 3 && u.data[u.pos] == 0xd9 && u.data[u.pos + 1] == 0xd9 |
| 46 | && u.data[u.pos + 2] == 0xf7 { |
| 47 | u.pos += 3 |
| 48 | } |
| 49 | value := u.unpack[T]()! |
| 50 | if !opts.allow_trailing_bytes && !u.done() { |
| 51 | return malformed(u.pos, '${u.remaining()} trailing byte(s) after top-level item') |
| 52 | } |
| 53 | return value |
| 54 | } |
| 55 | |