Embedded Test 09 · BLE ① 仕組みと状況把握 — 見えない電波を「見る」

Chapter 09

BLE ① 仕組みと状況把握 — 見えない電波を「見る」

この章のゴール.

BLE(Bluetooth Low Energy)を外からテストするために、まず受動的に状況を把握する—— どんなアドバタイズを出しているか、GATT にどんなサービスがあるか、接続がどう成立するかを、 スキャナと簡単な Python で観測できるようになること。攻撃・ファジングは次章に回す。

この章で使う既出の用語(定義は各リンク先). オラクル(01 章 8 節)、ブラックボックステスト(01 章 1 節)、仕様書(01 章 3 節)、規格(01 章 3 節)、状態遷移(02 章 1 節)、有無(04 章 5 節)

1. なぜ無線は特別に難しいか

03 章〜08 章は配線で信号に触れた。無線は線がない。電波は空間を飛び、他の機器と混ざり、再現しづらい。 だから無線のブラックボックステストは、まず「今、何が出ているのか」を可視化するところから始める。 本章で扱う BLE は、組み込み機器でいちばん多く出会う無線であり、道具(スキャナ、bleak)が整っているので学びやすい。

BLE(Bluetooth Low Energy) とは、低消費電力を狙った近距離無線の規格である。ボタン電池で年単位動くセンサやウェアラブルが典型で、生成 AI に書かせた組み込みファームでも「スマホと BLE でつなぐ」構成は非常に多い。

BLE は役割の違う層の積み重ね——本編が触るのは上二層アプリ(機器の機能)温度・警報などGATT — 何を読み書きするかサービス/キャラクタリスティックGAP — 見つける・つなぐアドバタイズ/スキャン/接続リンク層・物理層(PHY)電波そのもの(sniffer 領域)テストの主戦場スキャン/接続GAP=接続の作法、GATT=データの構造。まず GAP で見つけ、GATT で読み書きする
図9-1 BLE のプロトコルスタック。

2. BLE の二層構造 — GAP と GATT

BLE を理解する鍵は、役割の違う二つの層に分けて考えることである。

UUID(Universally Unique Identifier) とは、128 ビットの世界的にかぶらない識別番号のこと。標準サービス(心拍計など)は 16 ビットの短縮 UUID を持ち、独自機能は 128 ビットのフル UUID を使う。

GATT のデータは「サービス→キャラクタリスティック」の木GATT サーバ(機器)サービス:温度計UUID(16bit 短縮)サービス:設定UUID(128bit 独自)温度値Read / Notifyしきい値Read / Write履歴消去コマンドWrite各値に UUID(世界でかぶらない識別子)と権限(Read/Write/Notify)が付く=外から見た API 仕様書
図9-2 GATT のサービス/キャラクタリスティック階層。

3. まず聞く — アドバタイズとスキャン

機器は接続していない間、アドバタイズパケットを周期的に(例: 100 ms ごと)ブロードキャストしている。中には機器名、提供サービスの UUID、送信電力、独自の製造者データなどが入る。これをスキャンで拾えば、接続せずに多くのことが分かる。

接続せずに——アドバタイズを聞くだけで多くが分かる機器(周辺)周期的に叫ぶア) 電波スキャナ(PC/bleak)受け取って解析機器名サービス UUIDRSSI(距離目安)製造者データ(生値注意)秘密がアドバタイズに乗っていないか/MAC が固定でないかを、無接続で確かめる
図9-3 アドバタイズとスキャンで得られる情報。

道具は、スマホアプリ(nRF Connect、LightBlue)、PC の bleak(Python、クロスプラットフォーム)、より低層まで見るなら Sniffer(nRF52 の BLE sniffer + Wireshark)。まずは bleak のスキャンから。

import asyncio
from bleak import BleakScanner

async def main():
    devices = await BleakScanner.discover(timeout=5.0, return_adv=True)
    for addr, (dev, adv) in devices.items():
        print(addr, dev.name, adv.rssi, "dBm")
        print("  services:", adv.service_uuids)
        print("  mfr:", adv.manufacturer_data)   # 独自データがそのまま見える

asyncio.run(main())

4. つないで並べる — GATT の列挙

接続すると、機器が持つサービスとキャラクタリスティックの一覧(GATT テーブル)を読み出せる。これは機器の「外から見た API 仕様書」であり、テスト対象の面(attack surface / test surface)そのものである。

from bleak import BleakClient
async def enum(addr):
    async with BleakClient(addr) as c:
        for s in c.services:
            print("SERVICE", s.uuid)
            for ch in s.characteristics:
                print("  CHAR", ch.uuid, ch.properties)   # ['read','write','notify']

5. 状況把握でわかるテスト観点

接続せず・接続してすぐの受動観測だけで、こんな不具合が見える。

6. 何をオラクルにするか

7. 手を動かす

BLEアドバタイズとGATTを読む


この章のポイント