Implement separate Produce and Consume SetupPayload validation modes (#33285)
* Implement separate Produce and Consume SetupPayload validation modes
In Consume mode, unknown rendevouz flags are allowed.
* Simplify the validation code by using VerifyOrReturnValue
setUpPINCode range is checked in IsValidSetupPIN() via CheckPayloadCommonConstraints()
so no separate check is needed.
* Fix comment spelling.
---------
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/setup_payload/SetupPayload.cpp b/src/setup_payload/SetupPayload.cpp
index 0634563..1ef4795 100644
--- a/src/setup_payload/SetupPayload.cpp
+++ b/src/setup_payload/SetupPayload.cpp
@@ -36,60 +36,43 @@
// Check the Setup Payload for validity
//
// `vendor_id` and `product_id` are allowed all of uint16_t
-bool PayloadContents::isValidQRCodePayload() const
+bool PayloadContents::isValidQRCodePayload(ValidationMode mode) const
{
// 3-bit value specifying the QR code payload version.
- if (version >= 1 << kVersionFieldLengthInBits)
- {
- return false;
- }
+ VerifyOrReturnValue(version < (1 << kVersionFieldLengthInBits), false);
- if (static_cast<uint8_t>(commissioningFlow) > static_cast<uint8_t>((1 << kCommissioningFlowFieldLengthInBits) - 1))
- {
- return false;
- }
+ VerifyOrReturnValue(static_cast<uint8_t>(commissioningFlow) < (1 << kCommissioningFlowFieldLengthInBits), false);
// Device Commissioning Flow
+ // Even in ValidationMode::kConsume we can only handle modes that we understand.
// 0: Standard commissioning flow: such a device, when uncommissioned, always enters commissioning mode upon power-up, subject
// to the rules in [ref_Announcement_Commencement]. 1: User-intent commissioning flow: user action required to enter
// commissioning mode. 2: Custom commissioning flow: interaction with a vendor-specified means is needed before commissioning.
// 3: Reserved
- if (commissioningFlow != CommissioningFlow::kStandard && commissioningFlow != CommissioningFlow::kUserActionRequired &&
- commissioningFlow != CommissioningFlow::kCustom)
- {
- return false;
- }
-
- chip::RendezvousInformationFlags allvalid(RendezvousInformationFlag::kBLE, RendezvousInformationFlag::kOnNetwork,
- RendezvousInformationFlag::kSoftAP);
- if (!rendezvousInformation.HasValue() || !rendezvousInformation.Value().HasOnly(allvalid))
- {
- return false;
- }
+ VerifyOrReturnValue(commissioningFlow == CommissioningFlow::kStandard ||
+ commissioningFlow == CommissioningFlow::kUserActionRequired ||
+ commissioningFlow == CommissioningFlow::kCustom,
+ false);
// General discriminator validity is enforced by the SetupDiscriminator class, but it can't be short for QR a code.
- if (discriminator.IsShortDiscriminator())
- {
- return false;
- }
+ VerifyOrReturnValue(!discriminator.IsShortDiscriminator(), false);
- if (setUpPINCode >= 1 << kSetupPINCodeFieldLengthInBits)
+ // RendevouzInformation must be present for a QR code.
+ VerifyOrReturnValue(rendezvousInformation.HasValue(), false);
+ if (mode == ValidationMode::kProduce)
{
- return false;
+ chip::RendezvousInformationFlags valid(RendezvousInformationFlag::kBLE, RendezvousInformationFlag::kOnNetwork,
+ RendezvousInformationFlag::kSoftAP);
+ VerifyOrReturnValue(rendezvousInformation.Value().HasOnly(valid), false);
}
return CheckPayloadCommonConstraints();
}
-bool PayloadContents::isValidManualCode() const
+bool PayloadContents::isValidManualCode(ValidationMode mode) const
{
- // Discriminator validity is enforced by the SetupDiscriminator class.
-
- if (setUpPINCode >= 1 << kSetupPINCodeFieldLengthInBits)
- {
- return false;
- }
-
+ // No additional constraints apply to Manual Pairing Codes.
+ // (If the payload has a long discriminator it will be converted automatically.)
return CheckPayloadCommonConstraints();
}
@@ -109,31 +92,22 @@
bool PayloadContents::CheckPayloadCommonConstraints() const
{
- // A version not equal to 0 would be invalid for v1 and would indicate new format (e.g. version 2)
- if (version != 0)
- {
- return false;
- }
+ // Validation rules in this method apply to all validation modes.
- if (!IsValidSetupPIN(setUpPINCode))
- {
- return false;
- }
+ // Even in ValidationMode::kConsume we don't understand how to handle any payload version other than 0.
+ VerifyOrReturnValue(version == 0, false);
+
+ VerifyOrReturnValue(IsValidSetupPIN(setUpPINCode), false);
// VendorID must be unspecified (0) or in valid range expected.
- if (!IsVendorIdValidOperationally(vendorID) && (vendorID != VendorId::Unspecified))
- {
- return false;
- }
+ VerifyOrReturnValue((vendorID == VendorId::Unspecified) || IsVendorIdValidOperationally(vendorID), false);
// A value of 0x0000 SHALL NOT be assigned to a product since Product ID = 0x0000 is used for these specific cases:
// * To announce an anonymized Product ID as part of device discovery
// * To indicate an OTA software update file applies to multiple Product IDs equally.
// * To avoid confusion when presenting the Onboarding Payload for ECM with multiple nodes
- if (productID == 0 && vendorID != VendorId::Unspecified)
- {
- return false;
- }
+ // In these special cases the vendorID must be 0 (Unspecified)
+ VerifyOrReturnValue(productID != 0 || vendorID == VendorId::Unspecified, false);
return true;
}
diff --git a/src/setup_payload/SetupPayload.h b/src/setup_payload/SetupPayload.h
index 6e81e0e..cc71547 100644
--- a/src/setup_payload/SetupPayload.h
+++ b/src/setup_payload/SetupPayload.h
@@ -76,6 +76,7 @@
inline constexpr uint32_t kSetupPINCodeMaximumValue = 99999998;
inline constexpr uint32_t kSetupPINCodeUndefinedValue = 0;
+static_assert(kSetupPINCodeMaximumValue < (1 << kSetupPINCodeFieldLengthInBits));
// clang-format off
const int kTotalPayloadDataSizeInBits =
@@ -128,8 +129,20 @@
SetupDiscriminator discriminator;
uint32_t setUpPINCode = 0;
- bool isValidQRCodePayload() const;
- bool isValidManualCode() const;
+ enum class ValidationMode : uint8_t
+ {
+ kProduce, ///< Only flags or values allowed by the current spec version are allowed.
+ /// Producers of a Setup Payload should use this mode to ensure the
+ // payload is valid according to the current spec version.
+ kConsume, ///< Flags or values that are reserved for future use, or were allowed in
+ /// a previous spec version may be present. Consumers of a Setup Payload
+ /// should use this mode to ensure they are forward and backwards
+ /// compatible with payloads from older or newer Matter devices.
+ };
+
+ bool isValidQRCodePayload(ValidationMode mode = ValidationMode::kProduce) const;
+ bool isValidManualCode(ValidationMode mode = ValidationMode::kProduce) const;
+
bool operator==(const PayloadContents & input) const;
static bool IsValidSetupPIN(uint32_t setupPIN);
diff --git a/src/setup_payload/tests/TestQRCode.cpp b/src/setup_payload/tests/TestQRCode.cpp
index 6ca349c..6482b9a 100644
--- a/src/setup_payload/tests/TestQRCode.cpp
+++ b/src/setup_payload/tests/TestQRCode.cpp
@@ -307,6 +307,13 @@
invalid.SetRaw(static_cast<uint8_t>(invalid.Raw() + 1));
test_payload.rendezvousInformation.SetValue(invalid);
EXPECT_EQ(test_payload.isValidQRCodePayload(), false);
+ // When validating in Consume mode, unknown rendezvous flags are OK.
+ EXPECT_TRUE(test_payload.isValidQRCodePayload(PayloadContents::ValidationMode::kConsume));
+ test_payload.rendezvousInformation.SetValue(RendezvousInformationFlags(0xff));
+ EXPECT_TRUE(test_payload.isValidQRCodePayload(PayloadContents::ValidationMode::kConsume));
+ // Rendezvous information is still required even in Consume mode.
+ test_payload.rendezvousInformation.ClearValue();
+ EXPECT_FALSE(test_payload.isValidQRCodePayload(PayloadContents::ValidationMode::kConsume));
// test invalid setup PIN
test_payload = payload;