LoginSignup
88
45

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-03-18

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 では 9FFF , Map では BFFF で表現されます。

# [_ 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)" を紹介する予定です。

リンク

88
45
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
88
45