SDP(プロトコル)

SDPの必要性

  SDPはservice discovery protocolの略語です。bluetoothで言うサービスとは、大さっぱに言えば、プロファイルにて提供されるサービスです。webで検索すれば、プロファイルに関してどのようなものがあるかが分かりますので、ここでは説明を略します。ただ、ファームウェアを開発していると、もう少し、低位のレベルのものもサービスと呼んでいるように思えます。例えば、RFCOMMはプロファイルでありませんが、サービスに含まれます。

  いま、マスターがパーソナル・コンピュータ(PC)で、その周りには、いろいろなサービスを持ったbluetooth機器(スレーブ)が存在するとします。PCはスレーブがどんなサービスを提供できるのか分かりませんので、スレーブに問い合わせます。それに応答するかたちで、スレーブは自分の提供できるサービスに関する情報をマスターに転送します。このような問い合わせと応答のやり取りを規定した通信手順(プロトコル)がSDPです。スレーブがPICで、PCと接続したい場合は、SDPの理解は避けて通れません。というのは、PCはPICがどのようなサービスを持っているかを必ず問い合わせて来るからです。

ところで、PIC(マスター)とwiiリモコン(スレーブ)を接続した時には、SDPはどこにも顔を出しません。この場合、wiiリモコンがbluettoth-HIDなるサービスを持っているということは分かっており、PICのファームウェア作成時には、bluettoth-HIDのみに対応させれば良いという前提があるからです。つまり、マスターとして、スレーブの提供できるサービスを調べる必要がないから(bluettoth-HIDなるサービスであると分かっりきっているから)、SDPの出番がなかったのです。

以下、マニュアルといった時には、bluetoothのマニュアル Core v2.1 + EDR.pdf のVolume 3; Part BのService Discovery Protocol (SDP)を御覧ください。

ところで、 SDP(プロトコル)では複数バイトの整数を表すときにビッグエンディアン(big endian)方式を用います。複数バイトの整数を1バイトづつに分けるのですが、最上位バイトから最下位バイトへと並べて行く方式です。例えば、0x1234(0xは16進数表現であることを示します)の最上位バイトは0x12で、最下位バイトは0x34です。ビッグエンディアン方式では、0x12  0x34と、そのまま並べることになります。


SDPにおけるデータ型

SDPでは整数やデータ列などを示すのに、次のようなデータ型をデータの先頭に置きます。たとえば、符号なしの2バイト整数0x1234を表すのに、0x09 0x12 0x34と並べます。また、文字列  abcを表すのに、0x25 0x03 'a' 'b' 'c'と並べます(0x03は文字列の長さ)。

0x08:符号なしの1バイト整数
0x09:符号なしの2バイト整数
0x0a:符号なしの4バイト整数
0x19:2バイトのUUID  (UUIDをご覧ください)
0x25:文字列  (0x25の次の1バイトで文字列長を表します)
0x28:ブーリアン  「1は真」と「0は偽」という2値だけをとる。
0x35:データ列*  (0x35の次の1バイトがデータ列長を表します)
0x36:データ列*  (0x36の次の2バイトがデータ列長を表します)

*)ここで、データ列と書きましたが、原語ではData element sequenceです。SDPを解読するときに、データ列を表す0x35と、それに続くデータ列の長を表す1バイト(合計2バイト)、もしくは0x36と、それに続くデータ列の長を表す2バイト(合計3バイト)を前括弧”(”と置き換えてみると良いと思います。なお、この前括弧に対する後括弧”)”は、データ列が終わったところにあると考えます。


サービス内容のデータ形式

ホストに返すサービス内容のフォーマット(形式)は、大きく2種類あって、次のように抽象的に書くと分かり易いと思います。

①  例えばbluesoleilスタックの場合:  サービス1つにつき
(サービスの内容)

②  例えばMicrosoftスタックの場合:
((サービス1の内容)(サービス2の内容)(サービス3の内容)・・・・)
      当然ながら、サービスが1つしか無いときには((サービス1の内容))となります。

前括弧”(”に着目します。前括弧に対して、0x35+1バイトもしくは0x36+2バイトが対応します。1バイトとか2バイトは、それ以下に続くサービス内容のデータ数を表します。ここで、hid_mouse4.zipを解凍して出てくる\hid_mouse4\SDPdata\bluetooth\data.hを見てください。buf[]配列の最初が、
        0x36,0x00,0xf0
となっていますので、これ以降に0x00f0バイトのサービス内容のデータが続くという意味になります。0x36,0x00,0xf0自体は”(”の意味ですからデータ数には入りません。0x0036バイトのデータの終わりで、後括弧が仮想的にあると思ってください。すると、buf[]配列は、(サービスの内容)なる形式で書かれていることが分かります。

一方、\hid_mouse4\SDPdata\microsoft\data.hを見てください。buf[]配列の最初が
        0x36,0x00,0xf3,0x36,0x00,0xf0
となっています。前括弧”(”が2つあることになります。最初の前括弧が0x36,0x00,0xf3で、これ以降に0x00f3バイトのデータがあることを示しており、次の前括弧が0x36,0x00,0xf0で、それ以降に0x00f0バイトのデータ(サービスの内容)があることを示しています。0x00f3バイトと0x00f0バイトの差は0x36,0x00,0xf0の3バイト分に相当します。0x00f3バイトと0x00f0バイトのデータの終わりで、後括弧が仮想的にあると思ってください。後括弧が"))"のように2重になっています。したがって、buf[]配列は((サービスの内容))なる形式で書かれていることが分かります。

さて、次にサービス内容のフォーマットですが、data.hを見ると、前括弧の次が
        0x09,0x00,0x00,
        0x0a,0x00,0x01,0x00,0x00,
となっています。ここで、1行目がAttribute ID(アトリビュートID)で、2行目がAttribute Value(アトリビュート値)です。アトリビュートIDとアトリビュート値との繰り返しでサービスの内容が表現されています。アトリビュートIDは符号なし2バイト整数(0x09+2バイト)で書かれていますので、SDPのデータを見ただけで、どこがアトリビュートIDかが大体分かると思います。

さて、上の例の内容を説明します。アトリビュートIDが0x09,0x00,0x00ですから、符号なし2バイト整数で0x0000。したがって、アトリビュートはServiceRecordHandleアトリビュートであることがマニュアルから分かります。サービスが幾つかある場合に、それらを区別するためにサービスごとに番号を付けるのですが、それをServiceRecordHandle(サービス・レコード・ハンドル)と言います。このアトリビュートでは、その番号を与えています。0x0a,0x00,0x01,0x00,0x00,にて、0x0aでもって、それ以後の4バイトが符号なし整数型であることを意味しますから、アトリビュート値が0x00010000であることになります。結局、1番目のサービス・レコード・ハンドルを0x00010000としますと宣言しているわけです。なお、サービス・レコード・ハンドルは、必ず0x00010000以上の値を設定しなければいけません。

もう一つ例をあげます。data.hを見ると、
        0x09,0x00,0x04,
        0x35,0x0d,,
        0x35,0x06,0x19,0x01,0x00,0x09,0x00,0x11,
        0x35,0x03,0x19,0x00,0x11,
なる部分を見つけることができます。1行目は、アトリビュートIDが0x0004で、マニュアルを参照すると、ProtocolDescriptorListアトリビュートを表していることが分かります。2行目以降に記述されているアトリビュート値は、0x35+1バイトのところを前括弧、その1バイトが示すバイト数の後に後括弧を入れると、((0x19,0x01,0x00,0x09,0x00,0x11), (0x19,0x00,0x11))となります。そして、0x19,0x09がデータ型であることを考慮に入れれば((UUID=0x0100,PSM=0x0011), (UUID=0011))、すなわち((UUID=L2CAP,PSM=0x0011), (UUID=HIDP))となります。結局、L2CAPでPSMが0x11のチャンネルを確保して、その上でHIDProfileを利用していますという意味になります。

なお、SPDに関してどのようなアトリビュートIDが必要なのかについては、RFCOMMの場合はrfcomm.pdfの421ページを、またHIDの場合はHID_SPEC_V10.pdfの82ページ以降を参照ください。また、先にも言及しましたが、UUIDについてはここを参照ください。


サービスの問い合わせ

以下、マスターがPCでスレーブがPICとします。また、SDP専用のL2CAPチャンネルも確保されているとします(PSM=0x0001で確保する。確保の仕方は、L2CAP(プロトコル)におけるチャンネルの確保を参照のこと。)。

SDP専用のL2CAPチャンネルを確保すると、PCがPICに対して、PICが提供できるサービスを問い合わせて来ます。つまり、PCからPICへ
    SDP_ServiceSearchRequest (ID:0x02)

    SDP_ServiceSearchAttributeRequest (ID:0x06)
が送られてきます。前者(例えばbluesoleilスタック)の場合、PICはサービス内容を上記①のデータ形式で返します。一方、後者(例えばMicrosoftスタック)の場合、PICはサービス内容を上記②のデータ形式で返します。

○ SDP_ServiceSearchRequest (ID:0x02)の具体例

bluesoleilの場合で、あまり使用しないと思いますので、省略します。以下のことが理解できれば、自ずから理解できるようになっているとも思いますので。

○ SDP_ServiceSearchAttributeRequest (PDU ID=0x06)の具体例

  ログファイルyts_xp_mouse.txtを例にとって説明します。このファイルのSDP_SEARCH_REQなるラベルのすぐ下の行を見てください。

        2A 20 18 00 14 00 44 00 06 00 00 00 0F 35 03 19 01 00 03 F8 35 05 0A 00 00 FF FF 00
となっています。この意味は以下の通りです。

2A 20:(Connection Handle+PB Flag+BC Flag)
18 00:ACLデータの長さを表すバイト数
14 00: L2CAPデータの長さを表すバイト数
44 00:PICのSDP用データチャンネル
06:06はSDP_ServiceSearchAttributeRequestを意味する
00 00:Transuction ID:00 00から1つづつ増える。要求を出す方が決める数値です。いくつかの要求を送信する場合には、この値を1づつ増加して行くことになります。応答する方は、必ず要求の場合と同じ数値を使います。
00 0F:以下のデータの長さを表すバイト数:こことこれ以降はすべてビッグエンディアン
35 03 19 01 00:サービス検索パターン:(UUID=0x0100)でL2CAPを表している。L2CAPに関連したサービスにどのようなものがあるかを聞いています。*1
03 F8:1度にホストが受け取れるスレーブからのサービス内容の最大バイト数を0x03F8に制限する。ホストの受信バッファが小さい場合、スレーブからそれを超えるバイト数のデータが1度に送られて来ると、データがバッファから溢れ出て、正しくデータを受信できません。そこで、ここではサービス内容を最大0x03F8バイトまでしか1度に受け取れないので、それより長い場合には分割して送ってほしいと、スレーブに頼んでいるわけです。
35 05 0A 00 00 FF FF:スレーブの持つサービスのうちAttributeID番号(後出)が、どの範囲にあるものだけを探すかを、ここで指定します。ここでは、0x0000番から0xFFFF番となっており、その範囲の(つまり、すべての)AttributeID番号に対するサービス情報が欲しいと要求しています。*2
00:初めは0x00となります。*3

*1)35 03 19 01 00:0x35 0x03で前括弧(0x03はデータ長)、0x03で表される3バイトは19 01 00であり、0x19は2バイトのUUID型を表しているので、結局データは(01 00)と書けて、(UUID=0x0100)となります。
*2)35 05 0A 00 00 FF FF:35 05は前括弧、0x05で表される5バイトは0A 00 00 FF Fであり、0x0Aは符号なしの4バイト整数型であるから、結局データは(00 00 FF FF)と書け、本要求の場合は、0x0000番から0xFFFF番までという意味になります。
*3)渡すサービス内容が長い場合、次回はサービス内容のうち、初めから何バイト目からデータ転送を開始すれば良いかという情報をサービス・データの最後にスレーブが付加しますので、ホストは、その付加データを、この部分に書き写します。ログデータのSDP_ATTR_RESP4a_RESPの前後を書き出すと、つぎのようになっています。
2A 10 18 00 00 25 13 4E 69 6E 74 65 6E 64 6F 20 52 56 4C 2D 43 4E 54 2D 30 02 00 76  (第1回目に返ってくるサービス・データの最後の部分)
SDP_ATTR_RESP4a_RESP
2A 20 1A 00 16 00 45 00 06 00 01 00 11 35 03 19 01 00 03 F8 35 05 0A 00 00 FF FF 02 00 76  (次にスレーブへ要求するSDP_ServiceSearchAttributeRequest本体)
ここで、赤いデータに着目すれば上記のことが理解できると思います。02は、それ以降に続くバイト数(00 76の2バイト:つまり0x0076)です。次回は、0x0076バイト目からデータ転送を開始するようにしてくださいとホストに伝えているのが、最初の濃い赤で示したデータ(付加データ)です。2番目の赤で示したデータ(書き写したデータ)で、0x0076番目からデータ転送を開始するようにスレーブに要求しています。なお、スレーブがホストへ送る最後のサービス・データ(濃い赤で示した付加データの部分)は必ず0x00で終わります。


サービス内容の転送

  サービスの問い合わせに対する応答です。サービスの問い合わせに対して1度にすべてのサービス内容をホストに返せれば、話は簡単なのですが、L2CAPのデータパケットの長さ制限のため、サービス内容が大ききい場合、分割してホストに返します。サービスの問い合わせ、分割された1番目のサービス内容(+付加データ)の転送、付加データをもとに分割された2番目のサービスの問い合わせ、分割された2番目のサービス内容(+付加データ)の転送、付加データをもとに分割された3番目のサービスの問い合わせ、・・・・、分割された最後のサービス内容(付加データなしで、0x00で終わる)の転送、なる一連の作業となります。ところで、サービス内容をホストに転送するためには、サービス内容をL2CAPデータ・パケット形式に変換する必要があります。 変換されたサービス内容は、例えば、以下のようになります(例えばと書いたのは、L2CAPデータ・パケットは、長さの制限を越えなければ、任意に分割できるからです)。

SDP_ServiceSearchAttributeRequestである
        2A 20 18 00 14 00 44 00 06 00 00 00 0F 35 03 19 01 00 03 F8 35 05 0A 00 00 FF FF 00
に対する1番目の応答(SDP_ServiceSearchAttributeResponse)、すなわち、1番目のサービス内容(+付加データ)ですが、それをログファイルから拾い出すと、

2A 20 1B 00 80 00 40 00 07 00 00 00 7B 00 76 36 00 F3 36 00 F0 09 00 00 0A 00 01 00 00 09 00
2A 10 1B 00 01 35 03 19 11 24 09 00 04 35 0D 35 06 19 01 00 09 00 11 35 03 19 00 11 09 00 05
2A 10 1B 00 35 03 19 10 02 09 00 06 35 09 09 65 6E 09 00 6A 09 01 00 09 00 09 35 08 35 06 19
2A 10 1B 00 11 24 09 01 00 09 00 0D 35 0F 35 0D 35 06 19 01 00 09 00 13 35 03 19 00 11 09 01
2A 10 18 00 00 25 03 79 74 73 09 02 00 09 01 00 09 02 01 09 01 11 09 02 02 02 00 76
となっています。1つづ説明して行きます。

2A 20 1B 00 80 00 40 00 07 00 00 00 7B 00 76 36 00 F3 36 00 F0 09 00 00 0A 00 01 00 00 09 00
2A 20:(Connection Handle+PB Flag+BC Flag)
1B 00:この①データ・パケットのACLデータの長さを表すバイト数
80 00: ①から⑤までの中に含まれるL2CAPデータ全体の長さを表すバイト数。②から⑤は2A 10で始まっているので、分割されて送られて来たデータであることが分かります。つまり①から⑤で1つのデータを形成しているということです。①に含まれるL2CAPデータ数は、チャンネルIDを表す40 00の後からがL2CAPデータなので、0x1b-0x04バイト(引く4バイトは80 00 40 00)、②から④までに含まれるL2CAPデータ数は、それぞれ0x1bバイト、⑤に含まれるL2CAPデータ数は0x18バイトですから、全体では(0x1b-0x04) + 3*0x1b + 0x18 = 0x80となります。
40 00:PCのSDP用データチャンネル
07:07はSDP_ServiceSearchAttributeResponseを意味する
00 00:Transuction IDでSDP_ServiceSearchAttributeRequest(要求)におけるものと同じ数値を設定します。
00 7B:これ以降のL2CAPデータ全体の長さを表すバイト数。上の0x80バイトから、40 00 07 00 00 の5バイト分を引けばよいので、0x7Bとなる。
00 76:以下のサービス・データ全体の長さを表すバイト数。上の0x7Bから00 7Bの2バイトを引いて、0x79となります。ところで、⑤の最後の02 00 76は、以前説明したように継続を意味しているバイト列(付加データ)なので、サービス・データではありません。この3バイトを0x79から引いて0x76となります。
36 00 F3 36 00 F0 09 00 00 0A 00 01 00 00 09 00:サービス・データの初めの部分です。

2A 10 1B 00 01 35 03 19 11 24 09 00 04 35 0D 35 06 19 01 00 09 00 11 35 03 19 00 11 09 00 05
2A 10:(Connection Handle+PB Flag+BC Flag) BC Flagが01なので前のパケットの続きを意味します。
1B 00:この②データ・パケットのACLデータの長さを表すバイト数
01 35 03 19 11 24 09 00 04 35 0D 35 06 19 01 00 09 00 11 35 03 19 00 11 09 00 05:前のパケットに続くサービス・データの部分です。

③と④も②と同じパターンなので、説明は省略します。

2A 10 18 00 00 25 03 79 74 73 09 02 00 09 01 00 09 02 01 09 01 11 09 02 02 02 00 76
2A 10:(Connection Handle+PB Flag+BC Flag) BC Flagが01なので前のパケットの続きを意味します。
18 00:この⑤データ・パケットのACLデータの長さを表すバイト数
00 25 03 79 74 73 09 02 00 09 01 00 09 02 01 09 01 11 09 02 02:前のパケットに続くサービス・データの部分です。
02 00 76:付加データ;2番目以降のサービス内容が、まだ続くことを表しています(続かない場合は0x00となっています)。次回、サービス・データの0x76バイト目からのデータを要求してくれるようにホストに伝えています。したがって、次回、PCがPICへ要求するSDP_ServiceSearchAttributeRequestは、以前説明したように 2A 20 1A 00 16 00 45 00 06 00 01 00 11 35 03 19 01 00 03 F8 35 05 0A 00 00 FF FF 02 00 76 となります。なお、緑色で示した数値00 01はTransuction IDですが、このように00 00から1つづつ増えていることにも留意しましょう。

サービス・データをL2CAPデータ・パケット形式に変換するのは大変です。PICのデータ・メモリにサービス・データをおいておき、ファームウェアで変換するのも良いと思いますが、PICのプログラム・メモリをあまり無駄に使いたくない場合は、PCで変換作業をしておいて、変換されたものだけをPICのデータ・メモリに書き込んでおく方法が良いでしょう。この変換プログラムは、SDP用データの配列化ソフトウェアの章にあります。


注意:SDPのマニュアルを読んでいると、サーバーとクライアントという言葉が出てきます。サービスを提供する方、すなわち、スレーブをサーバーと呼んで、サービスを受ける方、すなわち、ホストをクライアントと呼んでいます。


戻る