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

1. 시작하기 전에

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

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

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

기본 요건

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

학습할 내용

  • 스레드 공유 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 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이 이 휴대기기의 기본 사용자 인증 정보를 결정합니다.

BAID에 스레드 네트워크 사용자 인증 정보를 추가하는 앱이 사용자 인증 정보의 소유자가 되며 사용자 인증 정보에 액세스할 수 있는 모든 권한을 갖습니다. 다른 앱에서 추가한 사용자 인증 정보에 액세스하려고 하면 PERMISSION_DENIED 오류가 표시됩니다. 하지만 사용자가 동의하면 선호하는 사용자 인증 정보는 항상 모든 앱에서 사용할 수 있습니다. 스레드 보더 라우터 네트워크가 업데이트되면 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 저장소에 이미 있는지 확인하는 것이 중요합니다.

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

cd8bc726f67b1fa1.png

4. Android 앱 클론 및 수정

스레드 API에 대한 주요 호출을 보여주는 Android 앱이 만들어졌습니다. 앱에서 이러한 패턴을 사용할 수 있습니다. 이 Codelab에서는 GitHub에서 Matter용 Google Home 샘플 앱을 클론합니다.

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

  1. 다음을 사용하여 클론합니다.
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Android 스튜디오를 다운로드하여 엽니다.
  2. 파일 > 열기를 클릭하고 클론된 저장소를 가리킵니다.
  3. Android 휴대전화에서 개발자 모드를 사용 설정합니다.
  4. USB 케이블을 통해 컴퓨터에 연결합니다.
  5. Android 스튜디오에서 <Cmd+R> (OS X) 또는 <Ctrl+R> (Windows, Linux)을 통해 앱을 실행합니다.
  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를 만듭니다.

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

6. 통합 정리

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

91979bf065e9673d.png

TBR 온보딩의 가능한 순서는 다음과 같습니다.

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

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

7. 결론

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

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

이 Codelab과 샘플 앱이 자체 앱과 스레드 보더 라우터 제품을 설계하고 개발하는 데 도움이 되기를 바랍니다.

8. 참조

RCP 보조 프로세서

DBUS