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.
library(zucbor) hex <- function(h) as.raw(strtoi(substring(h, seq(1, nchar(h), 2), seq(2, nchar(h), 2)), 16L))
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:
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:
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.
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.
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.
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.
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.
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.
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.
Every error is a condition inheriting zucbor_error, so one handler can
catch them all, and its subclass says what went wrong:
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.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.