| 1 | module cbor |
| 2 | |
| 3 | import io |
| 4 | |
| 5 | // Stream I/O wrappers over the standard `io.Reader` / `io.Writer` |
| 6 | // interfaces. Use these for files, sockets, pipes — anywhere the |
| 7 | // payload doesn't fit cleanly in a single `[]u8`. |
| 8 | |
| 9 | // encode_to serialises `val` into an internal buffer, then writes the |
| 10 | // bytes to `w` in a loop until everything is accepted. Errors on the |
| 11 | // first I/O failure. |
| 12 | pub fn encode_to[T](val T, mut w io.Writer, opts EncodeOpts) ! { |
| 13 | bytes := encode[T](val, opts)! |
| 14 | mut written := 0 |
| 15 | for written < bytes.len { |
| 16 | n := w.write(bytes[written..])! |
| 17 | if n == 0 { |
| 18 | return error('cbor: writer stopped accepting bytes at ${written}/${bytes.len}') |
| 19 | } |
| 20 | written += n |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | // decode_from reads bytes from `r` until EOF (or until |
| 25 | // `DecodeOpts.max_stream_bytes` is hit) and decodes a single top-level |
| 26 | // value. For multi-value streams, use `Unpacker` directly on a |
| 27 | // pre-buffered slice. |
| 28 | // |
| 29 | // Always set `max_stream_bytes` on untrusted readers — otherwise a peer |
| 30 | // that never sends EOF blocks the call forever. |
| 31 | pub fn decode_from[T](mut r io.Reader, opts DecodeOpts) !T { |
| 32 | bounded := opts.max_stream_bytes > 0 |
| 33 | mut buf := []u8{cap: 4096} |
| 34 | for { |
| 35 | if bounded && buf.len >= opts.max_stream_bytes { |
| 36 | return error('cbor: stream exceeded max_stream_bytes (${opts.max_stream_bytes})') |
| 37 | } |
| 38 | slot_len := if bounded { |
| 39 | cap_left := opts.max_stream_bytes - buf.len |
| 40 | if cap_left < 4096 { |
| 41 | cap_left |
| 42 | } else { |
| 43 | 4096 |
| 44 | } |
| 45 | } else { |
| 46 | 4096 |
| 47 | } |
| 48 | mut slot := []u8{len: slot_len} |
| 49 | // Treat io.Eof as the legitimate end of the stream; any other |
| 50 | // reader error must propagate so callers don't mistake a |
| 51 | // transport failure for a successful (truncated) decode. The |
| 52 | // unbounded branch used to delegate to `io.read_all`, which |
| 53 | // also swallows non-EOF errors — same loop here keeps the |
| 54 | // strict semantic regardless of whether `max_stream_bytes` is set. |
| 55 | n := r.read(mut slot) or { |
| 56 | if err is io.Eof { |
| 57 | break |
| 58 | } |
| 59 | return err |
| 60 | } |
| 61 | if n == 0 { |
| 62 | break |
| 63 | } |
| 64 | buf << slot[..n] |
| 65 | } |
| 66 | return decode[T](buf, opts)! |
| 67 | } |
| 68 | |
| 69 | // pack_to is the streaming sibling of `encode_to`, for users who built |
| 70 | // their payload manually via the `Packer` API. Errors if any |
| 71 | // indefinite-length container is still open — emitting half-closed |
| 72 | // CBOR would produce a payload no decoder can parse. |
| 73 | pub fn (mut p Packer) pack_to(mut w io.Writer) ! { |
| 74 | if !p.is_complete() { |
| 75 | return error('cbor: pack_to called with an open indefinite-length item — close it with pack_break first') |
| 76 | } |
| 77 | bytes := p.bytes() |
| 78 | mut written := 0 |
| 79 | for written < bytes.len { |
| 80 | n := w.write(bytes[written..])! |
| 81 | if n == 0 { |
| 82 | return error('cbor: writer stopped at ${written}/${bytes.len}') |
| 83 | } |
| 84 | written += n |
| 85 | } |
| 86 | } |
| 87 | |