Skip to content

Commit 36c4638

Browse files
vivien-applececille
authored andcommitted
[chip-tool] Add Enhanced Commissioning Support (T&C Flow via Local DCL) to chip-tool (project-chip#37049)
* [chip-tool] Add chip-tool dcl fake cluster commands * [chip-tool] Add a fake local dcl server script for testing/developement purposes * [chip-tool] Add chip-tool dcl tc-display and tc-display-by-payload commands * [General Commissioning Server] Dynamically encode the feature map 'GeneralCommissioning::Feature::kTermsAndConditions' if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED is set * [Examples/platform/linux] Set default TermsAndConditions if requested from the command line * [chip-tool] Add TermsAndConditions support to chip-tool pairing code command
1 parent e80c0fa commit 36c4638

20 files changed

+1705
-0
lines changed

examples/chip-tool/BUILD.gn

+15
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ if (config_use_interactive_mode) {
2323
import("//build_overrides/editline.gni")
2424
}
2525

26+
import("${chip_root}/build_overrides/boringssl.gni")
27+
import("${chip_root}/src/crypto/crypto.gni")
28+
2629
assert(chip_build_tools)
2730

2831
config("config") {
@@ -67,6 +70,14 @@ static_library("chip-tool-utils") {
6770
"commands/common/HexConversion.h",
6871
"commands/common/RemoteDataModelLogger.cpp",
6972
"commands/common/RemoteDataModelLogger.h",
73+
"commands/dcl/DCLClient.cpp",
74+
"commands/dcl/DCLClient.h",
75+
"commands/dcl/DisplayTermsAndConditions.cpp",
76+
"commands/dcl/DisplayTermsAndConditions.h",
77+
"commands/dcl/HTTPSRequest.cpp",
78+
"commands/dcl/HTTPSRequest.h",
79+
"commands/dcl/JsonSchemaMacros.cpp",
80+
"commands/dcl/JsonSchemaMacros.h",
7081
"commands/delay/SleepCommand.cpp",
7182
"commands/delay/WaitForCommissioneeCommand.cpp",
7283
"commands/discover/DiscoverCommand.cpp",
@@ -102,6 +113,10 @@ static_library("chip-tool-utils") {
102113
sources += [ "commands/common/DeviceScanner.cpp" ]
103114
}
104115

116+
if (chip_device_platform == "darwin" || chip_crypto == "boringssl") {
117+
deps += [ "${boringssl_root}:boringssl_with_ssl_sources" ]
118+
}
119+
105120
public_deps = [
106121
"${chip_root}/examples/common/tracing:commandline",
107122
"${chip_root}/src/app/icd/client:handler",
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2022 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
#pragma once
20+
21+
#include "commands/common/Commands.h"
22+
#include "commands/dcl/DCLCommands.h"
23+
24+
void registerCommandsDCL(Commands & commands)
25+
{
26+
const char * clusterName = "DCL";
27+
commands_list clusterCommands = {
28+
make_unique<DCLModelCommand>(), //
29+
make_unique<DCLModelByPayloadCommand>(), //
30+
make_unique<DCLTCCommand>(), //
31+
make_unique<DCLTCByPayloadCommand>(), //
32+
make_unique<DCLTCDisplayCommand>(), //
33+
make_unique<DCLTCDisplayByPayloadCommand>(), //
34+
};
35+
36+
commands.RegisterCommandSet(clusterName, clusterCommands, "Commands to interact with the DCL.");
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
#include "DCLClient.h"
20+
21+
#include <lib/support/CodeUtils.h>
22+
#include <lib/support/logging/CHIPLogging.h>
23+
#include <setup_payload/ManualSetupPayloadParser.h>
24+
#include <setup_payload/QRCodeSetupPayloadParser.h>
25+
26+
#include "HTTPSRequest.h"
27+
#include "JsonSchemaMacros.h"
28+
29+
namespace {
30+
constexpr const char * kDefaultDCLHostName = "on.dcl.csa-iot.org";
31+
constexpr const char * kErrorSchemaValidation = "Model schema validation failed for response content: ";
32+
constexpr const char * kErrorVendorIdIsZero = "Invalid argument: Vendor ID should not be 0";
33+
constexpr const char * kErrorProductIdIsZero = "Invalid argument: Product ID should not be 0";
34+
constexpr const char * kErrorOrdinalValueTooLarge = "Ordinal value exceeds the maximum allowable bits: ";
35+
constexpr const char * kRequestModelVendorProductPath = "/dcl/model/models/%u/%u";
36+
constexpr uint8_t kRequestPathBufferSize = 64;
37+
constexpr uint16_t kTermsAndConditionSchemaVersion = 1;
38+
} // namespace
39+
40+
namespace chip {
41+
namespace tool {
42+
namespace dcl {
43+
44+
namespace {
45+
CHIP_ERROR ValidateModelSchema(const Json::Value & json)
46+
{
47+
CHECK_REQUIRED_TYPE(json, model, Object)
48+
auto model = json["model"];
49+
50+
CHECK_REQUIRED_TYPE(model, commissioningCustomFlow, UInt);
51+
52+
// The "enhancedSetupFlowOptions" field is theoretically required by the schema.
53+
// However, the current DCL implementation does not include it.
54+
// To handle this gracefully, we inject the field and set its value to 0 if it is missing.
55+
if (!model.isMember("enhancedSetupFlowOptions"))
56+
{
57+
model["enhancedSetupFlowOptions"] = 0;
58+
}
59+
60+
CHECK_REQUIRED_TYPE(model, enhancedSetupFlowOptions, UInt)
61+
62+
// Check if enhancedSetupFlowOptions has bit 0 set.
63+
// Bit 0 indicates that enhanced setup flow is enabled.
64+
auto enhancedSetupFlowOptions = model["enhancedSetupFlowOptions"];
65+
VerifyOrReturnError((enhancedSetupFlowOptions.asUInt() & 0x01) != 0, CHIP_NO_ERROR);
66+
67+
// List of required keys in the "model" object if enhancedSetupFlowOptions has bit 0 set.
68+
CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCUrl, String)
69+
CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCDigest, String)
70+
CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCFileSize, UInt)
71+
CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCRevision, UInt)
72+
CHECK_REQUIRED_TYPE(model, enhancedSetupFlowMaintenanceUrl, String)
73+
74+
return CHIP_NO_ERROR;
75+
}
76+
77+
CHIP_ERROR ValidateModelCustomFlow(const Json::Value & json, CommissioningFlow payloadCommissioningFlow)
78+
{
79+
auto model = json["model"];
80+
CHECK_REQUIRED_VALUE(model, commissioningCustomFlow, to_underlying(payloadCommissioningFlow))
81+
return CHIP_NO_ERROR;
82+
}
83+
84+
CHIP_ERROR ValidateTCLanguageEntries(const Json::Value & languageEntries)
85+
{
86+
for (Json::Value::const_iterator it = languageEntries.begin(); it != languageEntries.end(); it++)
87+
{
88+
const Json::Value & languageArray = *it;
89+
90+
CHECK_TYPE(languageArray, languageArray, Array);
91+
92+
for (Json::ArrayIndex i = 0; i < languageArray.size(); i++)
93+
{
94+
const Json::Value & term = languageArray[i];
95+
CHECK_REQUIRED_TYPE(term, title, String);
96+
CHECK_REQUIRED_TYPE(term, text, String);
97+
CHECK_REQUIRED_TYPE(term, required, Bool);
98+
CHECK_REQUIRED_TYPE(term, ordinal, UInt);
99+
100+
auto ordinal = term["ordinal"].asUInt();
101+
VerifyOrReturnError(ordinal < 16, CHIP_ERROR_INVALID_ARGUMENT,
102+
ChipLogError(chipTool, "%s%u", kErrorOrdinalValueTooLarge, ordinal));
103+
}
104+
}
105+
106+
return CHIP_NO_ERROR;
107+
}
108+
109+
CHIP_ERROR ValidateTCCountryEntries(const Json::Value & countryEntries)
110+
{
111+
for (Json::Value::const_iterator it = countryEntries.begin(); it != countryEntries.end(); it++)
112+
{
113+
const Json::Value & countryEntry = *it;
114+
115+
CHECK_REQUIRED_TYPE(countryEntry, defaultLanguage, String);
116+
CHECK_REQUIRED_TYPE(countryEntry, languageEntries, Object);
117+
118+
ReturnErrorOnFailure(ValidateTCLanguageEntries(countryEntry["languageEntries"]));
119+
}
120+
121+
return CHIP_NO_ERROR;
122+
}
123+
124+
CHIP_ERROR ValidateTermsAndConditionsSchema(const Json::Value & tc, unsigned int expectedEnhancedSetupFlowTCRevision)
125+
{
126+
CHECK_REQUIRED_VALUE(tc, schemaVersion, kTermsAndConditionSchemaVersion)
127+
CHECK_REQUIRED_TYPE(tc, esfRevision, UInt)
128+
CHECK_REQUIRED_TYPE(tc, defaultCountry, String)
129+
CHECK_REQUIRED_TYPE(tc, countryEntries, Object)
130+
CHECK_REQUIRED_VALUE(tc, esfRevision, expectedEnhancedSetupFlowTCRevision)
131+
return ValidateTCCountryEntries(tc["countryEntries"]);
132+
}
133+
134+
CHIP_ERROR RequestTermsAndConditions(const Json::Value & json, Json::Value & tc)
135+
{
136+
auto & model = json["model"];
137+
if ((model["enhancedSetupFlowOptions"].asUInt() & 0x01) == 0)
138+
{
139+
ChipLogProgress(chipTool,
140+
"Enhanced setup flow is not enabled for this model (bit 0 of enhancedSetupFlowOptions is not set). No "
141+
"Terms and Conditions are required for this configuration.");
142+
tc = Json::nullValue;
143+
return CHIP_NO_ERROR;
144+
}
145+
146+
auto & enhancedSetupFlowTCUrl = model["enhancedSetupFlowTCUrl"];
147+
auto & enhancedSetupFlowTCFileSize = model["enhancedSetupFlowTCFileSize"];
148+
auto & enhancedSetupFlowTCDigest = model["enhancedSetupFlowTCDigest"];
149+
auto & enhancedSetupFlowTCRevision = model["enhancedSetupFlowTCRevision"];
150+
151+
auto * tcUrl = enhancedSetupFlowTCUrl.asCString();
152+
const auto optionalFileSize = MakeOptional(static_cast<uint32_t>(enhancedSetupFlowTCFileSize.asUInt()));
153+
const auto optionalDigest = MakeOptional(enhancedSetupFlowTCDigest.asCString());
154+
ReturnErrorOnFailure(https::Request(tcUrl, tc, optionalFileSize, optionalDigest));
155+
ReturnErrorOnFailure(ValidateTermsAndConditionsSchema(tc, enhancedSetupFlowTCRevision.asUInt()));
156+
157+
return CHIP_NO_ERROR;
158+
}
159+
160+
} // namespace
161+
162+
DCLClient::DCLClient(Optional<const char *> hostname, Optional<uint16_t> port)
163+
{
164+
mHostName = hostname.ValueOr(kDefaultDCLHostName);
165+
mPort = port.ValueOr(0);
166+
}
167+
168+
CHIP_ERROR DCLClient::Model(const char * onboardingPayload, Json::Value & outModel)
169+
{
170+
SetupPayload payload;
171+
bool isQRCode = strncmp(onboardingPayload, kQRCodePrefix, strlen(kQRCodePrefix)) == 0;
172+
if (isQRCode)
173+
{
174+
ReturnErrorOnFailure(QRCodeSetupPayloadParser(onboardingPayload).populatePayload(payload));
175+
VerifyOrReturnError(payload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT);
176+
}
177+
else
178+
{
179+
ReturnErrorOnFailure(ManualSetupPayloadParser(onboardingPayload).populatePayload(payload));
180+
VerifyOrReturnError(payload.isValidManualCode(), CHIP_ERROR_INVALID_ARGUMENT);
181+
}
182+
183+
auto vendorId = static_cast<VendorId>(payload.vendorID);
184+
auto productId = payload.productID;
185+
186+
// If both vendorId and productId are zero, return a null model without error.
187+
if (vendorId == 0 && productId == 0)
188+
{
189+
ChipLogProgress(chipTool, "Vendor ID and Product ID not found in the provided payload. DCL lookup will not be used.");
190+
outModel = Json::nullValue;
191+
return CHIP_NO_ERROR;
192+
}
193+
194+
ReturnErrorOnFailure(Model(vendorId, productId, outModel));
195+
196+
auto commissioningFlow = payload.commissioningFlow;
197+
CHIP_ERROR error = ValidateModelCustomFlow(outModel, commissioningFlow);
198+
VerifyOrReturnError(CHIP_NO_ERROR == error, error,
199+
ChipLogError(chipTool, "%s%s", kErrorSchemaValidation, outModel.toStyledString().c_str()));
200+
201+
return CHIP_NO_ERROR;
202+
}
203+
204+
CHIP_ERROR DCLClient::Model(const chip::VendorId vendorId, const uint16_t productId, Json::Value & outModel)
205+
{
206+
VerifyOrReturnError(0 != vendorId, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(chipTool, "%s", kErrorVendorIdIsZero));
207+
VerifyOrReturnError(0 != productId, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(chipTool, "%s", kErrorProductIdIsZero));
208+
209+
char path[kRequestPathBufferSize];
210+
VerifyOrReturnError(snprintf(path, sizeof(path), kRequestModelVendorProductPath, to_underlying(vendorId), productId) >= 0,
211+
CHIP_ERROR_INVALID_ARGUMENT);
212+
ReturnErrorOnFailure(https::Request(mHostName, mPort, path, outModel));
213+
214+
CHIP_ERROR error = ValidateModelSchema(outModel);
215+
VerifyOrReturnError(CHIP_NO_ERROR == error, error,
216+
ChipLogError(chipTool, "%s%s", kErrorSchemaValidation, outModel.toStyledString().c_str()));
217+
218+
return CHIP_NO_ERROR;
219+
}
220+
221+
CHIP_ERROR DCLClient::TermsAndConditions(const char * onboardingPayload, Json::Value & outTc)
222+
{
223+
Json::Value json;
224+
ReturnErrorOnFailure(Model(onboardingPayload, json));
225+
VerifyOrReturnError(Json::nullValue != json.type(), CHIP_NO_ERROR, outTc = Json::nullValue);
226+
ReturnErrorOnFailure(RequestTermsAndConditions(json, outTc));
227+
return CHIP_NO_ERROR;
228+
}
229+
230+
CHIP_ERROR DCLClient::TermsAndConditions(const chip::VendorId vendorId, const uint16_t productId, Json::Value & outTc)
231+
{
232+
Json::Value json;
233+
ReturnErrorOnFailure(Model(vendorId, productId, json));
234+
VerifyOrReturnError(Json::nullValue != json.type(), CHIP_NO_ERROR, outTc = Json::nullValue);
235+
ReturnErrorOnFailure(RequestTermsAndConditions(json, outTc));
236+
return CHIP_NO_ERROR;
237+
}
238+
239+
} // namespace dcl
240+
} // namespace tool
241+
} // namespace chip

0 commit comments

Comments
 (0)