Google Thread Credentials API와 스레드 네트워크 공유

1. 시작하기 전에

스레드 보더 라우터 (TBR) Codelab에서는 Raspberry Pi를 기반으로 스레드 보더 라우터를 빌드하는 방법을 보여줍니다. 이 Codelab에서는

  • 스레드와 Wi-Fi/이더넷 네트워크 간에 양방향 IP 연결을 설정합니다.
  • mDNS (Wi-Fi/이더넷 링크) 및 SRP (Thread 네트워크)를 통해 양방향 서비스 검색을 제공합니다.

이 Codelab에서는 이전 Codelab을 기반으로 자체 경계 라우터와 앱이 Google API와 상호작용하여 단일 스레드 네트워크를 만드는 방법을 설명합니다. 스레드 사용자 인증 정보를 통합하는 것은 네트워크 견고성을 높이고 스레드를 사용하는 애플리케이션과의 사용자 상호작용을 간소화하므로 중요합니다.

기본 요건

  • OTBR Codelab을 완료합니다.
  • Linux, Android/Kotlin, 스레드 네트워킹에 관한 기본 지식

학습할 내용

  • Thread Sharing API를 사용하여 사용자 인증 정보 세트를 가져오고 설정하는 방법
  • Google 네트워크와 동일한 사용자 인증 정보로 자체 OpenThread 보더 라우터를 설정하는 방법

필요한 항목

  • Open Thread Border Router (OTBR)를 실행하는 Raspberry Pi 4 보드 또는 다른 Linux 기반 보드
  • 라디오 코프로세서 (RCP)로 IEEE 802.15.4 연결을 제공하는 보드입니다. OpenThread GitHub 페이지에서 다양한 SoC 공급업체의 저장소 목록과 안내를 확인하세요.

2. HTTP 서비스 설정

필요한 첫 번째 구성요소는 활성 사용자 인증 정보를 읽고 대기 중인 사용자 인증 정보를 OTBR에 쓸 수 있는 인터페이스입니다. TBR을 빌드할 때는 다음 두 가지 예와 같이 자체 독점 메커니즘을 사용하세요. 첫 번째 옵션은 DBUS를 통해 OTBR 에이전트와 로컬에서 상호작용하는 방법을 보여주고, 두 번째 옵션은 OTBR에서 빌드할 수 있는 Rest API를 활용합니다.

두 방법 모두 안전하지 않으며 프로덕션 환경에서는 그대로 사용해서는 안 됩니다. 그러나 공급업체는 프로덕션 환경에서 사용할 수 있도록 두 메서드 중 하나를 중심으로 암호화를 빌드하거나 자체 모니터링 서비스를 확장하여 루프백 HTTP 또는 본질적으로 로컬 DBUS 호출을 실행할 수 있습니다.

옵션 1: Python 스크립트의 DBUS 및 HTTP API

91e5fdeed83e9354.png

이 단계에서는 사용자 인증 정보를 읽고 설정하기 위해 두 엔드포인트를 노출하고 궁극적으로 DBUS 명령어를 호출하는 간단한 HTTP 서비스를 만듭니다.

OTBR로 사용할 RPi에서 Python 3 종속 항목을 설치합니다.

$ pip install dbus-python shlex json

다음과 같이 스크립트를 실행합니다.

$  sudo python credentials_server.py 8081
serving at port 8081

이 샘플은 포트 8081에 HTTP 서버를 설정하고 스레드 사용자 인증 정보를 가져오는 GET 요청 또는 스레드 사용자 인증 정보를 설정하는 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 읽기/쓰기 명령어를 호출합니다.

# 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 Agent 네이티브 HTTP Rest API

c748ca5151b6cacb.png

OpenThread Border Router는 기본적으로 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/active에는 GET 동사가, /node/dataset/pending에는 PUT 동사가 사용됩니다.

3. Android에서 인증 프레임워크 설정

기본 사용자 인증 정보

Android의 Google Play 서비스는 네트워크의 모든 TBR에 대한 사용자 인증 정보를 등록하도록 허용하고 기대합니다. 각 라우터는 경계 라우터 에이전트 ID (BAID)로 식별됩니다. 이 작업을 실행하려면 ThreadNetworkClient 인터페이스의 addCredentials() 메서드를 사용합니다. Google Play 서비스 저장소에 추가된 첫 번째 TBR은 이 휴대기기의 기본 사용자 인증 정보를 결정합니다.

BAID에 Thread 네트워크 사용자 인증 정보 세트를 추가하는 앱이 사용자 인증 정보의 소유자가 되며 사용자 인증 정보에 액세스할 수 있는 전체 권한을 갖습니다. 다른 앱에서 추가한 사용자 인증 정보에 액세스하려고 하면 PERMISSION_DENIED 오류가 발생합니다. 그러나 사용자의 동의가 있으면 모든 앱에서 기본 사용자 인증 정보를 항상 사용할 수 있습니다. Thread Border Router 네트워크가 업데이트될 때 Google Play 서비스에 저장된 사용자 인증 정보를 최신 상태로 유지하는 것이 좋습니다. 현재는 이 정보가 사용되지 않지만 향후 개선된 여정을 제공할 수 있습니다.

나중에 첫 번째 TBR이 제외되더라도 선호 사용자 인증 정보는 Android 기기에 유지됩니다. 설정되면 스레드 사용자 인증 정보를 관리하는 다른 앱이 getPreferredCredentials() 호출에서 사용자 인증 정보를 가져올 수 있습니다.

Google TBR 동기화

Android 기기는 Google TBR과 자동으로 동기화됩니다. Android에 사용자 인증 정보가 없는 경우 기기는 네트워크의 Google TBR에서 사용자 인증 정보를 추출하며, 이러한 사용자 인증 정보가 기본 사용자 인증 정보가 됩니다. TBR과 Android 기기 간의 동기화는 TBR이 단일 사용자와 페어링되어 있거나 동일한 스마트 홈 (구조)에 있는 두 사용자와 페어링되어 있는 경우에만 발생합니다.

다른 Google 사용자가 Android용 GHA 또는 iOS용 GHA를 사용 중이고 동일한 구조에 있는 경우에도 이 프로세스가 실행됩니다. iOS용 GHA의 경우 기본 사용자 인증 정보가 없는 경우 iOS 저장소에 기본 사용자 인증 정보가 설정됩니다.

두 대의 Android 기기 (또는 Android + iGHA)가 서로 다른 우선 사용자 인증 정보 세트를 사용하여 동일한 네트워크에 있는 경우 원래 TBR을 구성한 기기가 TBR에 우선합니다.

서드 파티 TBR 온보딩

사용자의 스마트 홈 (구조)에 따라 사용자 인증 정보 저장소의 범위가 현재 지정되지 않았습니다. 각 Android 기기에는 자체 BAID 저장소가 있지만 네트워크에 Google TBR이 있으면 iOS용 Google Home 앱을 실행하는 다른 Android 기기와 iOS 기기가 해당 TBR과 동기화되고 휴대전화 저장소에 로컬 사용자 인증 정보를 설정하려고 시도합니다.

새 OOB TBR이 네트워크를 만들기 전에 Android의 저장소에 선호하는 네트워크가 이미 있는지 확인하는 것이 중요합니다.

  • 기본 네트워크가 있는 경우 공급업체는 이를 사용해야 합니다. 이렇게 하면 가능한 경우 Thread 기기가 단일 Thread 네트워크에 연결됩니다.
  • 선호하는 네트워크가 없는 경우 새 사용자 인증 정보 세트를 만들고 Google Play 서비스에서 TBR에 할당합니다. Android는 이러한 사용자 인증 정보를 모든 Google 기반 TBR에 설정된 표준 사용자 인증 정보로 사용하며, 다른 공급업체는 추가 기기로 메시 도달범위와 견고성을 개선할 수 있습니다.

cd8bc726f67b1fa1.png

4. Android 앱 클론 및 수정

Thread API의 주요 호출을 보여주는 Android 앱을 만들었습니다. 이러한 패턴은 앱에서 사용할 수 있습니다. 이 Codelab에서는 GitHub에서 Matter용 Google Home 샘플 앱을 클론합니다.

여기에 표시된 모든 소스 코드는 이미 샘플 앱에 코딩되어 있습니다. 필요에 따라 수정할 수 있지만 앱을 클론하거나 사전 빌드된 바이너리를 실행하여 기능을 검사할 수도 있습니다.

  1. 다음을 사용하여 클론합니다.
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Android 스튜디오를 다운로드하여 엽니다.
  2. File(파일) > Open(열기)을 클릭하고 클론된 저장소를 가리킵니다.
  3. Android 휴대전화에서 개발자 모드를 사용 설정합니다.
  4. USB 케이블을 통해 컴퓨터에 연결합니다.
  5. <Cmd+R> (OS X) 또는 <Ctrl+R> (Win, Linux)을 통해 Android 스튜디오에서 앱을 실행합니다.
  6. 휠 -> 개발자 유틸리티 -> 스레드 네트워크로 이동합니다.
  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)에 있고 인텐트 관찰자는 Activity Fragment (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을 등록해야 합니다. 앱만 TBR의 국경 관리자 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 Server 또는 OTBR의 네이티브 HTTP Rest Server를 사용하여 구현합니다.

/**
* 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 제품에서 사용자 인증 정보 가져오기

앞에서 설명한 대로 TBR에서 사용자 인증 정보를 가져오려면 GET HTTP 동사를 사용하세요. 샘플 Python 스크립트를 참고하세요.

빌드 및 가져오기

Android 앱을 만들 때 Google Play 서비스 스레드 모듈을 지원하도록 매니페스트, 빌드, 가져오기를 변경해야 합니다. 다음 세 스니펫은 추가된 내용의 대부분을 요약합니다.

샘플 앱은 주로 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 검색을 사용하여 네트워크에서 사용 가능한 스레드 보더 라우터 목록과 각 BAID를 만듭니다.

이는 GPS 사용자 인증 정보의 저장소에 TBR 정보를 입력할 때 매우 유용합니다. 그러나 이 Codelab에서는 사용법을 다루지 않습니다. Android 서비스 검색 라이브러리 NSDManager를 사용하며 전체 소스 코드는 샘플 앱의 ServiceDiscovery.kt에서 확인할 수 있습니다.

6. 통합 정리

이러한 호출을 구현하거나 샘플 앱을 사용하면 RPi OTBR을 완전히 온보딩할 수 있습니다. 샘플 앱은 8개의 버튼을 노출합니다.

91979bf065e9673d.png

TBR 온보딩의 가능한 시퀀스는 다음과 같습니다.

  1. 우선순위 사용자 인증 정보가 있는지 쿼리합니다 (파란색, 1행).
  2. 답변에 따라
  3. GPS 기본 사용자 인증 정보 가져오기 (파란색, 두 번째 행)
  4. GPS에서 TBR 사용자 인증 정보 설정 (파란색, 3번째 행) -> TBR 선택 -> 무작위 생성 -> 네트워크 이름 입력 -> 확인
  5. 이제 기본 사용자 인증 정보가 있으므로 Set RPi OTBR credentials를 사용하여 OTBR에 설정합니다. 그러면 대기 중인 세트에 해당 사용자 인증 정보가 적용됩니다.

샘플 앱의 기본값은 10초 지연을 사용하는 것입니다. 따라서 이 기간이 지나면 RPi TBR 및 네트워크에 있을 수 있는 다른 노드의 사용자 인증 정보가 새 데이터 세트로 이전됩니다.

7. 결론

이 Codelab에서는 샘플 Android 앱을 클론하고 Google Play 서비스의 Thread Storage API를 사용하는 여러 코드 스니펫을 분석했습니다. 이러한 API를 사용하여 공급업체의 TBR을 보여주는 RPi TBR에 온보딩할 수 있는 공통 데이터 세트를 보유했습니다.

사용자의 모든 TBR을 동일한 네트워크에 두면 스레드 네트워크의 복원력과 도달범위가 개선됩니다. 또한 사용자 인증 정보에 액세스할 수 없어 앱이 Thread 기기를 온보딩할 수 없는 결함이 있는 사용자 여정을 방지합니다.

이 Codelab과 샘플 앱이 자체 앱과 Thread Border Router 제품을 설계하고 개발하는 데 도움이 되기를 바랍니다.

8. 참조

RCP 보조 프로세서

DBUS