Nature ソフトウェアエンジニアの桒山です。この記事は、第2回 Nature Engineering Blog 祭の4日目の記事になります!
今回も引き続き Matter ネタです。今回は少々マニアックな Matter 1.0 仕様における TLV ついて紹介します。
Matter における TLV エンコーディング
TLV とは Tag-length-value のことです。情報の種類を Tag、情報の長さを Length、値を Value というセットで表現するフォーマットで広く知られています。
Matter 仕様においても、TLV が様々な情報を表現するのに用いられます。Matter における TLV 仕様は Matter で定義された独自のものです。Matter における TLV 仕様を知らないと、そのままでは読めないと思います。具体的には、Tag も文字列がそのままエンコードされているわけではありません。
Matter のアプリケーションを開発するとき、ほとんどの場合に意識しなくて良いはずです。しかし、ごく稀に Matter デバイスの状態を知るために Attribute を読みたくなる時があるかも知れません。その場合に、Matter TLV が読めると開発が進みやすくなるかも知れません。ちなみに、Remo nano 開発チームでは読む必要がありました。"特殊な訓練" だと思って読んでください。
本記事では、最初に TLV Tag-length-value について表現されるものを説明します。次に、どのようにエンコーディングされるか示します。そして最後に、実例で確認します。
Matter TLV における Tag・Length・Value の意味
説明の都合上、Value、Length、Tag の順番で説明します。
Value の型
Matter TLV の Value には型があり、構造を持つデータも表現できるようになっています。型は大別すると以下があります。
- 整数
- 文字列
- 浮動小数点数
- ブール値
- Null
- 構造体
- 配列
- リスト
整数にも、符号付き or 符号無しがあります。また、符号付きにも 1, 2, 4, 8-octets のものがあります。
全ての型については、Matter 1.0 Core Specification Appendix A: Tag-length-value (TLV) Encoding Format 1 を参照してください。
Length と型の関係
Matter TLV の Length は Value の型に応じて、エンコード上の表現が異なります。型は「長さが決まっている型」「長さを示すフィールドが必要な型」「終端を示すマーカーが必要な型」に分けられます。
- 長さが決まっている型
- 整数
- 浮動小数点数
- ブール値
- Null
- 長さを示すフィールドが必要な型
- 文字列
- 終端を示すマーカーが必要な型
- 構造体
- 配列
- リスト
Tag の種類
Matter TLV の Tag には大きく分けて 3 つ種類があります。それぞれ示す情報が
- Anonymous Tag
- Tag の無い要素を示します
- Context-specific Tag
- コンテキスト固有の Tag です、先述の構造体やリスト内で要素を識別します。Tag 番号が含まれます。
- Profile-specific Tag
- プロファイル固有の Tag です、この Tag は Matter またはベンダーによって定義されるプロファイルを識別します。
- Profile-specific Tag には、さらに Fully-Qualified Tag/Implicit Profile Tag/Common Profile Tag という種類があります。それぞれ、含められる情報が異なります。
Tag の示す意味を詳しく知りたい場合は、Matter 1.0 Core Specification Appendix A: Tag-length-value (TLV) Encoding Format を参照してください。
Matter TLV のエンコーディング
上記を踏まえて、Matter TLV のエンコーディングを見ていきましょう!
Matter TLV は
- Control Octet
- Tag
- Value
でエンコードされます。
Control Octet
Matter TLV において、TLV は Control Octet という制御のための 1-octet から始まります。Control Octet は Tag-length-value のセットが、どのような Tag を含むか、どの型の値を含むかを 1-octet で示します。
1-octet の前半 3-bits は Tag の種類を示します。Tag の種類は前述の通りです。
Control Octet | Description | |||||||
---|---|---|---|---|---|---|---|---|
Tag Control | Element Type | |||||||
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
0 | 0 | 0 | x | x | x | x | x | Anonymous Tag Form, 0 octets |
0 | 0 | 1 | x | x | x | x | x | Context-specific Tag Form, 1 octet |
0 | 1 | 0 | x | x | x | x | x | Context-specific Tag Form, 2 octet |
0 | 1 | 1 | x | x | x | x | x | Context-specific Tag Form, 4 octet |
1 | 0 | 0 | x | x | x | x | x | Implicit Profile Tag Form, 2 octets |
1 | 0 | 1 | x | x | x | x | x | Implicit Profile Tag Form, 4 octets |
1 | 1 | 0 | x | x | x | x | x | Fully-qualified Tag Form, 6 octets |
1 | 1 | 1 | x | x | x | x | x | Fully-qualified Tag Form, 8 octets |
後半 5-bits で Value の型を示します。
私が面白いと思ったのは、ブール値の True/False が Control Octet にて型として示されるている点です。つまり、Value 自体はエンコード上では含まれません。
また、0x18
は End of Container です。先述の「終端を示すマーカーが必要な型」におけるマーカーです。構造体、配列、リストの終端にエンコードされます。
Control Octet | Description | |||||||
---|---|---|---|---|---|---|---|---|
Tag Control | Element Type | |||||||
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
x | x | x | 0 | 0 | 0 | 0 | 0 | Signed Integer, 1-octet value |
x | x | x | 0 | 0 | 0 | 0 | 1 | Signed Integer, 2-octet value |
x | x | x | 0 | 0 | 0 | 1 | 0 | Signed Integer, 4-octet value |
x | x | x | 0 | 0 | 0 | 1 | 1 | Signed Integer, 8-octet value |
x | x | x | 0 | 0 | 1 | 0 | 0 | Unsigned Integer, 1-octet value |
x | x | x | 0 | 0 | 1 | 0 | 1 | Unsigned Integer, 2-octet value |
x | x | x | 0 | 0 | 1 | 1 | 0 | Unsigned Integer, 4-octet value |
x | x | x | 0 | 0 | 1 | 1 | 1 | Unsigned Integer, 8-octet value |
x | x | x | 0 | 1 | 0 | 0 | 0 | Boolean False |
x | x | x | 0 | 1 | 0 | 0 | 1 | Boolean True |
x | x | x | 0 | 1 | 0 | 1 | 0 | Floating Point Number, 4-octet value |
x | x | x | 0 | 1 | 0 | 1 | 1 | Floating Point Number, 8-octet value |
x | x | x | 0 | 1 | 1 | 0 | 0 | UTF-8 String, 1-octet length |
x | x | x | 0 | 1 | 1 | 0 | 1 | UTF-8 String, 2-octet length |
x | x | x | 0 | 1 | 1 | 1 | 0 | UTF-8 String, 4-octet length |
x | x | x | 0 | 1 | 1 | 1 | 1 | UTF-8 String, 8-octet length |
x | x | x | 1 | 0 | 0 | 0 | 0 | Octet String, 1-octet length |
x | x | x | 1 | 0 | 0 | 0 | 1 | Octet String, 2-octet length |
x | x | x | 1 | 0 | 0 | 1 | 0 | Octet String, 4-octet length |
x | x | x | 1 | 0 | 0 | 1 | 1 | Octet String, 1-octet length |
x | x | x | 1 | 0 | 1 | 0 | 0 | Null |
x | x | x | 1 | 0 | 1 | 0 | 1 | Structure |
x | x | x | 1 | 0 | 1 | 1 | 0 | Array |
x | x | x | 1 | 0 | 1 | 1 | 1 | List |
0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | End of Container |
x | x | x | 1 | 1 | 0 | 0 | 0 | Reserved (where xxx are not 000) |
x | x | x | 1 | 1 | 0 | 0 | 1 | Reserved |
x | x | x | 1 | 1 | 0 | 1 | 0 | Reserved |
x | x | x | 1 | 1 | 0 | 1 | 1 | Reserved |
x | x | x | 1 | 1 | 1 | 0 | 0 | Reserved |
x | x | x | 1 | 1 | 1 | 0 | 1 | Reserved |
x | x | x | 1 | 1 | 1 | 1 | 0 | Reserved |
x | x | x | 1 | 1 | 1 | 1 | 1 | Reserved |
Tag のエンコーディング
Matter TLV において、TLV は Control Octet の次に Tag がエンコードされます。Control Octet の前半 3-bits 部分にて、Tag の種類が示されます。Tag の種類ごとに、0, 1, 2, 4, 6, 8-octet(s) のそれぞれエンコードされます。
0-octet の場合は、Anonymous Tag です。つまり、Tag の無い要素の場合に Tag はエンコードされません。
Value のエンコーディング
Matter TLV において、Tag-length-value の最後に Value がエンコードされます。Control Octet の後半 5-bits 部分にて、Value の型が示されます。Value の型ごとに、エンコード方法が異なります。 本記事では、例として 5 つ説明します。
- Unsigned Integer, 1-octet
- リトルエンディアン形式の符号無し整数、1-octet なので範囲は 0~255 です
- Signed Integer, 1 octet
- リトルエンディアンの 2 の補数形式の符号付き整数、1-octet なので範囲は -128~127 です
- Boolean False
- UTF-8 String, 1-octet length
- Structure
- 構造体です。内部に TLV を含みます。終端は End of Container によって示されます
全ての Value 型 のエンコード方法を詳しく知りたい場合は、Matter 1.0 Core Specification Appendix A: Tag-length-value (TLV) Encoding Format を参照してください。
Matter TLV エンコーディングの例
本記事では、いくつか例を説明します。
00 ef
最初の 1-octet を読みます。Control Octet は下記の通りです。
27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Control Octet 前半 3-bits より、Tag は Anonymous Tag であることが分かります。Anonymous Tag はエンコードされません。つまり、2-octet 目以降は Value だと分かります。
Control Octet 後半 5-bits より、Value の型は Signed Integer, 1-octet であることが分かります。2-octet 目をリトルエンディアンの 2 の補数形式の符号付き整数で解釈すれば良いと分かります。
0xef
は十進数で表すと -17 です。
よって、00 ef
は Anonymous Tag で値は -17 (int8) であることが分かりました。
0x08
最初の 1-octet を読みます。Control Octet は下記の通りです。
27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
Control Octet 前半 3-bits より、Tag は Anonymous Tag であることが分かります。Anonymous Tag はエンコードされません。つまり、2-octet 目以降は Value だと分かります。
Control Octet 後半 5-bits より、Value の型は Boolean False であることが分かります。
よって、0x08
は Anonymous Tag で値は False であることが分かりました。
0x24 0x01 0x2a
最初の 1-octet を読みます。Control Octet は下記の通りです。
27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
Control Octet 前半 3-bits より、Tag は Context-specific Tag であることが分かります。Tag の長さは 1-octet であるため、2-octet 目が Tag 番号を示すことが分かります。
Tag 番号は 1 です。
Control Octet 後半 5-bits より、Value の型は Unsigned Integer, 1-octet であることが分かります。3-octet 目をリトルエンディアン形式の符号無し整数で解釈すれば良いと分かります。
0x2a
は十進数で表すと 42 です。
よって、0x24 0x01 0x2a
は Context-specific Tag の Tag 番号 1 で値は 42 (uint8) であることが分かりました。
0x0c 0x06 0x48 0x65 0x6c 0x6c 0x6f 0x21
最初の 1-octet を読みます。Control Octet は下記の通りです。
27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
Control Octet 前半 3-bits より、Tag は Anonymous Tag であることが分かります。Anonymous Tag はエンコードされません。つまり、2-octet 目以降は Value だと分かります。
Control Octet 後半 5-bits より、Value の型は UTF-8 String, 1-octet length であることが分かります。型より、Value の最初の 1-octet が長さを示すことが分かります。0x06
は十進数で表すと 6 です。続く 6-octets が文字列です。
0x48 0x65 0x6c 0x6c 0x6f 0x21
は UTF-8 で Hello! です。
よって、0x0c 0x06 0x48 0x65 0x6c 0x6c 0x6f 0x21
は Anonymous Tag で値は Hello! であることが分かりました。
0x15 0x20 0x00 0x2a 0x20 0x01 0xef 0x18
最初の 1-octet を読みます。Control Octet は下記の通りです。
27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
Control Octet 前半 3-bits より、Tag は Anonymous Tag であることが分かります。Anonymous Tag はエンコードされません。つまり、2-octet 目以降は Value だと分かります。
Control Octet 後半 5-bits より、Value の型は構造体であることが分かります。内部構造を模式的に書くと下記のようになります。
{ // 0x15 0: 42, // 0x20 0x00 0x2a 1: -17 // 0x20 0x01 0xef } // 0x18
0x20 0x00 0x2a
の最初の 1-octet を読み、Control Octet として解釈します。省略しますが、Tag 番号 0 で値は 42 (int8) です。
また、 0x20 0x01 0xef
の最初の 1-octet を読み、Control Octet として解釈します。省略しますが、Tag 番号 1 で値は -17 (int8) です。
よって、0x15 0x20 0x00 0x2a 0x20 0x01 0xef 0x18
は Anonymous Tag の構造体です。要素は、Tag 番号 0 として 42 (int8)、Tag 番号 1 として -17 (int8) を含むことが分かりました。
tlv_tool
Matter TLV エンコードを解釈してくれるツールもあります。”訓練" 不要な方は、こちらを利用するのが良いと思います。
$ git clone git@github.com:project-chip/matter-rs.git $ cd tools/tlv_tool $ cargo install --path . $ tlv_tool --hex "00, ef" S8(-17) ---------
まとめ
Matter TLV の仕様とエンコーディングについて、例を交えつつざっくり説明しました。もし Matter 開発において、TLV のバイナリを読みたくなった場合に助けになれば幸いです。Matter コントローラや Matter デバイスの自作する際には、ぜひ Remo nano を遊び相手としてご検討ください。Matter 対応の Nature Remo nano が定価 3,980 円で発売中です!
次回は月曜日、ファームウェアエンジニアの中林 (id:tomo-wait-for-it-yuki) さんが担当です。よろしくお願いします。
Nature エンジニアコミュニティ
Matter や、Nature Remo シリーズのお話をみなさまと楽しむために、開発者コミュニティもはじめました!Nature Remo Could API の疑問から雑談まで、Nature のエンジニアとお話ししましょう!Discord への参加はこちらから。開発者コミュニティへのご参加もお待ちしております!
更新履歴
- 2023/07/07 : 初版