Skip to main content

3. Specification of the CBOR Encoding (CBOR 编码规范)

CBOR 数据项 (第 2 节) 被编码到或从包含本节描述的良构编码数据项的字节串中解码. 编码在附录 B 的表 7 中总结, 按初始字节索引. 编码器必须仅生成良构的编码数据项. 解码器在遇到不是良构编码 CBOR 数据项的输入时, 不得返回解码的数据项 (这不会削弱诊断和恢复工具的有用性, 这些工具可能会从损坏的编码 CBOR 数据项中提供一些信息).

每个编码数据项的初始字节包含有关主类型 (Major Type, 高位 3 位, 在第 3.1 节中描述) 和附加信息 (Additional Information, 低位 5 位) 的信息. 除少数例外情况外, 附加信息的值描述如何加载无符号整数 "参数" (argument):

小于 24: 参数的值就是附加信息的值.

24, 25, 26 或 27: 参数的值分别保存在后续的 1, 2, 4 或 8 个字节中, 按网络字节序排列. 对于主类型 7 和附加信息值 25, 26, 27, 这些字节不用作整数参数, 而是用作浮点值 (参见第 3.3 节).

28, 29, 30: 这些值保留用于 CBOR 格式的未来添加. 在当前版本的 CBOR 中, 编码项不是良构的.

31: 不派生参数值. 如果主类型是 0, 1 或 6, 则编码项不是良构的. 对于主类型 2 到 5, 项的长度是不定的 (indefinite), 对于主类型 7, 该字节根本不构成数据项, 而是终止不定长度项; 所有这些都在第 3.2 节中描述.

初始字节和用于构造参数的任何附加字节统称为数据项的 头部 (head).

此参数的含义取决于主类型. 例如, 在主类型 0 中, 参数是数据项本身的值 (在主类型 1 中, 数据项的值从参数计算得出); 在主类型 2 和 3 中, 它给出后续字符串数据的字节长度; 在主类型 4 和 5 中, 它用于确定包含的数据项数量.

如果编码的字节序列在数据项结束之前结束, 则该项不是良构的. 如果在解码最外层编码项后, 编码的字节序列仍有剩余字节, 则该编码不是单个良构的 CBOR 项. 根据应用程序, 解码器可以将编码视为不良构, 或只是向应用程序标识剩余字节的起始位置.

CBOR 解码器实现可以基于跳转表 (Jump Table), 该表包含初始字节的所有 256 个定义值 (表 7). 受限实现中的解码器可以改用初始字节和后续字节的结构来获得更紧凑的代码 (参见附录 C 以大致了解这可能是什么样子).

3.1. Major Types (主类型)

以下列出了主类型以及与类型相关的附加信息和其他字节.

主类型 0: 范围 0..2^(64)-1 (包括两端) 内的无符号整数. 编码项的值就是参数本身. 例如, 整数 10 表示为一个字节 0b000_01010 (主类型 0, 附加信息 10). 整数 500 将是 0b000_11001 (主类型 0, 附加信息 25) 后跟两个字节 0x01f4, 即十进制的 500.

主类型 1: 范围 -2^(64)..-1 (包括两端) 内的负整数. 项的值是 -1 减去参数. 例如, 整数 -500 将是 0b001_11001 (主类型 1, 附加信息 25) 后跟两个字节 0x01f3, 即十进制的 499.

主类型 2: 字节串 (Byte String). 字符串中的字节数等于参数. 例如, 长度为 5 的字节串将有一个初始字节 0b010_00101 (主类型 2, 长度的附加信息 5), 后跟 5 个字节的二进制内容. 长度为 500 的字节串将有 3 个初始字节 0b010_11001 (主类型 2, 附加信息 25 表示两字节长度) 后跟两个字节 0x01f4 表示长度 500, 然后是 500 个字节的二进制内容.

主类型 3: 编码为 UTF-8 [RFC3629] 的文本串 (Text String, 第 2 节). 字符串中的字节数等于参数. 包含无效 UTF-8 序列的字符串是良构的但无效的 (第 1.2 节). 提供此类型是为需要解释或显示人类可读文本的系统, 并允许区分非结构化字节和具有指定字符集 (Unicode) 和编码 (UTF-8) 的文本. 与 JSON 等格式不同, 此类型中的 Unicode 字符从不转义. 因此, 换行符 (U+000A) 在字符串中始终表示为字节 0x0a, 而不是字节 0x5c6e (字符 "" 和 "n") 也不是 0x5c7530303061 (字符 "", "u", "0", "0", "0" 和 "a").

主类型 4: 数据项的数组 (Array). 在其他格式中, 数组也称为列表 (lists), 序列 (sequences) 或元组 (tuples) ("CBOR 序列" 是略有不同的东西, 参见 [RFC8742]). 参数是数组中数据项的数量. 数组中的项不需要都是相同类型. 例如, 包含 10 个任意类型项的数组将有一个初始字节 0b100_01010 (主类型 4, 长度的附加信息 10) 后跟 10 个剩余项.

主类型 5: 数据项对的映射 (Map). 映射也称为表 (tables), 字典 (dictionaries), 哈希 (hashes) 或对象 (objects, 在 JSON 中). 映射由数据项对组成, 每对由一个键 (key) 和紧随其后的一个值 (value) 组成. 参数是映射中数据项 的数量. 例如, 包含 9 对的映射将有一个初始字节 0b101_01001 (主类型 5, 对数的附加信息 9) 后跟 18 个剩余项. 第一项是第一个键, 第二项是第一个值, 第三项是第二个键, 依此类推. 因为映射中的项成对出现, 它们的总数始终是偶数: 包含奇数项的映射 (最后一个键数据项后没有值数据) 不是良构的. 具有重复键的映射可能是良构的, 但它不是有效的, 因此会导致不确定的解码; 另见第 5.6 节.

主类型 6: 标记数据项 ("标签", Tag), 其标签号 (Tag Number) 是范围 0..2^(64)-1 (包括两端) 内的整数, 作为参数, 其包含的数据项 (标签内容, tag content) 是紧随头部之后的单个编码数据项. 参见第 3.4 节.

主类型 7: 浮点数和简单值 (Simple Values), 以及 "break" 停止码. 参见第 3.3 节.

这八种主类型产生一个简单的表, 显示数据项初始字节的 256 个可能值中哪些被使用 (表 7).

在主类型 6 和 7 中, 许多可能的值保留用于未来规范. 有关这些值的更多信息, 请参见第 9 节.

表 1 总结了 CBOR 定义的主类型, 暂时忽略第 3.2 节. 此表中的数字 N 代表参数.

主类型含义内容
0无符号整数 N-
1负整数 -1-N-
2字节串N 字节
3文本串N 字节 (UTF-8 文本)
4数组N 个数据项 (元素)
5映射2N 个数据项 (键/值对)
6标签号 N1 个数据项
7简单值/浮点-

表 1: CBOR 主类型的定长使用概览 (N = 参数)

3.2. Indefinite Lengths for Some Major Types (某些主类型的不定长度)

四个 CBOR 项 (数组, 映射, 字节串和文本串) 可以使用附加信息值 31 以不定长度编码. 如果项的编码需要在数组或映射内的项数或字符串的总长度已知之前开始, 则这很有用. (在数据项的所有内容已知之前开始发送数据项的能力通常被称为该数据项内的 "流式传输" (streaming)).

不定长度数组和映射的处理方式与不定长度字符串 (字节串和文本串) 不同.

3.2.1. The "break" Stop Code ("break" 停止码)

"break" 停止码用主类型 7 和附加信息值 31 (0b111_11111) 编码. 它本身不是数据项: 它只是关闭不定长度项的语法特性.

如果 "break" 停止码出现在期望数据项的位置, 而不是直接在不定长度字符串, 数组或映射内 -- 例如, 直接在定长数组或映射内 -- 则包含项不是良构的.

3.2.2. Indefinite-Length Arrays and Maps (不定长度数组和映射)

不定长度数组和映射使用其主类型和附加信息值 31 表示, 后跟数组的零个或多个项的任意长度序列或映射的键/值对, 再后跟 "break" 停止码 (第 3.2.1 节). 换句话说, 不定长度数组和映射看起来与其他数组和映射相同, 除了以附加信息值 31 开始并以 "break" 停止码结束.

如果 "break" 停止码出现在映射中的键之后, 代替该键的值, 则映射不是良构的.

没有限制嵌套不定长度数组或映射项. "break" 只终止单个项, 因此嵌套的不定长度项需要与开始不定长度项的类型字节数完全相同的 "break" 停止码数量.

例如, 假设编码器想要表示抽象数组 [1, [2, 3], [4, 5]]. 定长编码将是 0x8301820203820405:

83        -- 长度为 3 的数组
01 -- 1
82 -- 长度为 2 的数组
02 -- 2
03 -- 3
82 -- 长度为 2 的数组
04 -- 4
05 -- 5

不定长度编码可以根据需要独立应用于此数据项中编码的三个数组中的每一个, 从而产生如下表示:

0x9f018202039f0405ffff

9F        -- 开始不定长度数组
01 -- 1
82 -- 长度为 2 的数组
02 -- 2
03 -- 3
9F -- 开始不定长度数组
04 -- 4
05 -- 5
FF -- "break" (内部数组)
FF -- "break" (外部数组)

3.2.3. Indefinite-Length Byte Strings and Text Strings (不定长度字节串和文本串)

不定长度字符串由包含字节串或文本串主类型且附加信息值为 31 的字节表示, 后跟指定类型的零个或多个具有定长的字符串 ("块", chunks), 并以 "break" 停止码 (第 3.2.1 节) 结束. 不定长度字符串表示的数据项是块的连接. 如果不存在块, 则数据项是指定类型的空字符串. 允许零长度块, 虽然不是特别有用.

如果不定长度字符串指示符 (0b010_11111 或 0b011_11111) 和 "break" 停止码之间的任何项不是相同主类型的定长字符串项, 则字符串不是良构的.

该设计不允许将不定长度字符串作为块嵌套到不定长度字符串中. 如果允许, 则需要解码器实现保留堆栈或至少保留嵌套级别的计数. 在编码器端这是不必要的, 因为内部不定长度字符串将由块组成, 这些块可以直接放入外部不定长度字符串中.

如果不定长度文本串内的任何定长文本串无效, 则不定长度文本串无效. 请注意, 这意味着单个 Unicode 码点 (标量值) 的 UTF-8 字节不能在块之间分散: 文本串的新块只能在码点边界处开始.

3.3. Floating-Point Numbers and Values with No Content (浮点数和无内容值)

主类型 7 用于两种类型的数据: 浮点数和不需要任何内容的 "简单值" (Simple Values). 初始字节中的 5 位附加信息的每个值都有其自己的独立含义, 如表 3 中所定义. 与整数的主类型一样, 此主类型的项不携带内容数据; 所有信息都在初始字节 (头部) 中.

5 位值语义
0..23简单值 (值 0..23)
24简单值 (后续字节中的值 32..255)
25IEEE 754 半精度浮点 (后跟 16 位)
26IEEE 754 单精度浮点 (后跟 32 位)
27IEEE 754 双精度浮点 (后跟 64 位)
28-30保留, 在本文档中不是良构的
31不定长度项的 "break" 停止码 (第 3.2.1 节)

表 3: 主类型 7 中附加信息的值

与所有其他主类型一样, 5 位值 24 表示单字节扩展: 它后跟一个附加字节来表示简单值. (为了最小化混淆, 仅使用值 32 到 255.) 这维护了初始字节的结构: 与其他主类型一样, 这些字节的长度始终取决于第一个字节中的附加信息. 表 4 列出了为简单值分配和可用的数值.

语义
0..19(未分配)
20false
21true
22null
23undefined
24..31(保留)
32..255(未分配)

表 4: 简单值

编码器不得发出以 0xf8 (主类型 7, 附加信息 24) 开头并继续以小于 0x20 (32 十进制) 的字节的两字节序列. 这样的序列不是良构的. (这意味着编码器不能以两字节序列编码 "false", "true", "null" 或 "undefined", 并且只有这些的单字节变体是良构的; 更一般地说, 每个简单值只有一个表示变体).

5 位值 25, 26 和 27 用于 16 位, 32 位和 64 位 IEEE 754 二进制浮点值 [IEEE754]. 这些浮点值在适当大小的附加字节中编码. (有关 16 位浮点数的一些信息, 请参见附录 D.)

3.4. Tagging of Items (项目标记)

在 CBOR 中, 数据项可以用标签 (tag) 包围以赋予其一些额外的语义, 由 标签号 (tag number) 唯一标识. 标签是主类型 6, 其参数 (第 3 节) 指示标签号, 并且它包含单个封闭数据项, 即 标签内容 (tag content). (如果标签需要其内容的进一步结构, 则此结构由封闭数据项提供.) 我们使用术语 标签 表示由标签号和标签内容组成的整个数据项: 标签内容是被标记的数据项.

例如, 假设长度为 12 的字节串标记有号码 2 的标签以指示它是无符号 大数 (bignum, 第 3.4.3 节). 编码的数据项将以字节 0b110_00010 (主类型 6, 标签号的附加信息 2) 开始, 后跟编码的标签内容: 0b010_01100 (主类型 2, 长度的附加信息 12) 后跟大数的 12 个字节.

在扩展通用数据模型中, 标签号的定义描述了标签号传达的额外语义. 这些语义可能包括某些标记数据项与其他数据项的等价性, 包括一些可以在基本通用数据模型中表示的数据项. 例如, 0xc24101, 一个标签内容为具有单个字节 0x01 的字节串的大数, 等价于整数 1, 也可以编码为 0x01, 0x1801 或 0x190001. 标签定义可以指定推荐用于通用编码器的首选序列化 (第 4.1 节); 这可能更喜欢基本通用数据模型表示而不是使用标签的表示.

标签定义通常定义哪些嵌套数据项对此类标签有效. 标签定义可以将其内容限制为非常特定的语法结构, 如本文档中定义的标签所做的那样, 或者它们可以更语义地定义其内容. 后者的一个例子是标签 40 和 1040 如何接受多种表示数组的方式 [RFC8746].

按照惯例, 许多标签不接受 "null" 或 "undefined" 值作为标签内容; 相反, 期望是可以使用 "null" 或 "undefined" 值代替整个标签; 第 3.4.2 节为一个特定标签提供了有关在应用程序协议中和映射到平台类型时处理此约定的一些进一步考虑.

解码器不需要理解每个标签号的标签, 并且在创建特定 CBOR 数据项的实现和解码该流的实现知道数据流中每个项的语义含义的应用程序中, 标签可能价值不大. 本规范中标签的主要目的是定义常见数据类型 (如日期). 次要目的是在预见到 CBOR 数据项需要翻译成不同格式时提供转换提示, 需要有关项内容的提示. 理解标签的语义对于解码器是可选的; 它可以简单地向应用程序呈现标签号和标签内容, 而不解释标签的额外语义.

标签将语义应用于其包含的数据项. 标签可以嵌套: 如果标签 A 包含标签 B, 而标签 B 包含数据项 C, 则标签 A 应用于对数据项 C 应用标签 B 的结果.

IANA 维护标签号注册表, 如第 9.2 节所述. 表 5 提供了在 [RFC7049] 中定义的标签号列表, 其定义在本节其余部分. (标签号 35 也在 [RFC7049] 中定义; 第 3.4.5.3 节讨论了此标签号.) 请注意, 自 [RFC7049] 发布以来, 已定义了许多其他标签号; 有关完整列表, 请参见第 9.2 节描述的注册表.

标签数据项语义
0文本串标准日期/时间字符串; 见第 3.4.1 节
1整数或浮点基于纪元的日期/时间; 见第 3.4.2 节
2字节串无符号大数; 见第 3.4.3 节
3字节串负大数; 见第 3.4.3 节
4数组十进制分数; 见第 3.4.4 节
5数组大浮点数; 见第 3.4.4 节
21(任意)期望转换为 base64url 编码; 见第 3.4.5.2 节
22(任意)期望转换为 base64 编码; 见第 3.4.5.2 节
23(任意)期望转换为 base16 编码; 见第 3.4.5.2 节
24字节串编码的 CBOR 数据项; 见第 3.4.5.1 节
32文本串URI; 见第 3.4.5.3 节
33文本串base64url; 见第 3.4.5.3 节
34文本串base64; 见第 3.4.5.3 节
36文本串MIME 消息; 见第 3.4.5.3 节
55799(任意)自描述 CBOR; 见第 3.4.6 节

表 5: RFC 7049 中定义的标签号

从概念上讲, 标签在通用数据模型中解释, 而不是在 (反) 序列化时解释. 少数标签 (目前, 标签号 25 和标签号 29 [IANA.cbor-tags]) 已注册的语义可能需要在 (反) 序列化时处理: 解码器需要知道, 编码器需要控制数据项编码到 CBOR 数据项中的确切顺序. 这意味着这些标签不能在任意通用 CBOR 编码器/解码器之上实现 (它可能不反映映射中条目在数据模型级别的序列化顺序, 反之亦然); 因此它们的实现通常需要集成到通用编码器/解码器中. 不推荐定义具有此属性的新标签.

IANA 分配了标签号 65535, 4294967295 和 18446744073709551615 (16 位, 32 位和 64 位的二进制全 1). 这些可以方便实现者使用, 他们希望使用单整数数据结构来指示特定标签的存在或标签的缺失. 该分配在 [CBOR-TAGS] 的第 10 节中描述. 这些标签不打算出现在实际的 CBOR 数据项中; 实现可以将这种出现标记为错误.

协议可以通过使用标签号 0 和 1 来扩展通用数据模型 (第 2 节) 以表示时间点的数据项, 通过使用标签号 2 和 3 来扩展任意大小的整数, 通过使用标签号 4 和 5 来扩展任意大小和精度的浮点值.