GATTはBluetooth LEにおける標準的なデータフォーマットです。今まで何度か「SNMP(Simple Network Management Protocol)のMIB(Management Information Base)のようなもの」と言及してきましたが、具体的な解説は避けてきました。今回はこのGATTについて具体的に・かつ簡単に解説してみます。

 GATTはデータフォーマットとプロトコルの定義であるATTと、ATTを用いて構築されたデータ構造であるGATTという階層構造になっています。GATT自身も「サービス」「グループ」「キャラクタリスティック」などの内部構造が階層をなしていて、必ずしもわかりやすい仕様ではありません。今回はこれをなるべく短く・簡単に解説してみます。

ATTについて

 ATT(Attribute Protocol)はデータ表現規則とアクセス手順の定義です。大抵のアーキテクチャーにおいて、データはデータ本体である「値(Value)」と、その種別を表す「型(Type)」「名前(NameまたはHandle Tag、IDなど)」で表記されます。例えばSNMPのASN.1表記であれば

02 01 01

は整数型(02)で長さ1の値1(01)を示しています。これだけだと「型」と「値」しかわからないので、SNMPではOIDという「名前」を付けて管理します。例えば

30 0D 06 08 43 06 01 02 01 02 01 00 02 01 01

です。30はASN.1における構造体(SEQUENCE)、0Dは構造体データの長さ、06はOID型、08はOIDの長さを示し、43 06 01 02 01 02 01 00はOID空間におけるiso.org.dod.internet.mgmt.mib-2.interface.ifNumber.0 という「名前」を示しています。この後に先ほどの02 01 01が連なることで、「そのシステムが備えるインターフェースの個数は1である」という情報が記されています。

 ATTも概念としては同じようなもので、全てのATTオブジェクトは「型(Type)」「ハンドル(Handle)」「値(Value)」そして「属性(Permission)」を持ちます。ただし実装はASN.1やTLVとは大きな違いがあります。

 まず、ATTオブジェクトの「ハンドル」は便宜的な識別子にすぎず、SNMPのOIDのように一意性を持つ「名前」ではありません。ハンドルの値は「それが指すデータ」とは直接何の関連も持たず、ただ単に紐づいているだけの関係です。

 一方、ATTオブジェクトの「型」はむしろSNMPにおける「名前」OIDに近いものです。ATTにおけるTypeは128bitのUUID値であり、「Type UUID=2A00-0000-1000-8000-00805F9B34FBはDevice Name」のように定義されています。「整数」「文字列」のような一般型はATTにはありません。これはATT/GATTを理解する上で紛らわしいところなので注意してください。

 なおATTの128bit Typeは長すぎるので、Bluetooth SIGが定義した標準型については通常、共通項の上位を省略した「UUID16」として表記されます。上記のDevice NameはUUID16で「2A00」と表記されます。

 ATTオブジェクトはATTプロトコルを用いて読み書きができます。しかし、ここにもSNMPとの相違があります。SNMPと異なり、ATTプロトコルには自己記述性がありません。ATTは原則として「ハンドルを指定して値を読み出す(書き込む)」プロトコルであり、そのハンドルに紐づけられている型(Type)や属性(Permission)には直接アクセスできないのです。ATT Read RequestでHandle=2とHandle=3を読みだした様子を下図に示します。

ATT Read Requestの例
ATT Read Requestの例

 ATT Read Responseでは02 03 00 00 2Aとか57 65 69 67 68 74 20 4D 65 61 73 75 72 65 6D 65 6E 74とか、「ただのデータ」しか返ってきません。それが整数なのか文字列なのか、後者は見るからに文字列っぽいですが、文字列だとしても何の名前なのかは全然わかりません。

 ATT Read Requestではハンドルに紐付いた型を読み出すことはできませんが、逆に「型に紐づいたハンドル(と値)を読み出す」機能は用意されています。これがATT Read by Type Requestで、これを用いると「指定された型に一致するATTオブジェクトの中で、指定したハンドル範囲内の一覧をまとめて読み出す」ことが可能です。例えばUUID16=0x2803の型に紐づいた値をまとめて読みだすと下記のようになります。

ATT Read by Typeの例
ATT Read by Typeの例

 しかし、UUID16=0x2803が何を意味するのかはATTの枠内では定義されていません。ここから先はGATTで定義される世界になります。