RFC7049 CBORによるオブジェクトのバイナリ表現
概要
この投稿はCBOR Web Token(CWT)入門3部作の第1弾です。
CWTとは
- みんな大好き JWT(JSON Web Token) のバイナリ版
- JSON + Base64な日常にピリリと辛い刺激を与える、IoT時代注目の一品
- 今話題のWebAuthn(WebAuthentication API)でも使われている(追記)
という感じです。
詳細な説明は後ほどの投稿で行うとして、ここではJWTにおけるJSONに相当する、CBORというオブジェクト表現についてざっくりと紹介します。
CBORとは
The Concise Binary Object Representation (CBOR) is a data format
whose design goals include the possibility of extremely small code
size, fairly small message size, and extensibility without the need
for version negotiation.
メッセージサイズよりも、コードを小さくしたい気持ちが伝わってきます。
CBORが目指すところ
- インターネット標準で利用される一般的なデータフォーマットをエンコード可能
- 電力、CPUパワー、命令セットが限られたシステムのために、エンコーダー/デコーダーをコンパクトに実装可能
- エンコードされたデータは自己記述的であり、デコードのためのスキーマ記述は不要
- (サイズ、実装の複雑性がJSONよりも)合理的なコンパクトシリアライゼーション
- 制限があり、大量のデータ処理を扱う端末のための効率的なCPU利用率
- JSON互換
- 拡張性
CBORで表現可能なデータ
- 整数
- バイナリ
- テキスト
-
Array
,Map
- 浮動小数点数、多倍長整数
-
true
/false
/null
/undefined
- 日付、日時
などなど、一通りのデータフォーマットがサポートされています。
エンコード仕様
全てのデータフォーマットについて細かいエンコードの方法を記載していくのは大変なので、少しだけ抜粋して紹介します。気になった方は仕様をご覧ください。
また、CBOR playground にて、エンコードしたいオブジェクトとバイナリの16進表記の変換ができるので大変便利です。
整数
エンコード結果は、整数であることとその値から構成されます。
# 0
00 # unsigned(0)
# 23
17 # unsigned(23)
# 24
18 18 # unsigned(24)
# 255
18 FF # unsigned(255)
1バイトの符号なし整数のうち、0 から 23 までは、 0 + 整数値
として表現します。
24 から 255 までの値は、先頭が 24(18)
、 その次に整数値が続く2バイトで表現します。
2, 4, 8バイトの符号なし整数については、それぞれ先頭が 25(19)
, 26(1A)
, 27(1B)
となります。
# 256
19 0100 # unsigned(256)
# 65535
19 FFFF # unsigned(65535)
# 65536
1A 00010000 # unsigned(65536)
# 4294967295
1A FFFFFFFF # unsigned(4294967295)
# 4294967296
1B 0000000100000000 # unsigned(4294967296)
# 18446744073709551615
1B FFFFFFFFFFFFFFFF # unsigned(18446744073709551615)
整数の表現は、バイナリやテキストをエンコードする際にもサイズの表現に使われます。
負の整数については、符号なし整数の表現の1バイトめに 20(32)
が加算され、その後に(符号なし整数の値 - 1)の値を続けて表現します。
# -1
20 # negative(0)
# -24
37 # negative(23)
# -25
38 18 # negative(24)
...
# -18446744073709551616
3B FFFFFFFFFFFFFFFF # negative(18446744073709551615)
バイナリ/テキスト
バイナリとテキストの表現は、(種類 + サイズ + バイナリ)で表現されます。
種類 + サイズの部分は、符号なし整数のエンコードと同様の形式です。
# h''
40 # bytes(0)
# ""
# ""
60 # text(0)
# ""
# h'6162636465'
45 # bytes(5)
6162636465 # "abcde"
# "abcde"
65 # text(5)
6162636465 # "abcde"
# h'6162636465666768696A6B6C6D6E6F707172737475767778'
58 18 # bytes(24)
6162636465666768696A6B6C6D6E6F707172737475767778 # "abcdefghijklmnopqrstuvwx"
# "abcdefghijklmnopqrstuvwx"
78 18 # text(24)
6162636465666768696A6B6C6D6E6F707172737475767778 # "abcdefghijklmnopqrstuvwx"
Array
, Map
それぞれ、Array
であることと要素数、Map
であることと key
の数に続いて、エンコードされた要素が並びます。
# [1, "a"]
82 # array(2)
01 # unsigned(1)
61 # text(1)
61 # "a"
# ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x"]
98 18 # array(24)
61 # text(1)
61 # "a"
...
61 # text(1)
78 # "x"
# {1:"a"}
A1 # map(1)
01 # unsigned(1)
61 # text(1)
61 # "a"
(Mapの要素が多いやつは省略)
Indefinite-Length
Indefinite-Length
つまり、不定長の Array
, Map
, バイナリ、文字列も表現可能です。
Array
では 9F
と FF
, Map
では BF
と FF
で表現されます。
# [_ 1, "a"]
9F # array(*)
01 # unsigned(1)
61 # text(1)
61 # "a"
FF # primitive(*)
# {_ 1:"a"}
BF # map(*)
01 # unsigned(1)
61 # text(1)
61 # "a"
FF # primitive(*)
その他
JSONで表現できる、false
/ true
/ null
も表現可能です。
# false
F4 # primitive(20)
# true
F5 # primitive(21)
# null
F6 # primitive(22)
その他、仕様に書いてあること
- CBORを用いたプロトコルの作り方(扱い方?)
- CBOR / JSON の相互変換
- 拡張について
- 既存のバイナリ表現との違い
と言ったあたりが定義されていますが、今回はどんな感じでエンコードできるの?ってのがわかれば良さそうという認識で、省略します。
MessagePack
あたりとの比較とかも面白そうなので後でやるかもしれません。
JSONとのサイズ比較
一通りのオブジェクトに対して、CBORでJSONと同レベルの表現ができそう、ということで、ちょっと気になるサイズ比較をしてみます。
Value | CBOR | JSON |
---|---|---|
0 | 1 | 1 |
23 | 1 | 2 |
18446744073709551615 | 9 | 20 |
18446744073709551616 | 11 | 20 |
"12345678901234567890123" | 24 | 25 |
"123456789012345678901234" | 26 | 26 |
[] | 1 | 2 |
[1, 2, 3, 4] | 5 | 9 |
[“a”, “b”, “c”, “d”] | 9 | 17 |
{} | 1 | 2 |
{1:2, 3:4} | 5 | 13 |
{“a”:”b”, “c”:”d”} | 9 | 17 |
h’01020304’ | 5 | 10 |
32 bytes binary | 34 | 46 |
やはり、整数はコンパクトになってる感じがしますね。
Map
もCBORだとkey/valを並べるだけなので、Array
と同様のサイズになります。
となると、例えばkey/value形式のクレームを扱いたいような場合、あらかじめ想定されている文字列などは整数値(できれば0~23)に置き換えつつ、 Array
なり Map
で構造化されたデータを作ってやるとデータ量を抑えることができそうです。
まとめ&次回予告
- CBORで一般的なデータフォーマットの表現方式(エンコード方法)について紹介した
- JSONとのサイズ比較をしてみた
次回は、このCBORを用いた署名付き/暗号化された構造体の実現方法、つまりJWTで言う所のJOSEに相当する、"CBOR Object Signing and Encryption(COSE)" を紹介する予定です。