Google Thread Credentials API による Thread ネットワークの共有

1. 始める前に

Thread ボーダー ルーター(TBR)Codelab では、Raspberry Pi をベースとした Thread ボーダー ルーターの構築方法を紹介しています。この Codelab では、

  • Thread ネットワークと Wi-Fi/イーサネット ネットワーク間の双方向 IP 接続を確立します。
  • mDNS(Wi-Fi/イーサネット リンク上)と SRP(Thread ネットワーク上)による双方向のサービス ディスカバリを提供します。

この Codelab は、前の Codelab を基にして、独自のボーダー ルーターとアプリが Google API と通信して単一の Thread ネットワークを作成する方法を取り上げます。Thread 認証情報の収束は、ネットワークの堅牢性を高め、Thread に依存するアプリケーションとのユーザー操作を簡素化できるため、重要です。

前提条件

  • OTBR Codelab を完了する
  • Linux、Android/Kotlin、Thread ネットワーキングに関する基本的な知識

学習内容

  • Thread Sharing API を使用して認証情報セットを取得および設定する方法
  • Google のネットワークと同じ認証情報を使用して独自の OpenThread ボーダー ルーターを設定する方法

必要なもの

  • Raspberry Pi 4 ボード、または Open Thread Border Router(OTBR)が動作する別の Linux ベースのボード
  • 無線コプロセッサ(RCP)として IEEE 802.15.4 接続を提供するボード。OpenThread GitHub ページで、さまざまな SoC ベンダーのリポジトリと手順のリストを確認できます。

2. HTTP サービスの設定

最初に必要な構成要素は、アクティブな認証情報を読み取り、保留中の認証情報を OTBR に書き込むためのインターフェースです。TBR を構築する際は、ここ 2 つの例で示すように、独自のメカニズムを使用します。1 つ目のオプションは DBUS を介して OTBR エージェントとローカルでやり取りする方法を示し、2 つ目のオプションは OTBR 上に構築できる REST API を利用する方法を示します。

どちらの方法も安全ではないため、本番環境でそのまま使用するべきではありません。ただし、ベンダーは、本番環境で使用するために、いずれかの方法に基づいて暗号化を構築できます。また、独自のモニター サービスを拡張して、ループバック HTTP または本質的にローカルの DBUS 呼び出しを発行することもできます。

オプション 1: Python スクリプトで DBUS と HTTP API を使用する

91e5fdeed83e9354.png

このステップでは、2 つのエンドポイントを公開して認証情報の読み取りと設定を行い、最終的に DBUS コマンドを呼び出すベアボーン HTTP サービスを作成します。

OTBR として機能する RPi に、Python 3 の依存関係をインストールします。

$ pip install dbus-python shlex json

次のようにスクリプトを実行します。

$  sudo python credentials_server.py 8081
serving at port 8081

このサンプルは、ポート 8081 で HTTP サーバーをセットアップし、Thread 認証情報を取得するための GET リクエストまたは Thread 認証情報を設定する POST リクエストをルートパスでリッスンします。ペイロードは常に、TLV を含む JSON 構造です。

次の PUT リクエストは、/node/dataset/pending パスを介して新しい保留中のスレッド認証情報を OTBR に設定します。この場合、保留中の認証情報は 10 秒で適用されます。

PUT /node/dataset/pending
Host: <IP>:8081
ContentType: "application/json"
acceptMimeType: "application/json"
...
{
        "ActiveDataset": "<TLV encoded new Thread Dataset>"
"PendingTimestamp": {
        "Seconds": <Unix timestamp in seconds>,
        "Ticks": 0,
        "Authoritative": false
},
"Delay": 10000 // in milliseconds
}

/node/dataset/active に対する GET リクエストは、現在アクティブな認証情報を取得します。

GET /node/dataset/active
Host: <IP>:8081
ContentType = "application/json"
acceptMimeType = "text/plain"
...
<TLV encoded Thread Dataset>

このスクリプトは、バスパス io.openthread.BorderRouter.wpan0、オブジェクト パス /io/openthread/BorderRouter/wpan0 に対して DBUS R/W コマンドを呼び出します。

# D-BUS interface
def call_dbus_method(interface, method_name, *arguments):
    bus = dbus.SystemBus()
    obj = bus.get_object('io.openthread.BorderRouter.wpan0', '/io/openthread/BorderRouter/wpan0')
    iface = dbus.Interface(obj, interface)
    method = getattr(iface, method_name)
    res = method(*arguments)
    return res

def get_dbus_property(property_name):
    return call_dbus_method('org.freedesktop.DBus.Properties', 'Get', 'io.openthread.BorderRouter',
                                 property_name)

def set_dbus_property(property_name, property_value):
    return call_dbus_method('org.freedesktop.DBus.Properties', 'Set', 'io.openthread.BorderRouter',
                                 property_name, property_value)                               

DBUS を使用すると、その機能のイントロスペクションが可能になります。方法は次のとおりです。

$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \
        --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \
        org.freedesktop.DBus.Introspectable.Introspect

サポートされている機能については、こちらでご確認いただけます。

オプション 2: OTBR エージェントのネイティブ HTTP Rest API

c748ca5151b6cacb.png

OpenThread ボーダー ルーターはデフォルトで REST_API=1 フラグでビルドされ、REST API が有効になります。前の Codelab のビルドで REST API が有効になっていない場合は、このフラグを使用して RPi で OTBR をビルドします。

$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup

OTBR エージェントは、次のコマンドを実行して再起動できます。

$ sudo systemctl restart otbr-agent.service

エージェントは、ポート 8081 で HTTP サーバーを起動します。このサーバーにより、ユーザーまたはモニター プログラムが OTBR(こちらに記載)の多くのタスクを実行できます。ブラウザ(curl または wget)を使用して内容を調べることができます。サポートされている多くのパスの中には、/node/dataset/activeGET 動詞、/node/dataset/pendingPUT 動詞がある上記のユースケースがあります。

3. Android での認証情報フレームワークを設定する

優先するクルデンシャル

Android 版 Google Play 開発者サービスは、ネットワーク内のすべての TBR の認証情報の登録を許可し、想定しています。それぞれがボーダー ルーター エージェント ID(BAID)で識別されます。このタスクを行うには、ThreadNetworkClient インターフェースの addCredentials() メソッドを使用します。Google Play 開発者サービスのストレージに追加される最初の TBR によって、このモバイル デバイスの優先認証情報が決定されます。

Thread ネットワーク認証情報のセットを BAID に追加するアプリが、認証情報の所有者となり、認証情報にアクセスするための完全な権限を持ちます。他のアプリによって追加された認証情報にアクセスしようとすると、PERMISSION_DENIED エラーが発生します。ただし、希望する認証情報は、ユーザーの同意があればどのアプリでも常に使用できます。Thread ボーダー ルーター ネットワークを更新する際は、Google Play 開発者サービスに保存されている認証情報を最新の状態に保つことをおすすめします。この情報は現在は使用されていませんが、今後、改善されたジャーニーを提供する可能性があります。

最初の TBR が後で除外されても、優先認証情報は Android デバイスで保持されます。設定されると、Thread 認証情報を管理する他のアプリは、getPreferredCredentials() 呼び出しから認証情報を取得できます。

Google TBR Sync

Android デバイスは Google TBR と自動的に同期されます。Android に認証情報が存在しない場合、デバイスはネットワーク内の Google TBR から認証情報を抽出し、その認証情報が優先認証情報になります。TBR と Android デバイスの同期は、TBR が 1 人のユーザーとペア設定されている場合、または同じスマートホーム内の 2 人のユーザーとペア設定されている場合にのみ行われます(ストラクチャ)。

このプロセスは、別の Google ユーザーが Android 版 GHA または iOS 版 GHA を使用しており、同じストラクチャにいる場合にも行われます。iOS 版 GHA では、優先する認証情報が存在しない場合は、優先する認証情報が iOS ストレージに設定されます。

異なる優先認証情報セットを持つ 2 台の Android デバイス(または Android と iGHA)が同じネットワーク内に存在する場合、最初に TBR を設定したデバイスが優先されます。

サードパーティの TBR オンボーディング

現在、認証情報のストレージの範囲は、ユーザーのスマートホームの影響を受けません(ストラクチャ)。各 Android デバイスには BAID ストレージがありますが、ネットワークに Google TBR が発生すると、Google Home アプリ(iOS)を実行している他の Android デバイスおよび iOS デバイスがその TBR と同期し、スマートフォンのストレージにローカル認証情報を設定しようとします。

新しい OOB TBR でネットワークを作成する前に、優先ネットワークが Android のストレージにすでに存在するかどうかを確認することが重要です。

  • 優先ネットワークが存在する場合、ベンダーはそれを使用する必要があります。これにより、Thread デバイスは、可能であれば単一の Thread ネットワークに接続できます。
  • 優先ネットワークが存在しない場合は、新しい認証情報セットを作成し、Google Play 開発者サービスで TBR に割り当てます。Android はこれらの認証情報を、Google ベースのすべての TBR で設定された標準認証情報として使用し、他のベンダーはデバイスを追加することでメッシュのリーチと堅牢性を強化できるようになります。

cd8bc726f67b1fa1.png

4. Android アプリのクローンを作成して変更する

Thread API を実行できる主な呼び出しを紹介する Android アプリを作成しました。これらのパターンはアプリで使用できます。この Codelab では、Matter 用の Google Home サンプルアプリの GitHub のクローンを作成します。

ここに示されているソースコードはすべて、すでにサンプルアプリでコーディングされています。必要に応じて変更できますが、アプリのクローンを作成するか、ビルド済みのバイナリを実行するだけで、機能を検査できます。

  1. 次のコマンドを使用してクローンを作成します。
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Android Studio をダウンロードして開きます。
  2. [ファイル] > [開く] をクリックし、クローン作成したリポジトリを指定します。
  3. Android スマートフォンでデベロッパー モードを有効にします
  4. USB ケーブルでパソコンに接続します。
  5. Android Studio から <Cmd+R>(OS X)または <Ctrl+R>(Win、Linux)でアプリを実行します。
  6. ホイール -> [Developer Utilities] -> [Thread Network] に移動します。
  7. さまざまなオプションを試します。以降のセクションでは、各ボタンで実行されるコードについて詳しく説明します。

優先する認証情報はありますか?

TBR メーカーが Google に最初に確認すべき質問は、優先する認証情報セットがデバイスにすでに存在するかどうかを確認することです。これがフローの出発点になります。以下のコードは、GPS に認証情報の存在を問い合わせます。認証情報が共有されないため、ユーザーの同意を求めることはありません。

/**
* Prompts whether credentials exist in storage or not. Consent from user is not necessary
*/

fun doGPSPreferredCredsExist(activity: FragmentActivity) {
 try {
   // Uses the ThreadNetwork interface for the preferred credentials, adding
   // a listener that will receive an intentSenderResult. If that is NULL, 
   // preferred credentials don't exist. If that isn't NULL, they exist.
   // In this case we'll not use it.

   ThreadNetwork.getClient(activity).preferredCredentials.addOnSuccessListener { intentSenderResult ->
     intentSenderResult.intentSender?.let { intentSender ->
       ToastTimber.d("threadClient: preferred credentials exist", activity)
       // don't post the intent on `threadClientIntentSender` as we do when
       // we really want to know which are the credentials. That will prompt a
       // user consent. In this case we just want to know whether they exist
     } ?: ToastTimber.d(
       "threadClient: no preferred credentials found, or no thread module found", activity
     )
   }.addOnFailureListener { e: Exception ->
     Timber.d("ERROR: [${e}]")
   }
 } catch (e: Exception) {
   ToastTimber.e("Error $e", activity)
 }
}

GPS 優先認証情報の取得

認証情報が存在する場合は、それを読み取ります。前のコードとの唯一の違いは、intentSenderResult を受け取った後、センダーからの結果を使用してインテントをビルドして起動する点です。

このコードでは、組織/アーキテクチャの目的で MutableLiveData<IntentSender?> を使用します。元のコードは ViewModel(ThreadViewModel.kt)にあり、インテント オブザーバーはアクティビティ フラグメント(ThreadFragment.kt)にあるためです。したがって、intentSenderResult がライブデータに送信されたら、このオブザーバーの内容を実行します。

viewModel.threadClientIntentSender.observe(viewLifecycleOwner) { sender ->
 Timber.d(
   "threadClient: intent observe is called with [${intentSenderToString(sender)}]"
 )
 if (sender != null) {
   Timber.d("threadClient: Launch GPS activity to get ThreadClient")
   threadClientLauncher.launch(IntentSenderRequest.Builder(sender).build())
   viewModel.consumeThreadClientIntentSender()
 }
}

これにより、認証情報の共有に関するユーザーの同意がトリガーされ、承認されると、以下を介してコンテンツが返されます。

threadClientLauncher =
 registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
   if (result.resultCode == RESULT_OK) {
     val threadNetworkCredentials =
       ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
     viewModel.threadPreferredCredentialsOperationalDataset.postValue(
       threadNetworkCredentials
     )
   } else {
     val error = "User denied request."
     Timber.d(error)
     updateThreadInfo(null, "")
   }
 }

認証情報を MutableLiveData<ThreadNetworkCredentials?> に投稿する方法については、下記をご覧ください。

GPS 認証情報の設定

TBR は、それらが存在するかどうかにかかわらず、Google Play 開発者サービスに登録する必要があります。TBR の Border Agent ID に関連付けられた認証情報を読み取ることができるのはアプリのみですが、TBR が最初に登録された場合は、これらの認証情報が優先認証情報セットにコピーされます。この情報は、ユーザーが許可している限り、スマートフォンのすべてのアプリからアクセスできます。

/**
* Last step in setting the GPS thread credentials of a TBR
*/
private fun associateGPSThreadCredentialsToThreadBorderRouterAgent(
 credentials: ThreadNetworkCredentials?,
 activity: FragmentActivity,
 threadBorderAgent: ThreadBorderAgent,
) {
 credentials?.let {
   ThreadNetwork.getClient(activity).addCredentials(threadBorderAgent, credentials)
     .addOnSuccessListener {
       ToastTimber.d("threadClient: Credentials added", activity)
     }.addOnFailureListener { e: Exception ->
       ToastTimber.e("threadClient: Error adding the new credentials: $e", activity)
     }
 }
}

TBR プロダクトに認証情報を設定する

この部分は各ベンダーに固有のものであり、この Codelab では、DBUS+Python の HTTP REST サーバー、または OTBR のネイティブ HTTP REST サーバーを介して実装します。

/**
* Creates credentials in the format used by the OTBR HTTP server. See its documentation in
* https://github.com/openthread/ot-br-posix/blob/main/src/rest/openapi.yaml#L215
*/
fun createJsonCredentialsObject(newCredentials: ThreadNetworkCredentials): JSONObject {
 val jsonTimestamp = JSONObject()
 jsonTimestamp.put("Seconds", System.currentTimeMillis() / 1000)
 jsonTimestamp.put("Ticks", 0)
 jsonTimestamp.put("Authoritative", false)

 val jsonQuery = JSONObject()
 jsonQuery.put(
   "ActiveDataset",
   BaseEncoding.base16().encode(newCredentials.activeOperationalDataset)
 )
 jsonQuery.put("PendingTimestamp", jsonTimestamp)
 // delay of committing the pending set into active set: 10000ms
 jsonQuery.put("Delay", 10000)

 Timber.d(jsonQuery.toString())

 return jsonQuery
}

//(...)

var response = OtbrHttpClient.createJsonHttpRequest(
 URL("http://$ipAddress:$otbrPort$otbrDatasetPendingEndpoint"),
 activity,
 OtbrHttpClient.Verbs.PUT,
 jsonQuery.toString()
)

TBR プロダクトから認証情報を取得する

前述のように、GET HTTP Verb を使用して TBR から認証情報を取得します。Python スクリプトのサンプルをご覧ください。

ビルドとインポート

Android アプリを作成するときは、Google Play 開発者サービスの Thread モジュールをサポートするために、マニフェスト、ビルド、インポートに変更を加える必要があります。次の 3 つのスニペットは、追加された機能のほとんどを要約したものです。

なお、サンプルアプリは主に Matter のコミッショニング用に作成されています。そのため、マニフェスト ファイルと Gradle ファイルは、スレッド認証情報を使用するだけの場合に必要な追加よりも複雑になります。

マニフェストの変更

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    (...)
    <!-- usesCleartextTraffic needed for OTBR local unencrypted communication -->
    <!-- Not needed for Thread Module, only used for HTTP -->
    <uses-feature
    (...)
        android:usesCleartextTraffic="true">

    <application>
    (...)
    <!-- GPS automatically downloads scanner module when app is installed -->
    <!-- Not needed for Thread Module, only used for scanning QR Codes -->
    <meta-data
        android:name="com.google.mlkit.vision.DEPENDENCIES"
        android:value="barcode_ui"/>
    </application>
</manifest>

Build.gradle

// Thread Network
implementation 'com.google.android.gms:play-services-threadnetwork:16.0.0'
// Thread QR Code Scanning
implementation 'com.google.android.gms:play-services-code-scanner:16.0.0'
// Thread QR Code Generation
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
// Needed for using BaseEncoding class
implementation 'com.google.guava:guava:31.1-jre'

関連するインポート

// Thread Network Module
import com.google.android.gms.threadnetwork.ThreadNetworkCredentials
import com.google.android.gms.threadnetwork.ThreadBorderAgent
import com.google.android.gms.threadnetwork.ThreadNetwork

// Conversion of credentials to/fro Base16 (hex)
import com.google.common.io.BaseEncoding

// HTTP
import java.io.BufferedInputStream
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets

// Co-routines for HTTP calls
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


// JSON
import org.json.JSONObject

// Logs
import timber.log.Timber

// mDNS/SD
import android.net.nsd.NsdServiceInfo

// QR Code reader / writer
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder

5. mDNS/SD 検出

このサンプルアプリでは、mDNS/SD 検出を使用して、ネットワーク内で利用可能な Thread ボーダー ルーターとそれぞれの BAID のリストを作成します。

これは、TBR の情報を GPS 認証情報のストレージに入力する際に非常に役立ちます。ただし、この Codelab の範囲外です。Android Service Discovery ライブラリの NSDManager を使用します。完全なソースコードは、ServiceDiscovery.kt のサンプルアプリで入手できます。

6. すべてを 1 つに統合

これらの呼び出しを実装するか、サンプルアプリを使用すると、RPi OTBR を完全にオンボーディングできます。このサンプルアプリでは、次の 8 つのボタンが公開されています。

91979bf065e9673d.png

TBR のオンボーディングは、次のような順序で行います。

  1. 優先認証情報が存在するかどうかをクエリする(青色、1 行目)
  2. 回答による
  3. GPS 優先の認証情報を取得する(青、2 行目)
  4. GPS で TBR 認証情報を設定(青色の 3 行目)-> TBR を選択 -> ランダムを作成 -> ネットワーク名を入力 -> [OK] を選択します。
  5. 優先する認証情報を設定したら、[Set RPi OTBR credentials] を使用して OTBR に設定します。これにより、これらの認証情報が保留中のセットに適用されます。

サンプルアプリのデフォルトは 10 秒の遅延です。したがって、この期間を過ぎると、RPi TBR(およびネットワーク上に存在する可能性がある他のノード)の認証情報が新しいデータセットに移行されます。

7. まとめ

この Codelab では、サンプル Android アプリのクローンを作成し、Google Play 開発者サービスの Thread Storage API を使用するいくつかのコード スニペットを分析しました。これらの API を使用して、ベンダーの TBR を示す RPi TBR にオンボーディングできる共通のデータセットを用意しました。

ユーザーの TBR をすべて同じネットワークに含めると、Thread ネットワークの復元力とリーチが向上します。また、アプリが認証情報にアクセスできないために Thread デバイスをオンボーディングできないような、ユーザー ジャーニーの欠陥を防ぐこともできます。

この Codelab とサンプルアプリが、独自のアプリと Thread ボーダー ルーター製品の設計と開発に役立つことを願っています。

8. 参照

RCP コプロセッサ

DBUS