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

1. 시작하기 전에

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

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

이 Codelab은 이전 Codelab을 기반으로 하며, 보더 라우터와 앱이 Google API와 상호작용하여 단일 스레드 네트워크를 만드는 방법을 다룹니다. 스레드 크리덴셜 수렴은 네트워크 견고성을 높이고 스레드를 사용하는 애플리케이션과의 사용자 상호작용을 간소화하기 때문에 중요합니다.

기본 요건

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

학습할 내용

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

필요한 항목

  • OTBR (Open Thread Border Router)를 실행하는 Raspberry Pi 4 보드 또는 다른 Linux 기반 보드
  • RCP (Radio Co-Processor)로 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

이 단계에서는 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 서버를 설정하고 스레드 사용자 인증 정보를 검색하기 위한 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 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에 추가하는 앱이 사용자 인증 정보의 소유자가 되고 해당 정보에 액세스할 수 있는 전체 권한을 가집니다. 다른 앱에서 추가한 사용자 인증 정보에 액세스하려고 하면 PERMISSION_DENIED 오류가 발생합니다. 그러나 기본 사용자 인증 정보는 사용자 동의가 있는 모든 앱에서 항상 사용할 수 있습니다. 스레드 보더 라우터 네트워크가 업데이트될 때 Google Play 서비스에 저장된 사용자 인증 정보를 최신 상태로 유지하는 것이 좋습니다. 이 정보는 현재 사용되지 않지만 향후 향상된 여정을 제공할 수 있습니다.

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

Google TBR 동기화

Android 기기는 Google TBR과 자동으로 동기화됩니다. Android에 사용자 인증 정보가 없는 경우 기기는 네트워크의 Google TBR에서 사용자 인증 정보를 추출하며 이러한 사용자 인증 정보는 Preferred Credentials가 됩니다. TBR과 Android 기기 간의 동기화는 TBR이 한 명의 사용자와 페어링되었거나 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 앱 클론 및 수정

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. 파일 > 열기를 클릭하고 클론된 저장소를 가리킵니다.
  3. Android 휴대전화에서 개발자 모드를 사용 설정합니다.
  4. USB 케이블을 통해 컴퓨터에 연결합니다.
  5. <Cmd+R> (OS X) 또는 <Ctrl+R> (Win, Linux)을 통해 Android 스튜디오에서 앱을 실행합니다.
  6. Wheel -> 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)에 있기 때문입니다. 따라서 인텐트SenderResult가 라이브 데이터에 게시되면 이 관찰자의 콘텐츠가 실행됩니다.

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의 Border Agent ID와 연결된 사용자 인증 정보를 읽을 수 있지만, TBR이 가장 먼저 등록되면 해당 사용자 인증 정보가 Preferred Credentials 세트에 복사됩니다. 이 정보는 사용자가 승인하는 한 휴대전화의 모든 앱에서 액세스할 수 있습니다.

/**
* 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 동사를 사용하여 TBR에서 사용자 인증 정보를 가져옵니다. 샘플 Python 스크립트를 참조하세요.

빌드 및 가져오기

Android 앱을 만들 때는 Google Play 서비스 스레드 모듈을 지원하도록 매니페스트, 빌드, 가져오기를 변경해야 합니다. 다음 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 검색을 사용하여 네트워크에서 사용 가능한 스레드 보더 라우터 목록과 각 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 서비스의 Thread Storage API를 사용하는 여러 코드 스니펫을 분석했습니다. 이러한 API를 사용하여 공급업체의 TBR을 보여주는 RPi TBR에 온보딩할 수 있는 공통 데이터 세트를 만들었습니다.

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

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

8. 참조

RCP 보조 프로세서

DBUS