[CWT入門その1] CBORによるオブジェクトのバイナリ表現

# 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](http://cbor.me/) にて、エンコードしたいオブジェクトとバイナリの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)" を紹介する予定です。

## リンク

* [[CWT入門その2] CBOR Object Signing and Encryption (COSE)](https://qiita.com/ritou/items/bd2d429dae63bf0ad325)
* [[CWT入門その3] クレームもコンパクトに表現された CBOR Web Token (RFC8392)](https://qiita.com/ritou/items/d6a6c635ce72b14982f6#_reference-b191e7294b9c35e4de88)
* [CBOR Overview](https://speakerdeck.com/ritou/cbor-overview) 本投稿の流れに沿った粗い資料
* [CBOR — Concise Binary Object Representation | Overview](http://cbor.io/) CBOR関連の情報がまとまっているところ
* [RFC 7049 Concise Binary Object Representation (CBOR)](https://tools.ietf.org/html/rfc7049) RFC
* [CBOR playground](http://cbor.me/) 動作確認ができるところ