プロトコル仕様
bleRPCはBLE GATTの上に階層化されたプロトコルスタックを使用します。コマンド層はProtocol BuffersペイロードをRPCメタデータでカプセル化し、オプションの暗号化層はシリアライズされたコマンドを暗号化し、コンテナ層はMTUサイズのパケットに分割してBLE伝送します。
接続セットアップシーケンス
RPC呼び出しを行う前に、セントラルはペリフェラルとの短いセットアップシーケンスを実行します。これは全体のタイムラインであり、各ステップは以下のセクション(および鍵交換については暗号化)で詳しく説明します。
コマンド層
コマンド層は、Protocol BuffersでエンコードされたデータをRPCメタデータとともに CommandPacket にまとめます。コマンドは名前(ASCII文字列)で識別され、柔軟で人間が読みやすいディスパッチを可能にします。
コマンドフォーマット
| フィールド | ビット数 | 説明 |
|---|---|---|
type | 1 | 0 = リクエスト、1 = レスポンス |
reserved | 7 | ゼロ埋め |
cmd_name_len | 8 | コマンド名の長さ(バイト) |
cmd_name | 可変 | ASCIIコマンド名(例: echo、flash_read) |
data_len | 16 | protobufデータの長さ(バイト、リトルエンディアン) |
data | 可変 | Protocol Buffersでエンコードされたペイロード |
コマンド検出
コードジェネレーターは、次の2つの方法のいずれかで.protoファイルからコマンドを導出します:
- サービス定義(推奨) — ファイルに
service { ... }ブロックが宣言されている場合、各rpcメソッドがコマンドになります。コマンド名はメソッド名をsnake_caseにしたもので、そのリクエスト/レスポンス型はメソッドシグネチャから取得されます - 命名規則(フォールバック) — サービスが定義されていない場合、対応する
*Request/*Responseメッセージのペアから、プレフィックスをsnake_caseに変換した名前のコマンドが生成されます:
| メッセージペア | コマンド名 |
|---|---|
EchoRequest + EchoResponse | echo |
FlashReadRequest + FlashReadResponse | flash_read |
DataWriteRequest + DataWriteResponse | data_write |
ストリーミングコマンドは別途登録されます — サービスブロック内のストリーミングrpcメソッドを介するか、proto/streaming.txtにリストアップする(例: counter_stream p2c、counter_upload c2p)かのいずれかです。これらは、単純なリクエスト/レスポンスメソッドの代わりに、専用のストリーミングAPI(ストリーミングを参照)を生成します。
暗号化層
暗号化はオプションです。セッションが有効な場合(暗号化を参照)、シリアライズされたコマンドは、コンテナ層に渡される前にAES-128-GCMで暗号化されます。暗号化されたペイロードは[counter:4][ciphertext:N][tag:16]であり、20バイトのオーバーヘッド(4バイトのカウンター + 16バイトのGCMタグ)を持ちます。これは、プレーンテキストのコマンドとまったく同じようにコンテナに分割されます。
カウンターは、AES-GCMのnonceとリプレイ防止の両方を兼ねます。ハンドシェイク、鍵導出、nonceの構成、リプレイ規則については暗号化を参照してください。
コンテナ層
コンテナ層は、大きなペイロードをMTUサイズのパケットに分割し、受信側で再組み立てします。各コンテナは追跡と順序付けのためにトランザクションIDとシーケンス番号を持ちます。
コンテナフォーマット
すべてのマルチバイトフィールドはリトルエンディアンです。
最初のコンテナ (type=0b00)
トランザクションの最初のコンテナには、ペイロードの合計長が含まれます:
| フィールド | ビット数 | 説明 |
|---|---|---|
transaction_id | 8 | このリクエスト/レスポンスペアの一意のID。トランザクションごとにリセットされます。 |
sequence_number | 8 | コンテナ送信ごとにインクリメント。トランザクションごとにリセットされます。 |
type | 2 | 0b00 = 最初 |
control_cmd | 4 | 0x0(データコンテナでは未使用) |
reserved | 2 | ゼロ埋め |
total_length | 16 | 全コンテナにわたるペイロードの合計サイズ(バイト、リトルエンディアン) |
payload_len | 8 | このコンテナ内のペイロードサイズ(バイト) |
payload | 可変 | コマンドデータのフラグメント |
ヘッダーサイズ: 6バイト(transaction_id + sequence_number + flags + total_length + payload_len)
後続コンテナ (type=0b01)
継続コンテナはtotal_lengthを省略します:
ヘッダーサイズ: 4バイト(total_lengthフィールドなし)
バイト2のビットフィールド詳細
各コンテナの3バイト目は、タイプ、コントロールコマンド、および予約ビットをエンコードします:
type:00= 最初、01= 後続、11= 制御control_cmd:0x0–0x6(type=11の場合のみ有効)reserved: ゼロ埋め
コンテナ分割の例
MTU=247(ATTオーバーヘッド後の利用可能サイズ244)で500バイトのコマンドペイロードの場合:
| コンテナ | タイプ | ヘッダー | ペイロード | 合計 |
|---|---|---|---|---|
| 1 | FIRST | 6バイト | 238バイト | 244バイト |
| 2 | SUBSEQUENT | 4バイト | 240バイト | 244バイト |
| 3 | SUBSEQUENT | 4バイト | 22バイト | 26バイト |
ペイロード合計: 500バイト(238 + 240 + 22)
最大ペイロード
単一トランザクションは2つの制限で制約されます。sequence_numberは8ビットであるため、約255コンテナに制限されます。一般的なMTU 247(後続コンテナあたり240バイト)の場合、これは実用的な最大値として約60 KBになります。また、16ビットのtotal_lengthフィールドにより、絶対的な上限として65,535バイトが課されます。より大きな転送には、複数のリクエスト/レスポンスサイクルを使用してください。
BLEトランスポート
すべての通信は単一のGATT Characteristicを使用します:
- リクエスト: CentralがWrite Without Responseで書き込み
- レスポンス: PeripheralがNotifyで送信
MTUはBLE ATT MTU Exchangeプロシージャにより自動的に合意形成されます。アプリケーションは合意形成されたMTU(ATTオーバーヘッドの3バイトを引いた値)を使用して、コンテナペイロードの最大サイズを決定
デフォルトタイムアウトは100msで、タイムアウト制御コンテナで設定可能
リクエスト/レスポンスフロー
完全なRPC呼び出しは、コマンド層でのエンコード、コンテナ層での分割、BLE伝送、そして受信側での逆処理で構成されます:
制御コンテナ
制御コンテナ(type=0b11)は、アプリケーションデータの代わりにプロトコルレベルのコマンドを送信します。
タイムアウト共有 (control_cmd=0x1)
ペリフェラルが処理タイムアウトをセントラルに伝えることができます。
リクエスト (Central → Peripheral)
| フィールド | 値 |
|---|---|
| type | 0b11(制御) |
| control_cmd | 0x1 |
| payload_len | 0x00 |
レスポンス (Peripheral → Central)
| フィールド | 値 |
|---|---|
| type | 0b11(制御) |
| control_cmd | 0x1 |
| payload_len | 0x02 |
| payload | timeout_ms(16ビットLE、ミリ秒) |
機能共有 (control_cmd=0x4)
ペリフェラルがバッファ制約と機能フラグをセントラルに伝えることができます。
リクエスト (Central → Peripheral)
| フィールド | 値 |
|---|---|
| type | 0b11(制御) |
| control_cmd | 0x4 |
| payload_len | 0x06 |
| payload[0:2] | max_request_payload_size(16ビットLE、通常0) |
| payload[2:4] | max_response_payload_size(16ビットLE、通常0) |
| payload[4:6] | flags(16ビットLE、通常0) |
レスポンス (Peripheral → Central)
| フィールド | 値 |
|---|---|
| type | 0b11(制御) |
| control_cmd | 0x4 |
| payload_len | 0x06 |
| payload[0:2] | max_request_payload_size(16ビットLE) |
| payload[2:4] | max_response_payload_size(16ビットLE) |
| payload[4:6] | flags(16ビットLE) — ビット0: 暗号化サポート |
機能ペイロードは常に6バイトです。オプション機能を持たないペリフェラルは、単にflags = 0x0000を報告します。
ストリーム終了 (control_cmd=0x2, 0x3)
ストリーミングシーケンスの終了を通知します。どちら側からでもストリームを終了できます。
| control_cmd | 方向 | 説明 |
|---|---|---|
0x2 | Central → Peripheral | Centralがアップロードストリームを終了 |
0x3 | Peripheral → Central | Peripheralがダウンロードストリームを終了 |
どちらもpayload_len=0x00(ペイロードなし)
エラー通知 (control_cmd=0x5)
エラー発生時(例: レスポンスがバッファ制限を超えた場合)にペリフェラルから送信されます。
| フィールド | 値 |
|---|---|
| type | 0b11(制御) |
| control_cmd | 0x5 |
| payload_len | 0x01 |
| payload[0] | エラーコード |
エラーコード:
0x01— RESPONSE_TOO_LARGE: レスポンスペイロードがmax_response_payload_sizeを超過0x02— BUSY: ペリフェラルに空きのリクエストスロットがない(別のトランザクションを処理中)。セントラルは再試行する必要がある
鍵交換 (control_cmd=0x6)
暗号化セッションを確立するための鍵交換ハンドシェイクペイロードを送信します。ペリフェラルが対応機能のflagsフィールドで暗号化サポートを通知した場合に使用されます。
| フィールド | 値 |
|---|---|
| type | 0b11(制御) |
| control_cmd | 0x6 |
| payload_len | 可変 |
| payload | 鍵交換ステップデータ(暗号化を参照) |
ハンドシェイクを完了するために4つのコンテナ(ステップ1〜4)が交換されます。完了後、すべての後続データコンテナは暗号化されたペイロードを送信します。
ストリーミング
bleRPCは、単純なリクエスト/レスポンスに加えて、2つのストリーミングパターンをサポートしています:
Peripheral → Central ストリーム(サーバーストリーミング)
1つのリクエストが複数のレスポンスをトリガーします。ストリームはSTREAM_END_P2C制御コンテナで終了します。
Central → Peripheral ストリーム(クライアントストリーミング)
複数のリクエストが送信され、STREAM_END_C2P制御コンテナが続きます。ペリフェラルは最終メッセージで応答します。