From 233d7756d952d4748e58778bae114f6945bac063 Mon Sep 17 00:00:00 2001 From: James Swan <122404367+swan-amazon@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:47:20 +0000 Subject: [PATCH] Implemented support for T&C --- .../all-clusters-app.matter | 8 +- .../all-clusters-common/all-clusters-app.zap | 83 ++++- .../linux/include/CHIPProjectAppConfig.h | 23 +- .../google/chip/chiptool/CHIPToolActivity.kt | 5 + .../chiptool/GenericChipDeviceListener.kt | 8 + .../chip/chiptool/SelectActionFragment.kt | 2 +- .../DeviceProvisioningFragment.kt | 8 + .../CHIPDeviceDetailsFragment.kt | 4 +- .../CHIPLedgerDetailsFragment.kt | 113 ++++--- .../res/layout/chip_device_info_fragment.xml | 4 +- .../res/layout/chip_ledger_info_fragment.xml | 13 +- .../res/layout/select_action_fragment.xml | 2 +- .../main/res/raw/mock_dcl_responses_json.json | 38 +++ .../app/src/main/res/values/strings.xml | 5 +- .../commands/pairing/PairingCommand.cpp | 13 +- .../commands/pairing/PairingCommand.h | 12 +- .../general-commissioning-server.cpp | 189 ++++++++--- src/app/server/BUILD.gn | 8 +- .../DefaultEnhancedSetupFlowProvider.cpp | 151 +++++++++ .../server/DefaultEnhancedSetupFlowProvider.h | 87 +++++ .../DefaultTermsAndConditionsProvider.cpp | 129 ++++++++ .../DefaultTermsAndConditionsProvider.h | 71 ++++ src/app/server/EnhancedSetupFlowProvider.h | 108 +++++++ src/app/server/Server.cpp | 120 ++++++- src/app/server/Server.h | 101 ++---- src/app/server/TermsAndConditionsProvider.h | 75 +++++ src/app/tests/BUILD.gn | 5 +- .../TestDefaultEnhancedSetupFlowProvider.cpp | 305 ++++++++++++++++++ .../TestDefaultTermsAndConditionsProvider.cpp | 225 +++++++++++++ src/controller/AutoCommissioner.cpp | 4 +- src/controller/CHIPDeviceController.cpp | 51 ++- src/controller/CHIPDeviceController.h | 7 +- src/controller/CommissioningDelegate.cpp | 3 + src/controller/CommissioningDelegate.h | 22 +- .../java/CHIPDeviceController-JNI.cpp | 36 +++ .../ChipDeviceController.java | 13 + .../src/matter/controller/MatterController.kt | 4 + src/lib/support/DefaultStorageKeyAllocator.h | 6 +- 38 files changed, 1864 insertions(+), 197 deletions(-) create mode 100644 examples/android/CHIPTool/app/src/main/res/raw/mock_dcl_responses_json.json create mode 100644 src/app/server/DefaultEnhancedSetupFlowProvider.cpp create mode 100644 src/app/server/DefaultEnhancedSetupFlowProvider.h create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.cpp create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.h create mode 100644 src/app/server/EnhancedSetupFlowProvider.h create mode 100644 src/app/server/TermsAndConditionsProvider.h create mode 100644 src/app/tests/TestDefaultEnhancedSetupFlowProvider.cpp create mode 100644 src/app/tests/TestDefaultTermsAndConditionsProvider.cpp diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index f243ce415d23fc..255b5946195980 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -7508,11 +7508,15 @@ endpoint 0 { callback attribute regulatoryConfig; callback attribute locationCapability; callback attribute supportsConcurrentConnection; + ram attribute TCAcceptedVersion; + ram attribute TCMinRequiredVersion default = 1; + ram attribute TCAcknowledgements default = 0x0000; + ram attribute TCAcknowledgementsRequired default = true; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 0; + ram attribute featureMap default = 1; ram attribute clusterRevision default = 1; handle command ArmFailSafe; @@ -7521,6 +7525,8 @@ endpoint 0 { handle command SetRegulatoryConfigResponse; handle command CommissioningComplete; handle command CommissioningCompleteResponse; + handle command SetTCAcknowledgements; + handle command SetTCAcknowledgementsResponse; } server cluster NetworkCommissioning { diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 9e0452b0c423fc..28d3d174be2b0d 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -2124,6 +2124,7 @@ "define": "GENERAL_COMMISSIONING_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "ArmFailSafe", @@ -2172,6 +2173,22 @@ "source": "server", "isIncoming": 0, "isEnabled": 1 + }, + { + "name": "SetTCAcknowledgements", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetTCAcknowledgementsResponse", + "code": 7, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 } ], "attributes": [ @@ -2255,6 +2272,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "TCAcceptedVersion", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCMinRequiredVersion", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCAcknowledgements", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "bitmap16", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0000", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCAcknowledgementsRequired", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "true", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -2329,7 +2410,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h b/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h index 0285563216e706..24658d46ca1c8c 100644 --- a/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h +++ b/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2022-2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,3 +51,24 @@ // Claim a device type while advertising that matches the device type on our // endpoint 1. 0x0101 is the "Dimmable Light" device type. #define CHIP_DEVICE_CONFIG_DEVICE_TYPE 0x0101 + +/** + * @brief Configures the required terms and conditions acknowledgements. + * + * This macro defines the required terms and conditions acknowledgements bitmask. The bit-field is 16 bits long, so the possible + * value range is [0, 65535). This setting can be used to require that terms and conditions are presented to the user during + * commissioning. + */ +#define CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS 1 // Require that terms and conditions ordinal 1 must be accepted. + +/** + * @brief Configures the latest known version of the terms and conditions. + * + * This macro defines the version number of the latest terms and conditions. It allows the application to iterate on revisions of + * the terms and conditions. A value of 0 indicates that no specific version is required. This setting can be used to enforce + * version-specific terms and conditions acknowledgements in the application. When the set of terms and conditions needs to be + * changes, the version number should be monotomically increased. If the lateast terms and conditions version is updated (most + * likely during an OTA) then this may signal to the Administrator that updated terms and conditions should be presented to the + * user. + */ +#define CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION 1 // The current terms and conditions revision is 1. diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt index 2b2078dde5e688..75353ffa5ea355 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt @@ -122,6 +122,11 @@ class CHIPToolActivity : startActivity(redirectIntent) } + override fun handleEnhancedSetupFlowClicked() { + Toast.makeText(this, "Enhanced Setup Flow", Toast.LENGTH_SHORT).show() + showFragment(DeviceProvisioningFragment.newInstance(deviceInfo!!, null)) + } + override fun setNetworkType(type: ProvisionNetworkType?) { networkType = type } diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt index 2eb2a1c77fa042..bc333f3153ee0b 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt @@ -60,4 +60,12 @@ open class GenericChipDeviceListener : ChipDeviceController.CompletionListener { override fun onICDRegistrationComplete(errorCode: Long, icdDeviceInfo: ICDDeviceInfo) { // No op } + + override fun onTermsAndConditionsRequired() { + // No op + } + + override fun onTermsAndConditionsRequiredComplete(termsAndConditionsAcknowledgements: Int, termsAndConditionsVersion: Int) { + // No op + } } diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt index 69bfdc109912c9..9f23e67db4f559 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt @@ -69,7 +69,7 @@ class SelectActionFragment : Fragment() { binding.basicClusterBtn.setOnClickListener { handleBasicClicked() } binding.attestationTestBtn.setOnClickListener { handleAttestationTestClicked() } binding.clusterInteractionBtn.setOnClickListener { handleClusterInteractionClicked() } - binding.provisionCustomFlowBtn.setOnClickListener { handleProvisionCustomFlowClicked() } + binding.provisionCustomFlowReadFromLedgerBtn.setOnClickListener { handleProvisionCustomFlowClicked() } binding.wildcardBtn.setOnClickListener { handleWildcardClicked() } binding.unpairDeviceBtn.setOnClickListener { handleUnpairDeviceClicked() } binding.groupSettingBtn.setOnClickListener { handleGroupSettingClicked() } diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt index 97b2576827079a..3466fe4e746c13 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt @@ -314,6 +314,14 @@ class DeviceProvisioningFragment : Fragment() { .show() } } + + override fun onTermsAndConditionsRequired() { + } + + override fun onTermsAndConditionsRequiredComplete(termsAndConditionsAcknowledgements: Int, termsAndConditionsVersion: Int) { + Log.d(TAG, "onTermsAndConditionsRequiredComplete: $termsAndConditionsAcknowledgements, $termsAndConditionsVersion") + deviceController.updateTermsAndConditionsAcknowledgements(termsAndConditionsAcknowledgements, termsAndConditionsVersion) + } } private fun ByteArray.toHex(): String = diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPDeviceDetailsFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPDeviceDetailsFragment.kt index 411f51eae3de73..d8200b22c7ef2a 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPDeviceDetailsFragment.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPDeviceDetailsFragment.kt @@ -80,8 +80,8 @@ class CHIPDeviceDetailsFragment : Fragment() { // commissioningFlow = 2 (Custom), read device info from Ledger if (deviceInfo.commissioningFlow == 2) { - binding.customFlowBtn.visibility = View.VISIBLE - binding.customFlowBtn.setOnClickListener { + binding.customFlowReadFromLedgerBtn.visibility = View.VISIBLE + binding.customFlowReadFromLedgerBtn.setOnClickListener { FragmentUtil.getHost(this@CHIPDeviceDetailsFragment, Callback::class.java) ?.handleReadFromLedgerClicked(deviceInfo) } diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPLedgerDetailsFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPLedgerDetailsFragment.kt index 92edce9da1331b..0f6fce6e95d85f 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPLedgerDetailsFragment.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPLedgerDetailsFragment.kt @@ -25,14 +25,19 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.fragment.app.Fragment import com.android.volley.Request +import com.android.volley.Response.Listener import com.android.volley.toolbox.JsonObjectRequest import com.android.volley.toolbox.Volley import com.google.chip.chiptool.R import com.google.chip.chiptool.databinding.ChipLedgerInfoFragmentBinding import com.google.chip.chiptool.util.FragmentUtil import com.google.gson.Gson +import java.io.BufferedReader +import java.io.InputStreamReader +import org.json.JSONObject /** Show the [CHIPDeviceInfo] from Ledger */ class CHIPLedgerDetailsFragment : Fragment() { @@ -55,7 +60,7 @@ class CHIPLedgerDetailsFragment : Fragment() { // Ledger api url val url = - Uri.parse(context!!.getString(R.string.dcl_api_root_url)) + Uri.parse(DCL_API_ROOT_URL) .buildUpon() .appendPath("${deviceInfo.vendorId}") .appendPath("${deviceInfo.productId}") @@ -63,53 +68,42 @@ class CHIPLedgerDetailsFragment : Fragment() { .toString() Log.d(TAG, "Dcl request Url: $url") - // Ledger API call - val jsonObjectRequest = - JsonObjectRequest( - Request.Method.GET, - url, - null, - { response -> - Log.d(TAG, "Response from dcl $response") - - // parse redirect Url - val responseJson = response.getJSONObject(context!!.getString(R.string.dcl_response_key)) - val redirectUrl = - responseJson.getString(context!!.getString(R.string.dcl_custom_flow_url_key)) - Log.d(TAG, "Redirect Url from Ledger: $redirectUrl") - binding.commissioningFlowUrlTv.text = redirectUrl - - // generate redirect payload - val gson = Gson() - val payloadJson = gson.toJson(deviceInfo) - val payloadBase64 = Base64.encodeToString(payloadJson.toByteArray(), Base64.DEFAULT) - val redirectUrlWithPayload = - Uri.parse(redirectUrl) - .buildUpon() - .appendQueryParameter("payload", payloadBase64) - .appendQueryParameter( - "returnUrl", - context!!.getString(R.string.custom_flow_return_url) - ) - .build() - .toString() - - Log.d(TAG, "Redirect Url with Payload: $redirectUrlWithPayload") - binding.redirectBtn.setOnClickListener { - FragmentUtil.getHost(this@CHIPLedgerDetailsFragment, Callback::class.java) - ?.handleCustomFlowRedirectClicked(redirectUrlWithPayload) - } + val context = getContext()!! + val mockDclRequestsResponsesRaw = readRawResourceFile(R.raw.mock_dcl_responses_json) + val mockDclRequestsResponses: org.json.JSONArray = org.json.JSONArray(mockDclRequestsResponsesRaw) + + val mockDclRequestsResponseMap = HashMap() + for (i in 0 until mockDclRequestsResponses.length()) { + val mockDclRequestsResponse = mockDclRequestsResponses.getJSONObject(i) + + val mockDclRequest: org.json.JSONObject = mockDclRequestsResponse.getJSONObject("request") + val mockDclResponse: org.json.JSONObject = mockDclRequestsResponse.getJSONObject("response") - // enable redirect button - binding.redirectBtn.visibility = View.VISIBLE - }, - { error -> - Log.e(TAG, "Dcl request failed: $error") - binding.commissioningFlowUrlTv.text = - context!!.getString(R.string.chip_ledger_info_commissioning_flow_url_not_available) - } - ) - queue.add(jsonObjectRequest) + val mockDclRequestURL = mockDclRequest.getString("url") + val mockDclResponseBody = mockDclResponse.getJSONObject("body") + + mockDclRequestsResponseMap.put(mockDclRequestURL, mockDclResponseBody) + Log.d(TAG, "mockDclRequestURL= $mockDclRequestURL") + Log.d(TAG, "mockDclResponseBody= $mockDclResponseBody") + } + + Log.d(TAG, "Request url= $url") + val response: org.json.JSONObject = mockDclRequestsResponseMap.get(url.toString())!! + Log.d(TAG, "Response from dcl $response") + Toast.makeText(context, "Enhanced Setup Flow: $response", Toast.LENGTH_LONG).show() + + val enhancedSetupFlowOptions = response.optInt("enhancedSetupFlowOptions", 0) + + if (0 != (enhancedSetupFlowOptions and 0b00000001)) { + Toast.makeText(context, "Enhanced Setup Flow supported!", Toast.LENGTH_SHORT).show() + + binding.commissionBtn.setOnClickListener { + FragmentUtil.getHost(this@CHIPLedgerDetailsFragment, Callback::class.java) + ?.handleEnhancedSetupFlowClicked() + } + + binding.commissionBtn.visibility = View.VISIBLE + } return binding.root } @@ -119,15 +113,40 @@ class CHIPLedgerDetailsFragment : Fragment() { _binding = null } + private fun readRawResourceFile(resourceId: Int): String? { + return try { + val inputStream = resources.openRawResource(resourceId) + val bufferedReader = BufferedReader(InputStreamReader(inputStream)) + val stringBuilder = StringBuilder() + var line: String? = bufferedReader.readLine() + while (line != null) { + stringBuilder.append(line) + line = bufferedReader.readLine() + } + bufferedReader.close() + inputStream.close() + stringBuilder.toString() + } catch (e: Exception) { + Log.e("MainActivity", "Error reading raw resource file", e) + null + } + } + /** Interface for notifying the host. */ interface Callback { /** Notifies listener of Custom flow redirect button click. */ fun handleCustomFlowRedirectClicked(redirectUrl: String) + + /** Notifies listener of Enhanced Setup flow button click. */ + fun handleEnhancedSetupFlowClicked() } companion object { private const val TAG = "CUSTOM_FLOW" private const val ARG_DEVICE_INFO = "device_info" + private const val DCL_CUSTOM_FLOW_URL_KEY = "commissioningCustomFlowUrl"; + private const val DCL_API_ROOT_URL = "https://on.dcl.csa-iot.org/dcl/model/models"; + private const val CUSTOM_FLOW_RETURN_URL = "mt://modelinfo"; @JvmStatic fun newInstance(deviceInfo: CHIPDeviceInfo): CHIPLedgerDetailsFragment { diff --git a/examples/android/CHIPTool/app/src/main/res/layout/chip_device_info_fragment.xml b/examples/android/CHIPTool/app/src/main/res/layout/chip_device_info_fragment.xml index a1deab648adc9f..04df24030f35cd 100644 --- a/examples/android/CHIPTool/app/src/main/res/layout/chip_device_info_fragment.xml +++ b/examples/android/CHIPTool/app/src/main/res/layout/chip_device_info_fragment.xml @@ -220,7 +220,7 @@ tools:ignore="LabelFor" />