Matter 09 · インタラクションモデル — Read / Write / Invoke / Subscribe

Chapter 09

インタラクションモデル — Read / Write / Invoke / Subscribe

この章がなぜ必要なのか——データモデルを「動かす」ための唯一の手段だから.

07 章〜08 章で「機器は Attribute と Command の集まりである」と分かった。 ではそれをどうやって読み書きするのか。

Matter のインタラクションモデル (IM) は 4 種類しかない。 Read・Write・Invoke・Subscribe。HTTP の GET/POST よりも少ない。

シンプルだが、Path とワイルドカード、そしてステータスコードの扱いに 実装上の要点が集中している。

この章で使う既出の用語(定義は各リンク先). Matter(01 章 3 節)、コミッショニング(01 章 3 節)、ネットワーク(01 章 3 節)、ACL(02 章 4 節)、Attribute(02 章 2 節)、Cluster(02 章 2 節)、Command(02 章 2 節)、Endpoint(02 章 2 節)、Event(02 章 2 節)、Node(02 章 2 節)、Report(02 章 6 節)、インタラクションモデル(02 章 1 節)、NDP(03 章 5 節)、マルチキャスト(03 章 3 節)、必要(05 章 5 節)、Exchange(06 章 5 節)、Group(06 章 4 節)、再送(06 章 3 節)、Fabric-Scoped(07 章 7 節)、Binding(08 章 2 節)、Descriptor(08 章 1 節)、OnOff(08 章 2 節)、イベント(08 章 8 節)

1. 4 つのインタラクション

種類対象用途
ReadAttribute / Event現在の値・過去のイベントを取得
WriteAttribute値を設定
InvokeCommand動作を要求
SubscribeAttribute / Event変化を通知させる(10 章)

これだけである。すべての操作がこの 4 つに帰着する。

2. Path — 何を指すか

インタラクションの対象は Path で指定する。

Attribute Path

{ Node, Endpoint, Cluster, Attribute }
フィールドワイルドカード可
Endpoint○
Cluster○
Attribute○

Command Path

{ Endpoint, Cluster, Command }

Invoke では原則としてワイルドカードは使えない(グループコマンドを除く)。 「どの機器に何をさせるか」は曖昧であってはならないからである。

Event Path

{ Node, Endpoint, Cluster, Event, isUrgent }

3. ワイルドカードの展開

ワイルドカードは「省略」で表現する。省略されたフィールドは「すべて」を意味する。

指定意味結果の例
1/6/0Endpoint 1 / On/Off / OnOff1 個
1/6/*Endpoint 1 の On/Off の全属性OnOff, StartUpOnOff, グローバル属性…
*/6/0全 Endpoint の OnOff 属性3 口タップなら 3 個
1/*/*Endpoint 1 の全クラスタの全属性数十個
*/*/*すべて数百個
# chip-tool でのワイルドカード読み出し
chip-tool onoff read on-off 1 0xFFFF          # 全 Endpoint の OnOff
chip-tool any read-by-id 0xFFFFFFFF 0xFFFFFFFF 1 0xFFFF   # すべて

*/*/* は強力だが危険である.

コントローラが機器の全体像を把握するのに使う定石だが、 Thread 機器では応答が巨大になり、フラグメントが多発して失敗する(03 章)。

実務では:

と段階的に読むのが安全である。 仕様には応答を分割する仕組み(後述の chunking)があるが、 それでも回数が増えれば失敗確率は上がる。

ワイルドカードで返らないもの

ワイルドカード読み出しでは、以下は返らない(明示指定が必要)。

「読めるはずの属性が返ってこない」の主因は権限である. ワイルドカードは「読める範囲で返す」動作をする。 明示的に指定すれば UNSUPPORTED_ACCESS が返り、原因が分かる。 デバッグでは明示指定に切り替えて確認するとよい。

4. Read

Read Request { AttributePaths, EventPaths, DataVersionFilters, FabricFiltered }
        ↓
Report Data { AttributeReports[], EventReports[], SuppressResponse }

DataVersion — 無駄な転送を減らす

各クラスタは DataVersion という単調増加のカウンタを持つ。 クラスタ内の属性が変わるたびに増える。

Read Request に DataVersionFilter を付ける
   → 機器側で「変わっていない」なら、その分は返さない

これが帯域とバッテリーを大きく節約する. コントローラが再接続のたびに全属性を読み直すと、Thread 機器には大きな負担になる。 前回の DataVersion を覚えておいて添えれば、 変化した部分だけが返る。

ただし DataVersion はクラスタ単位である。 1 つの属性が変わればクラスタ全体が返る。

FabricFiltered

Fabric-Scoped な属性(07 章)を読むとき、 「自分の Fabric の分だけ」か「全部(見える範囲で)」かを指定する。

Chunking

応答が MTU を超えるとき、複数のメッセージに分割して送る。 リストの途中で切れる場合、受信側は再構築する必要がある。

リスト属性の chunking は実装上の落とし穴である. 分割されたリストは「最初に空リストを送り、以降 append する」形で送られる。 コントローラ側で正しく再構築しないと、データが壊れる。 SDK を使っていれば処理されるが、独自実装では要注意。

5. Write

Write Request { WriteRequests[{ Path, Data, DataVersion }], TimedRequest, SuppressResponse }
        ↓
Write Response { WriteResponses[{ Path, Status }] }
特徴内容
複数属性を一度に書ける部分的な成功もありうる
DataVersion を添えられる「読んだときから変わっていなければ書く」楽観的排他制御
Timed Write一部の属性は Timed Request が必須(次節)

DataVersionMismatch (0x92) が返ったら、読み直してから書き直す. これは「他の誰かが先に変更した」ことを意味する。 マルチアドミン環境では実際に起きる。

6. Timed Interaction — 中間者攻撃への対策

一部の操作は、Timed Request を先行させなければならない。

Timed Request { Timeout }        ← 「これから T ms 以内に本番の要求を送ります」
        ↓
Status Response
        ↓
Write / Invoke Request           ← 期限内に送る
目的内容
遅延した再送の悪用を防ぐ攻撃者がパケットを保留し、後で流し込む攻撃を封じる

どの操作が Timed 必須かは仕様が決めている. ドアロックの解錠など、遅延して実行されると危険な操作が対象である。

「後から流されると困る操作」—— 深夜に保留されていた解錠コマンドが朝に実行される、といった攻撃を想定している。

実装で NEEDS_TIMED_INTERACTION (0xC6) が返ったら、この仕組みを使っていない。

7. Invoke

Invoke Request { InvokeRequests[{ CommandPath, CommandFields }], TimedRequest, SuppressResponse }
        ↓
Invoke Response { InvokeResponses[{ Command or Status }] }
応答の形例
ステータスのみOn → SUCCESS
応答コマンドAttestationRequest → AttestationResponse

Group Command

グループ ID 宛にマルチキャストで送るコマンド。応答はない。

「リビングの照明」グループに Off コマンド
   → 該当する全機器が同時に消える
特徴内容
一斉性個別送信より同時に近い(照明の一斉制御で重要)
応答なし成否は分からない
グループ鍵で暗号化CASE セッション不要

照明の一斉制御でグループが必要な理由. 10 個の電球に個別に Off を送ると、最初と最後で数百 ms のずれが出て、 バラバラに消えるのが目に見える。 グループのマルチキャストなら 1 パケットで同時に届く。

Binding(07 章)と組み合わせれば、 壁スイッチが直接グループに送る——ハブなしで一斉制御ができる。

8. ステータスコード

主なものを挙げる。エラーの意味を知っていると、デバッグが劇的に速くなる。

コード名前意味よくある原因
0x00SUCCESS成功—
0x01FAILURE一般的な失敗実装側の内部エラー
0x7EUNSUPPORTED_ACCESS権限不足ACL の設定漏れ(14 章)
0x7FUNSUPPORTED_ENDPOINTその Endpoint がないEndpoint 番号の誤り
0x80INVALID_ACTION要求の形式が不正パスやエンコードの誤り
0x81UNSUPPORTED_COMMANDそのコマンドがない未実装
0x85INVALID_COMMAND引数が不正値域外の引数
0x86UNSUPPORTED_ATTRIBUTEその属性がない未実装 or ID の誤り
0x87CONSTRAINT_ERROR値が制約に反する値域外(例: Level に 255)
0x88UNSUPPORTED_WRITE読み取り専用OnOff を書こうとした
0x89RESOURCE_EXHAUSTEDリソース不足サブスクリプション数の上限
0x8BNOT_FOUND見つからない—
0x8DINVALID_DATA_TYPE型が違うTLV の型の誤り
0x8FUNSUPPORTED_READ書き込み専用—
0x92DATA_VERSION_MISMATCH版が古い他者が先に変更した
0x94TIMEOUTタイムアウト—
0x9CBUSY処理中後で再試行
0xC3UNSUPPORTED_CLUSTERそのクラスタがない未実装
0xC6NEEDS_TIMED_INTERACTIONTimed 必須Timed Request を先行させる
0xC7UNSUPPORTED_EVENTそのイベントがない—
0xC8PATHS_EXHAUSTEDパス数の上限一度に要求しすぎ
0xCAFAILSAFE_REQUIREDFail-Safe が必要コミッショニング手順の誤り

0x7E (UNSUPPORTED_ACCESS) は最頻出のエラーである. 「コミッショニングは成功したのに、属性が読めない」—— ほぼ確実に ACL の問題である(14 章)。 エラーコードを見ずに「通信できない」と言うと、原因究明が何倍も遅くなる。

9. Fail-Safe — コミッショニング中の巻き戻し

コミッショニング中に失敗すると、機器が中途半端な状態で残る危険がある。

General Commissioning クラスタの Fail-Safe がこれを防ぐ。

ArmFailSafe(timeout)            ← タイマーを開始
   ↓
… 証明書の追加、ネットワーク設定など …
   ↓
CommissioningComplete           ← 成功: 変更を確定
   or
タイムアウト                      ← 失敗: すべて巻き戻す
巻き戻される対象内容
追加された NOC / Trusted Root削除
ネットワーク設定以前の状態に
未完了の Fabric 追加破棄

これがないと、失敗した機器がゴミの設定を抱えたまま残る. ユーザーは「もう一度セットアップ」しようとして、 Fabric の枠を使い切った機器に出会うことになる。

実装側の注意: Fail-Safe の巻き戻しを正しく実装するのは意外に難しい。 「どこまで進んだか」を追跡し、正確に元へ戻す必要がある。 認証テストでは、わざと途中で切断して巻き戻しを検証する。

10. 実装上の制約

項目考慮点
同時 Exchange 数機器のメモリで決まる。超えたら BUSY
1 要求あたりのパス数上限を超えたら PATHS_EXHAUSTED
応答サイズMTU を超えたら chunking
サブスクリプション数CapabilityMinima で公開(10 章)
処理時間長い処理はコマンドを即座に受理し、非同期で実行する

「コマンドの処理に 5 秒かかる」場合の設計. ハンドラの中で 5 秒ブロックしてはいけない。 すぐに応答を返し、実際の動作は非同期で行い、 完了したら属性を更新する(購読者に通知が飛ぶ)。

ブロックするとスタック全体が止まり、他の通信が滞る。 カーテンの開閉やドアロックのように時間のかかる機器では必須の設計である。

11. まとめ