nlohmann/json

CBOR: cbor_tag_handler_t::ignore does not ignore tags 0-5 and 21-23 (head bytes 0xC0-0xC5, 0xD5-0xD7)

Open

#5,315 opened on Jul 27, 2026

 (3 comments) (0 reactions) (0 assignees)C++ (7,450 forks)batch import
aspect: binary formatsgood first issue

Repository metrics

Stars
 (50,278 stars)
PR merge metrics
 (Avg merge 9d 4h) (16 merged PRs in 30d)

Description

Description

cbor_tag_handler_t::ignore is documented as "ignore tags", but from_cbor(…, cbor_tag_handler_t::ignore) still throws parse_error.112 for CBOR tags 0–5 and 21–23.

Every byte in 0xC00xDB is major type 6 (tag). The switch in parse_cbor_internal() enumerates only 0xC60xD4 and 0xD80xDB (binary_reader.hpp:763-781), so head bytes 0xC00xC5 and 0xD50xD7 fall through to default: and are reported as "invalid byte" — in every handler mode, including ignore and store.

The gap is at both ends of the range and covers the tags most likely to appear in real data: 0 (RFC 3339 date/time string), 1 (epoch time), 2/3 (bignum), 4 (decimal fraction), 5 (bigfloat), and 21/22/23 (expected base64url/base64/base16 conversion).

There is also an interop consequence. An encoder using preferred serialization (RFC 8949 §4.1) encodes a tag number below 24 in the 1-byte head, e.g. tag 23 as 0xD7. This library writes binary subtypes as 0xD8 0x17 instead (see write_cbor, binary_writer.hpp:310-332), so its own round-trip works — but a byte string tagged 23 by a conforming encoder cannot be read back at all, in any mode.

Reproduction steps

Parse a tagged value with cbor_tag_handler_t::ignore for each tag head byte in 0xC00xDB.

Expected vs. actual results

Expected: ignore skips the tag head and parses the enclosed data item, uniformly for all of 0xC00xDB.

Actual, sweeping the whole range with the tagged item being a byte string (Y = parses, n = parse_error.112):

head byte tag error ignore store
0xC00xC5 0–5 n n n
0xC60xD4 6–20 n Y Y
0xD50xD7 21–23 n n n
0xD80xDB 1/2/4/8-byte argument n Y Y

A realistic document fails:

map with tag-0 datetime, ignore : THROW: [json.exception.parse_error.112]
    parse error at byte 4: syntax error while parsing CBOR value: invalid byte: 0xC0

Minimal code example

#include <nlohmann/json.hpp>
#include <iostream>
using json = nlohmann::json;

int main()
{
    // {"t": 0("2026-07-27T00:00:00Z")} -- a text string tagged with tag 0
    const std::vector<std::uint8_t> v{
        0xA1, 0x61, 0x74, 0xC0, 0x74, '2','0','2','6','-','0','7','-','2','7',
        'T','0','0',':','0','0',':','0','0','Z'};

    // works: tag 6 in the same position is skipped
    const std::vector<std::uint8_t> w{0xC6, 0x18, 0x2A};
    std::cout << json::from_cbor(w, true, true, json::cbor_tag_handler_t::ignore) << "\n";

    // throws: tag 0 is not skipped
    std::cout << json::from_cbor(v, true, true, json::cbor_tag_handler_t::ignore) << "\n";
}

Error messages

[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing CBOR value: invalid byte: 0xC0

Compiler and operating system

g++ 13.3.0, Ubuntu 24.04, -std=c++11

Library version

develop @ 8ec98e2

Would the fix be breaking?

Not in the API/ABI sense — no signatures, types, or enumerators change, and the amalgamated header stays source- and binary-compatible.

Behaviorally it is a narrow, opt-in-scoped change:

  • Default behavior is untouched. cbor_tag_handler_t::error is the default and must keep throwing on every tag, including these. Callers who never pass a tag handler see no difference at all.
  • ignore/store callers would see input that currently throws start to parse. That is a real behavior change, but it moves in the direction those callers explicitly asked for. Code that relies on ignore throwing for tag 0 while silently accepting tag 6 seems very unlikely to be intentional.
  • This is not a request to interpret the tags. No bignum/date decoding is implied — the tag head is skipped and the enclosed data item is parsed on its own, exactly as tags 6–20 are handled today. A tagged text string stays a string; 0xC2 over a byte string stays a byte string, not an integer.
  • Test and doc churn. unit-cbor.cpp's "all CBOR first bytes" test lists 0xc00xc5 and 0xd50xd7 in its unsupported set, and the "Incomplete mapping" warning in docs/mkdocs/docs/features/binary_formats/cbor.md lists them as parse errors. Both would need updating, which also means the current behavior is documented rather than accidental — so this may warrant a release note even though nothing breaks.

A conservative alternative, if changing ignore is considered too much: leave the reader as is and only document the asymmetry more explicitly. That keeps the interop gap, though.

Related: #1968 added tag skipping in 3.9.0 and closed on the 0xD9 0xD9 0xF7 self-describe case, which lands inside the supported 0xD80xDB range — the two omitted ranges were likely just missed then.

Contributor guide