---
title: "Decoding untrusted CBOR"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Decoding untrusted CBOR}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```

CBOR usually arrives from somewhere you do not control: a network peer, a
sensor, a browser relaying an authenticator. zucbor treats every input as
hostile, and this vignette shows what that means in practice.

```{r}
library(zucbor)
hex <- function(h) as.raw(strtoi(substring(h, seq(1, nchar(h), 2), seq(2, nchar(h), 2)), 16L))
```

## Check first, build second

`cbor_decode()` works in two phases. The first walks the whole input and
checks it: well-formedness, UTF-8, the content of the tags it knows,
duplicate map keys and the limits. Only when all of that passes does the
second phase build R values. So a nine-byte header that claims an array of
2^64 − 1 elements is refused before R allocates anything:

```{r}
tryCatch(cbor_decode(hex("9bffffffffffffffff")),
         zucbor_error = function(e) conditionMessage(e))
```

`cbor_validate()` runs exactly that first phase and nothing else. It
returns `TRUE` or `FALSE`; with `error = TRUE` it raises the condition
that explains a `FALSE`:

```{r}
cbor_validate(hex("830102"))
tryCatch(cbor_validate(hex("830102"), error = TRUE), zucbor_error = function(e) e$offset)
```

The two agree about the bytes. They disagree only where the bytes are valid
CBOR that R cannot hold, such as a text string containing U+0000, which
`cbor_decode()` refuses with `zucbor_unrepresentable`.

## Limits

Three limits bound what an input can cost.

| Limit | Default | What it bounds |
|---|---|---|
| `max_size` | 64 MiB | the input, in bytes |
| `max_depth` | 256 | nesting of arrays, maps and tags (at most `r zucbor_info()$max_depth_cap`) |
| `max_items` | 1,000,000 | data items, counting every tag and string chunk |

`max_items` matters even though `max_size` bounds the input: one byte of
CBOR can become dozens of bytes of R objects, so 64 MiB of empty arrays
would otherwise ask R for gigabytes.

```{r}
deep <- c(rep(as.raw(0x81), 300), as.raw(0x00))    # [[[...[0]...]]]
tryCatch(cbor_decode(deep), zucbor_limit_error = function(e) c(e$limit, e$limit_value))
tryCatch(cbor_decode(hex("83010203"), max_items = 3),
         zucbor_limit_error = function(e) class(e)[1])
```

`cbor_read()` reads a file, URL or connection, and never reads more than
`max_size + 1` bytes, so an endless stream fails instead of filling memory.
A log or a socket that is a long CBOR sequence is read item by item with
`cbor_read_seq(each =)`: then `max_size` bounds each item rather than the
stream, and each item is still checked whole before it is built.

## Duplicate map keys

RFC 8949 leaves duplicate keys to the application, and they are a classic
way to make two parsers of the same signed structure see different values.
zucbor refuses them by default, comparing keys by value: the integer 1
written in one byte and in two is the same key.

```{r}
cbor_validate(hex("a2010018010000"))          # {1: 0, 1: 0}, the second 1 longer
cbor_decode(hex("a2616101616102"), duplicate_keys = TRUE)   # {"a": 1, "a": 2}
```

When duplicates are allowed, the map comes back as a `cbor_map`, which keeps
both entries in order; a named list could not say which was which.

## Deterministic input

Some protocols require a single encoding for each value, so that a
re-encoded structure matches what was signed. `deterministic = TRUE`
refuses anything that is not RFC 8949 core deterministic encoding: the
shortest integers, lengths and floats, definite lengths, map keys in
bytewise order and bignums in preferred form.

```{r}
cbor_validate(hex("1801"), deterministic = TRUE)       # 1 in two bytes
cbor_validate(hex("a203040102"), deterministic = TRUE) # keys out of order
```

Everything `cbor_encode()` writes passes that check, and decoding then
encoding deterministic input gives back the same bytes.

## Handling errors

Every error is a condition inheriting `zucbor_error`, so one handler can
catch them all, and its subclass says what went wrong:

```{r}
classify <- function(x) tryCatch({cbor_decode(x); "ok"}, zucbor_error = function(e) class(e)[1])
vapply(list(hex("8201"), hex("61ff"), hex("a201000100"), hex("6100"), hex("0102")), classify, "")
```

Conditions raised while checking input also carry `offset`, the byte
offset of the fault, and `status`, the name of the underlying status. See
`?"zucbor-conditions"` for the whole hierarchy.
