Blog

2020.03.09

CDDLの紹介

エンジニアの森田です。

本記事ではRFC 8610にて標準化されているスキーマ記述言語CDDL(Concise Data Definition Language)について紹介、解説します。

前置き(CBORについて)

IoTな世界がやってきて組み込みデバイスもインターネット上でデータをやり取りをすることが多くなってきました。

ご存知のとおり、Web上ではデータの交換にJSONフォーマットがよく使われています。 PCなどのリッチな世界ではJSONはとても便利ですが、 組み込みデバイスの世界ではメモリやコードサイズといった計算に使えるリソースが限られており、JSONの利用が適さない場合もあります。

CBOR(Concise Binary Object Representation)は、JSONとの相互運用性とともに、 エンコードやデコードに必要な計算リソースに対する配慮がなされたオブジェクトのバイナリ表現の標準であり、IETFのIoT関連分野の標準化において積極的に利用されています。

CBORについて以前のブログ記事でも紹介されているので、より詳しく知りたい方は是非ご覧ください。

CDDLとは

さて、本記事の本題であるCDDL(Concise Data Definition Language)について解説したいと思います。 まずCDDLとは何かというと、CBORのデータ構造を記述するためのスキーマ記述言語です。 CDDLを使うことで、プロトコルで使われるCBORデータのフォーマットを曖昧さなく記述でき、あるCBORデータが記述されたフォーマットに従っているかどうかを検証できるようになります。

(CDDLで検索するとしばしばCommon Development and Distribution Licenseがひっかかってしまうのですが、それとは別なものなのでご注意ください。)

また、CBORで利用できるデータ型はJSONで使えるデータ型のスーパーセットになっているため、CDDLはJSONのスキーマ記述にも利用できるとされています。 以降、CDDLの使い方や記述の仕方について簡単な例を元に説明したいと思います。

他のスキーマ記述言語とCDDLとの違い

CDDLからは少し話が逸れますが、JSON向けのスキーマ記述言語としてjson-schema.orgによって策定されているJSON Schemaという規格があり、Web API等でやり取りされるJSONデータのフォーマットや型を記述できます。 こちらはJSONスキーマを表現する方法としてJSON自身を利用するという方法をとっており、文法的にはJSONそのものです。 またXMLにもXML Schemaというものがあり、これもXML自身で記述されます。 CDDLとこれらのスキーマ記述言語の違いとして、そもそも対象とするデータフォーマットが異なる、という事もあるのですが、 独立した文法をもっているという事です。

XML SchemaやJSON Schemaは文法を覚える必要はない代わりに、キーワードやタグ名の意味や配置方法を正確に覚える必要があり、 個人的には読み書きのために大変労力を必要とするものでした。

一方、CDDLでは、その目標の一つとして機械可読性とともに人間によって読み書きできるフォーマットを提供する事が挙げられており、 個人的にも、スキーマ記述の読み書きが随分やりやすくなるよう設計されていると感じます。

(G4) Provide a single format that is both readable and editable for humans and processable by a machine.

CDDLの例

CDDLの文法としてはBNFに似ているところがあり、ルールの宣言を並べることによってスキーマ全体が表現されます。 BNFを知っている方は特に違和感なく読めるのではないかと思います。

例としてシンプルなJSON向けの(CBORにも使えますが)スキーマを作ってみました。

gender-choice = "man" / "woman" / "transgender" / "not listed"

schema = {
  name: text,
  age: number,
  ? favorite-foods: [+ text]
  ? gender: gender-choice
}

このスキーマは、「必須なkeyとして”name”と”age”を持ち、オプショナルなkeyとして”favorite-foods”, “gender”を持つオブジェクトであって、”name”に対する値として任意の文字列、”age”に対する値として任意の数値、”favorite-foods”に対する値として、各要素が文字列であるような1以上の長さの配列が対応するし、”gender”に対する値として四つ文字列の何れかをもつもの」を表現しています。

詳しく見ると、{}は map(JSONにおけるobject)が出現する事、[]は配列が出現する事、:はkey/valueペアが出現する事を意味しており、JSONの記法と直接対応しています。 ?はオプショナルな出現、+は1個以上の出現を意味しており、正規表現でおなじみのパターンです。この例では出てきませんでしたが* は0個以上の繰り返しを意味します。 /はchoiceと呼ばれ、/で区切られた要素の内のどれか一つが出現する事を意味しています。

上のスキーマは簡単な例ではありますが、直感的に読める事がおわかりいただけたのではないでしょうか。 このスキーマに従うJSONのインスタンスとしては次のようなものがあります。

{
  "name": "morita",
  "age": 3,
  "favorites": ["miso soup with nameko", "dekopon", "anpanman"]
}

さて、CDDLの主な適用先はCBORですので、上のJSONデータに相当するCBORデータを考えると、次のような68byteのバイナリになります。

A3                                      # map(3)
   64                                   # text(4)
      6E616D65                          # "name"
   66                                   # text(6)
      6D6F72697461                      # "morita"
   63                                   # text(3)
      616765                            # "age"
   03                                   # unsigned(3)
   69                                   # text(9)
      6661766F7269746573                # "favorites"
   83                                   # array(3)
      75                                # text(21)
         6D69736F20736F75702077697468206E616D656B6F # "miso soup with nameko"
      67                                # text(7)
         64656B6F706F6E                 # "dekopon"
      68                                # text(8)
         616E70616E6D616E               # "anpanman"

上記のCBORデータはhttp://cbor.me/を利用して作成しました。 JSONデータからCBORデータを生成したい時は是非使ってみてください。

CBORらしくしてみる

さて、JSONではkey/valueペアのkeyは必ず""で囲まれた文字列である必要がありました(上のJSONにおける”name”,”age”,”favrites”のように)。 しかし、CBORではJSONとは異なり、文字列以外の型の値をkeyとして利用できます。 実際、CBORではデータサイズを小さくするために、keyとして文字列ではなく整数値が使われる事が多いです。 整数をkeyとして扱いたい場合、key/valueペアを表現するために:の代わりに=>を使います。 先程のスキーマを整数キーを使って書き換えてみると次のようになります。

gender-choice = "man" / "woman" / "transgender" / "not listed"

name = 0
age = 1
favorite-foods = 2
gender = 3

schema = {
  name => text,
  age => number,
  ? favorite-foods => [+ text]
  ? gender => gender-choice
}

なぜ:ではなく=>を使う必要があったのかというと、これはCDDLの文法上のルールによるものです。 CDDLではhoge:"hoge" => の別記法とされていますので、keyとして文字列以外を利用する時は常に=>を使う必要があります。

では、上記のCDDLで記述されたフォーマットに従うインスタンスを作ってみましょう。 JSONでは本来整数はkeyとして使えないので以下のような書き方はinvalidですが、無理矢理JSONインスタンスを書くと次のようになります。

{
  0: "morita",
  1: 3,
  2: ["miso soup with nameko", "dekopon", "anpanman"]
}

http://cbor.me/ では、このようなinvalidなJSONもCBORに変換してくれるらしく、 変換結果として以下の52byteのデータを得ました。たしかに少しサイズが小さくなっています。

A3                                      # map(3)
   00                                   # unsigned(0)
   66                                   # text(6)
      6D6F72697461                      # "morita"
   01                                   # unsigned(1)
   03                                   # unsigned(3)
   02                                   # unsigned(2)
   83                                   # array(3)
      75                                # text(21)
         6D69736F20736F75702077697468206E616D656B6F # "miso soup with nameko"
      67                                # text(7)
         64656B6F706F6E                 # "dekopon"
      68                                # text(8)
         616E70616E6D616E               # "anpanman"

その他の文法要素

CDDLには上の例で出てきたものの他にもいろいろ文法要素があります。 中には少し難しいものもありますが、本記事では簡単に紹介するにとどめておきます。

  • Groups( () ) – key/valueペアのセットをcurly brace(())で括ったもの。groupとして定義した識別子をmap/arrayで再利用することで冗長な記述を減らせる
  • Ranges( ... ) – 繰り返しの回数を制御するオペレータ
  • Tables( type => value ) – key-valueのkeyに型を指定する事でその型に属する任意のkeyの出現を表現できる
  • Cuts( ^ ) – keyのマッチを終了させるオペレータでTablesの利用で意図しないkeyのマッチを禁止する
  • Unwraping( ~ ) – map/arrayから内部のgroupを取り出す

CDDLの実装

CDDLの実装は、まだそれほど多くないと思われますが、 それでも幾つか既に開発されているものがあるようです。

私は記事を書くまではRuby製のものしか知らなかったのですが、組み込みデバイス上でバリデーションが動かしたいという動機をかんがえると小さいフットプリントで動作するRustという選択肢はなかなかよさそうです。

最後に

組み込み開発というと、昔は閉ざされた環境で悶々と開発をしているイメージでした(私も昔はそんな感じで仕事していました)。 しかし、今はIoTの時代です。CBORとCDDLを使って組み込みデバイス越しにインターネット上でお話しする未来がすぐそこにあるのかもしれません。

また、CDDLはCBORにもJSONにも使えるスキーマ記述言語です。 Web開発者の方でJSON Schemaはちょっと使いづらいな、と感じられている方は、是非CDDLを試してみては如何でしょうか。JSON APIの仕様策定などに役立つかもしれません。

以上、CBORとCDDLの解説でした。