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 badb11f5097014..7e65f2ae525471 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 @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/bridge-app/bridge-common/bridge-app.matter b/examples/bridge-app/bridge-common/bridge-app.matter index 18c6dc420112d8..1f21b34ed80857 100644 --- a/examples/bridge-app/bridge-common/bridge-app.matter +++ b/examples/bridge-app/bridge-common/bridge-app.matter @@ -42,7 +42,7 @@ client cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } @@ -104,7 +104,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/door-lock-app/door-lock-common/door-lock-app.matter b/examples/door-lock-app/door-lock-common/door-lock-app.matter index a09ae7ec0b5750..ce063313690ddc 100644 --- a/examples/door-lock-app/door-lock-common/door-lock-app.matter +++ b/examples/door-lock-app/door-lock-common/door-lock-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.matter b/examples/light-switch-app/light-switch-common/light-switch-app.matter index e79b5e23efc08a..7ca2ce1f7b3d65 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.matter +++ b/examples/light-switch-app/light-switch-common/light-switch-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/lighting-app/lighting-common/lighting-app.matter b/examples/lighting-app/lighting-common/lighting-app.matter index df7b51a7518649..c697dc9524683a 100644 --- a/examples/lighting-app/lighting-common/lighting-app.matter +++ b/examples/lighting-app/lighting-common/lighting-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/lock-app/lock-common/lock-app.matter b/examples/lock-app/lock-common/lock-app.matter index 9107a06c99bf80..d658430da14f54 100644 --- a/examples/lock-app/lock-common/lock-app.matter +++ b/examples/lock-app/lock-common/lock-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/log-source-app/log-source-common/log-source-app.matter b/examples/log-source-app/log-source-common/log-source-app.matter index d6196305deaa03..800f9564284329 100644 --- a/examples/log-source-app/log-source-common/log-source-app.matter +++ b/examples/log-source-app/log-source-common/log-source-app.matter @@ -37,7 +37,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/ota-provider-app/ota-provider-common/ota-provider-app.matter b/examples/ota-provider-app/ota-provider-common/ota-provider-app.matter index 35cd61bba64fa9..f2a77bbc0a7c43 100644 --- a/examples/ota-provider-app/ota-provider-common/ota-provider-app.matter +++ b/examples/ota-provider-app/ota-provider-common/ota-provider-app.matter @@ -42,7 +42,7 @@ client cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } @@ -104,7 +104,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter b/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter index 488b02e24eabea..d2f259dfc59c5c 100644 --- a/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter +++ b/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/pump-app/pump-common/pump-app.matter b/examples/pump-app/pump-common/pump-app.matter index f5a5e6ba7b1166..402c3b64d7fa1e 100644 --- a/examples/pump-app/pump-common/pump-app.matter +++ b/examples/pump-app/pump-common/pump-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/pump-controller-app/pump-controller-common/pump-controller-app.matter b/examples/pump-controller-app/pump-controller-common/pump-controller-app.matter index bba78f8dd84786..17c76cbb874026 100644 --- a/examples/pump-controller-app/pump-controller-common/pump-controller-app.matter +++ b/examples/pump-controller-app/pump-controller-common/pump-controller-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/temperature-measurement-app/esp32/main/temperature-measurement.matter b/examples/temperature-measurement-app/esp32/main/temperature-measurement.matter index 28bed31546655d..072068a81de046 100644 --- a/examples/temperature-measurement-app/esp32/main/temperature-measurement.matter +++ b/examples/temperature-measurement-app/esp32/main/temperature-measurement.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/thermostat/thermostat-common/thermostat.matter b/examples/thermostat/thermostat-common/thermostat.matter index d63e70076dd7b8..b2618a97fb277e 100644 --- a/examples/thermostat/thermostat-common/thermostat.matter +++ b/examples/thermostat/thermostat-common/thermostat.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/tv-app/tv-common/tv-app.matter b/examples/tv-app/tv-common/tv-app.matter index 9d51b1683ef775..fc7404bfaa70a0 100644 --- a/examples/tv-app/tv-common/tv-app.matter +++ b/examples/tv-app/tv-common/tv-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter b/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter index 854e02bdb1e5b2..6d212c00ca1217 100644 --- a/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter +++ b/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/examples/window-app/common/window-app.matter b/examples/window-app/common/window-app.matter index f45ad80b652656..5681c8f3911047 100644 --- a/examples/window-app/common/window-app.matter +++ b/examples/window-app/common/window-app.matter @@ -42,7 +42,7 @@ server cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/scripts/tests/cirque_tests.sh b/scripts/tests/cirque_tests.sh index 2f7457005c5c76..4d04aae9e0af8d 100755 --- a/scripts/tests/cirque_tests.sh +++ b/scripts/tests/cirque_tests.sh @@ -37,6 +37,7 @@ OT_SIMULATION_CACHE_STAMP_FILE="$CIRQUE_CACHE_PATH/ot-simulation.commit" CIRQUE_TESTS=( "EchoTest" "EchoOverTcpTest" + "FailsafeTest" "MobileDeviceTest" "CommissioningTest" "InteractionModelTest" diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index b22911e56c5c8c..1e74fc875974e6 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,9 @@ using namespace chip::Access; namespace AccessControlCluster = chip::app::Clusters::AccessControl; +// TODO(#13590): generated code doesn't automatically handle max length so do it manually +constexpr int kExtensionDataMaxLength = 128; + namespace { struct Subject @@ -355,18 +359,17 @@ class AccessControlAttribute : public chip::app::AttributeAccessInterface CHIP_ERROR ReadAcl(AttributeValueEncoder & aEncoder); CHIP_ERROR ReadExtension(AttributeValueEncoder & aEncoder); CHIP_ERROR WriteAcl(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); - CHIP_ERROR WriteExtension(AttributeValueDecoder & aDecoder); + CHIP_ERROR WriteExtension(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); }; constexpr uint16_t AccessControlAttribute::ClusterRevision; -CHIP_ERROR LogEntryChangedEvent(const AccessControl::Entry & entry, const Access::SubjectDescriptor & subjectDescriptor, - AccessControlCluster::ChangeTypeEnum changeType) +CHIP_ERROR LogAclChangedEvent(const AccessControl::Entry & entry, const Access::SubjectDescriptor & subjectDescriptor, + AccessControlCluster::ChangeTypeEnum changeType) { CHIP_ERROR err; // Record AccessControlEntry event - EventNumber eventNumber; DataModel::Nullable adminNodeID; DataModel::Nullable adminPasscodeID; DataModel::Nullable latestValue; @@ -432,10 +435,39 @@ CHIP_ERROR LogEntryChangedEvent(const AccessControl::Entry & entry, const Access AccessControlCluster::Events::AccessControlEntryChanged::Type event{ adminNodeID, adminPasscodeID, changeType, latestValue, subjectDescriptor.fabricIndex }; + EventNumber eventNumber; err = LogEvent(event, 0, eventNumber); if (CHIP_NO_ERROR != err) { - ChipLogError(DataManagement, "AccessControlCluster: log event failed"); + ChipLogError(DataManagement, "AccessControlCluster: log event failed %" CHIP_ERROR_FORMAT, err.Format()); + } + + return err; +} + +CHIP_ERROR LogExtensionChangedEvent(const AccessControlCluster::Structs::ExtensionEntry::Type & item, + const Access::SubjectDescriptor & subjectDescriptor, + AccessControlCluster::ChangeTypeEnum changeType) +{ + AccessControlCluster::Events::AccessControlExtensionChanged::Type event{ .changeType = changeType, + .adminFabricIndex = subjectDescriptor.fabricIndex }; + + if (subjectDescriptor.authMode == Access::AuthMode::kCase) + { + event.adminNodeID.SetNonNull(subjectDescriptor.subject); + } + else if (subjectDescriptor.authMode == Access::AuthMode::kPase) + { + event.adminPasscodeID.SetNonNull(PAKEKeyIdFromNodeId(subjectDescriptor.subject)); + } + + event.latestValue.SetNonNull(item); + + EventNumber eventNumber; + CHIP_ERROR err = LogEvent(event, 0, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(DataManagement, "AccessControlCluster: log event failed %" CHIP_ERROR_FORMAT, err.Format()); } return err; @@ -476,7 +508,31 @@ CHIP_ERROR AccessControlAttribute::ReadAcl(AttributeValueEncoder & aEncoder) CHIP_ERROR AccessControlAttribute::ReadExtension(AttributeValueEncoder & aEncoder) { - return aEncoder.EncodeEmptyList(); + auto & storage = Server::GetInstance().GetPersistentStorage(); + DefaultStorageKeyAllocator key; + + auto & fabrics = Server::GetInstance().GetFabricTable(); + + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + for (auto & fabric : fabrics) + { + uint8_t buffer[kExtensionDataMaxLength] = { 0 }; + uint16_t size = static_cast(sizeof(buffer)); + CHIP_ERROR errStorage = storage.SyncGetKeyValue(key.AccessControlExtensionEntry(fabric.GetFabricIndex()), buffer, size); + ReturnErrorCodeIf(errStorage == CHIP_ERROR_BUFFER_TOO_SMALL, CHIP_ERROR_INCORRECT_STATE); + if (errStorage == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + continue; + } + ReturnErrorOnFailure(errStorage); + AccessControlCluster::Structs::ExtensionEntry::Type item = { + .data = ByteSpan(buffer, size), + .fabricIndex = fabric.GetFabricIndex(), + }; + ReturnErrorOnFailure(encoder.Encode(item)); + } + return CHIP_NO_ERROR; + }); } CHIP_ERROR AccessControlAttribute::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) @@ -486,7 +542,7 @@ CHIP_ERROR AccessControlAttribute::Write(const ConcreteDataAttributePath & aPath case AccessControlCluster::Attributes::Acl::Id: return WriteAcl(aPath, aDecoder); case AccessControlCluster::Attributes::Extension::Id: - return WriteExtension(aDecoder); + return WriteExtension(aPath, aDecoder); } return CHIP_NO_ERROR; @@ -520,7 +576,8 @@ CHIP_ERROR AccessControlAttribute::WriteAcl(const ConcreteDataAttributePath & aP ReturnErrorOnFailure(list.ComputeSize(&newCount)); ReturnErrorOnFailure(GetAccessControl().GetMaxEntryCount(maxCount)); VerifyOrReturnError(allCount >= oldCount, CHIP_ERROR_INTERNAL); - VerifyOrReturnError(static_cast(allCount - oldCount + newCount) <= maxCount, CHIP_ERROR_INVALID_LIST_LENGTH); + VerifyOrReturnError(static_cast(allCount - oldCount + newCount) <= maxCount, + CHIP_IM_GLOBAL_STATUS(ConstraintError)); auto iterator = list.begin(); size_t i = 0; @@ -529,14 +586,14 @@ CHIP_ERROR AccessControlAttribute::WriteAcl(const ConcreteDataAttributePath & aP if (i < oldCount) { ReturnErrorOnFailure(GetAccessControl().UpdateEntry(i, iterator.GetValue().entry, &accessingFabricIndex)); - ReturnErrorOnFailure(LogEntryChangedEvent(iterator.GetValue().entry, aDecoder.GetSubjectDescriptor(), - AccessControlCluster::ChangeTypeEnum::kChanged)); + ReturnErrorOnFailure(LogAclChangedEvent(iterator.GetValue().entry, aDecoder.GetSubjectDescriptor(), + AccessControlCluster::ChangeTypeEnum::kChanged)); } else { ReturnErrorOnFailure(GetAccessControl().CreateEntry(nullptr, iterator.GetValue().entry, &accessingFabricIndex)); - ReturnErrorOnFailure(LogEntryChangedEvent(iterator.GetValue().entry, aDecoder.GetSubjectDescriptor(), - AccessControlCluster::ChangeTypeEnum::kAdded)); + ReturnErrorOnFailure(LogAclChangedEvent(iterator.GetValue().entry, aDecoder.GetSubjectDescriptor(), + AccessControlCluster::ChangeTypeEnum::kAdded)); } ++i; } @@ -549,7 +606,7 @@ CHIP_ERROR AccessControlAttribute::WriteAcl(const ConcreteDataAttributePath & aP --oldCount; ReturnErrorOnFailure(GetAccessControl().ReadEntry(oldCount, entry, &accessingFabricIndex)); ReturnErrorOnFailure( - LogEntryChangedEvent(entry, aDecoder.GetSubjectDescriptor(), AccessControlCluster::ChangeTypeEnum::kRemoved)); + LogAclChangedEvent(entry, aDecoder.GetSubjectDescriptor(), AccessControlCluster::ChangeTypeEnum::kRemoved)); ReturnErrorOnFailure(GetAccessControl().DeleteEntry(oldCount, &accessingFabricIndex)); } } @@ -560,7 +617,7 @@ CHIP_ERROR AccessControlAttribute::WriteAcl(const ConcreteDataAttributePath & aP ReturnErrorOnFailure(GetAccessControl().CreateEntry(nullptr, item.entry, &accessingFabricIndex)); ReturnErrorOnFailure( - LogEntryChangedEvent(item.entry, aDecoder.GetSubjectDescriptor(), AccessControlCluster::ChangeTypeEnum::kAdded)); + LogAclChangedEvent(item.entry, aDecoder.GetSubjectDescriptor(), AccessControlCluster::ChangeTypeEnum::kAdded)); } else { @@ -570,10 +627,79 @@ CHIP_ERROR AccessControlAttribute::WriteAcl(const ConcreteDataAttributePath & aP return CHIP_NO_ERROR; } -CHIP_ERROR AccessControlAttribute::WriteExtension(AttributeValueDecoder & aDecoder) +CHIP_ERROR AccessControlAttribute::WriteExtension(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) { - DataModel::DecodableList list; - ReturnErrorOnFailure(aDecoder.Decode(list)); + auto & storage = Server::GetInstance().GetPersistentStorage(); + DefaultStorageKeyAllocator key; + + FabricIndex accessingFabricIndex = aDecoder.AccessingFabricIndex(); + + uint8_t buffer[kExtensionDataMaxLength] = { 0 }; + uint16_t size = static_cast(sizeof(buffer)); + CHIP_ERROR errStorage = storage.SyncGetKeyValue(key.AccessControlExtensionEntry(accessingFabricIndex), buffer, size); + ReturnErrorCodeIf(errStorage == CHIP_ERROR_BUFFER_TOO_SMALL, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(errStorage != CHIP_NO_ERROR && errStorage != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND, errStorage); + + if (!aPath.IsListItemOperation()) + { + DataModel::DecodableList list; + ReturnErrorOnFailure(aDecoder.Decode(list)); + + size_t count = 0; + ReturnErrorOnFailure(list.ComputeSize(&count)); + + if (count == 0) + { + ReturnErrorCodeIf(errStorage == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND, CHIP_NO_ERROR); + ReturnErrorOnFailure(storage.SyncDeleteKeyValue(key.AccessControlExtensionEntry(accessingFabricIndex))); + AccessControlCluster::Structs::ExtensionEntry::Type item = { + .data = ByteSpan(buffer, size), + .fabricIndex = accessingFabricIndex, + }; + ReturnErrorOnFailure( + LogExtensionChangedEvent(item, aDecoder.GetSubjectDescriptor(), AccessControlCluster::ChangeTypeEnum::kRemoved)); + } + else if (count == 1) + { + auto iterator = list.begin(); + if (!iterator.Next()) + { + ReturnErrorOnFailure(iterator.GetStatus()); + // If counted an item, iterator doesn't return it, iterator has no error, that's bad. + return CHIP_ERROR_INCORRECT_STATE; + } + auto & item = iterator.GetValue(); + // TODO(#13590): generated code doesn't automatically handle max length so do it manually + ReturnErrorCodeIf(item.data.size() > kExtensionDataMaxLength, CHIP_IM_GLOBAL_STATUS(ConstraintError)); + ReturnErrorOnFailure(storage.SyncSetKeyValue(key.AccessControlExtensionEntry(accessingFabricIndex), item.data.data(), + static_cast(item.data.size()))); + ReturnErrorOnFailure(LogExtensionChangedEvent(item, aDecoder.GetSubjectDescriptor(), + errStorage == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND + ? AccessControlCluster::ChangeTypeEnum::kAdded + : AccessControlCluster::ChangeTypeEnum::kChanged)); + } + else + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + } + else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem) + { + ReturnErrorCodeIf(errStorage != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND, CHIP_IM_GLOBAL_STATUS(ConstraintError)); + AccessControlCluster::Structs::ExtensionEntry::DecodableType item; + ReturnErrorOnFailure(aDecoder.Decode(item)); + // TODO(#13590): generated code doesn't automatically handle max length so do it manually + ReturnErrorCodeIf(item.data.size() > kExtensionDataMaxLength, CHIP_IM_GLOBAL_STATUS(ConstraintError)); + ReturnErrorOnFailure(storage.SyncSetKeyValue(key.AccessControlExtensionEntry(accessingFabricIndex), item.data.data(), + static_cast(item.data.size()))); + ReturnErrorOnFailure( + LogExtensionChangedEvent(item, aDecoder.GetSubjectDescriptor(), AccessControlCluster::ChangeTypeEnum::kAdded)); + } + else + { + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; + } + return CHIP_NO_ERROR; } diff --git a/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp b/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp index 7ae2421b5d5875..a022e26560b6ff 100644 --- a/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp +++ b/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -104,9 +105,12 @@ bool emberAfAdministratorCommissioningClusterOpenCommissioningWindowCallback( ChipLogProgress(Zcl, "Received command to open commissioning window"); - FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); - FabricInfo * fabricInfo = Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabricIndex); + FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); + FabricInfo * fabricInfo = Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabricIndex); + DeviceLayer::FailSafeContext & failSafeContext = DeviceLayer::DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); + VerifyOrExit(fabricInfo != nullptr, status.Emplace(StatusCode::EMBER_ZCL_STATUS_CODE_PAKE_PARAMETER_ERROR)); + VerifyOrExit(!failSafeContext.IsFailSafeArmed(), status.Emplace(StatusCode::EMBER_ZCL_STATUS_CODE_BUSY)); VerifyOrExit(Server::GetInstance().GetCommissioningWindowManager().CommissioningWindowStatus() == CommissioningWindowStatus::kWindowNotOpen, @@ -165,11 +169,15 @@ bool emberAfAdministratorCommissioningClusterOpenBasicCommissioningWindowCallbac FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); FabricInfo * fabricInfo = Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabricIndex); + chip::DeviceLayer::FailSafeContext & failSafeContext = + DeviceLayer::DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); + VerifyOrExit(fabricInfo != nullptr, status.Emplace(StatusCode::EMBER_ZCL_STATUS_CODE_PAKE_PARAMETER_ERROR)); VerifyOrExit(Server::GetInstance().GetCommissioningWindowManager().CommissioningWindowStatus() == CommissioningWindowStatus::kWindowNotOpen, status.Emplace(StatusCode::EMBER_ZCL_STATUS_CODE_BUSY)); + VerifyOrExit(!failSafeContext.IsFailSafeArmed(), status.Emplace(StatusCode::EMBER_ZCL_STATUS_CODE_BUSY)); VerifyOrExit(commissioningTimeout <= CommissioningWindowManager::MaxCommissioningTimeout(), globalStatus = InteractionModel::Status::InvalidCommand); VerifyOrExit(commissioningTimeout >= CommissioningWindowManager::MinCommissioningTimeout(), diff --git a/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp b/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp index 6330afe765a14d..056519629bf29b 100644 --- a/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp +++ b/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -162,22 +164,36 @@ bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * FabricIndex accessingFabricIndex = commandObj->GetAccessingFabricIndex(); + // We do not allow CASE connections to arm the failsafe for the first time while the commissioning window is open in order + // to allow commissioners the opportunity to obtain this failsafe for the purpose of commissioning if (!failSafeContext.IsFailSafeBusy() && (!failSafeContext.IsFailSafeArmed() || failSafeContext.MatchesFabricIndex(accessingFabricIndex))) { - if (commandData.expiryLengthSeconds == 0) + // We do not allow CASE connections to arm the failsafe for the first time while the commissioning window is open in order + // to allow commissioners the opportunity to obtain this failsafe for the purpose of commissioning + if (!failSafeContext.IsFailSafeArmed() && + Server::GetInstance().GetCommissioningWindowManager().CommissioningWindowStatus() != + AdministratorCommissioning::CommissioningWindowStatus::kWindowNotOpen && + commandObj->GetSubjectDescriptor().authMode == Access::AuthMode::kCase) + { + response.errorCode = CommissioningError::kBusyWithOtherAdmin; + commandObj->AddResponse(commandPath, response); + } + else if (commandData.expiryLengthSeconds == 0) { // Force the timer to expire immediately. failSafeContext.ForceFailSafeTimerExpiry(); + response.errorCode = CommissioningError::kOk; + commandObj->AddResponse(commandPath, response); } else { CheckSuccess( failSafeContext.ArmFailSafe(accessingFabricIndex, System::Clock::Seconds16(commandData.expiryLengthSeconds)), Failure); + response.errorCode = CommissioningError::kOk; + commandObj->AddResponse(commandPath, response); } - response.errorCode = CommissioningError::kOk; - commandObj->AddResponse(commandPath, response); } else { diff --git a/src/app/server/CommissioningWindowManager.cpp b/src/app/server/CommissioningWindowManager.cpp index b62aef382a75a8..f2169f37835a21 100644 --- a/src/app/server/CommissioningWindowManager.cpp +++ b/src/app/server/CommissioningWindowManager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include using namespace chip::app::Clusters; using namespace chip::System::Clock; @@ -94,6 +95,11 @@ void CommissioningWindowManager::ResetState() void CommissioningWindowManager::Cleanup() { StopAdvertisement(/* aShuttingDown = */ false); + DeviceLayer::FailSafeContext & failSafeContext = DeviceLayer::DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); + if (failSafeContext.IsFailSafeArmed()) + { + failSafeContext.ForceFailSafeTimerExpiry(); + } ResetState(); } @@ -161,6 +167,23 @@ void CommissioningWindowManager::OnSessionEstablished() DeviceLayer::PlatformMgr().AddEventHandler(OnPlatformEventWrapper, reinterpret_cast(this)); StopAdvertisement(/* aShuttingDown = */ false); + + DeviceLayer::FailSafeContext & failSafeContext = DeviceLayer::DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); + // This should never be armed because we don't allow CASE sessions to arm the failsafe when the commissioning window is open and + // we check that the failsafe is not armed before opening the commissioning window. None the less, it is good to double-check. + if (failSafeContext.IsFailSafeArmed()) + { + ChipLogError(AppServer, "Error - arm failsafe is already armed on PASE session establishment completion"); + } + else + { + err = failSafeContext.ArmFailSafe(kUndefinedFabricId, System::Clock::Seconds16(60)); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Error arming failsafe on PASE session establishment completion"); + } + } + ChipLogProgress(AppServer, "Device completed Rendezvous process"); } @@ -169,6 +192,8 @@ CHIP_ERROR CommissioningWindowManager::OpenCommissioningWindow(Seconds16 commiss VerifyOrReturnError(commissioningTimeout <= MaxCommissioningTimeout() && commissioningTimeout >= mMinCommissioningTimeoutOverride.ValueOr(MinCommissioningTimeout()), CHIP_ERROR_INVALID_ARGUMENT); + DeviceLayer::FailSafeContext & failSafeContext = DeviceLayer::DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); + VerifyOrReturnError(!failSafeContext.IsFailSafeArmed(), CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(DeviceLayer::SystemLayer().StartTimer(commissioningTimeout, HandleCommissioningWindowTimeout, this)); diff --git a/src/app/zap-templates/zcl/data-model/chip/access-control-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/access-control-cluster.xml index fa7d0eb82254ce..561925936784dc 100644 --- a/src/app/zap-templates/zcl/data-model/chip/access-control-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/access-control-cluster.xml @@ -58,8 +58,8 @@ limitations under the License. - - + + diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index a0d033a57e7740..3f7a2f9a8fac1b 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -42,7 +42,7 @@ client cluster AccessControl = 31 { } struct ExtensionEntry { - OCTET_STRING<254> data = 1; + OCTET_STRING<128> data = 1; fabric_idx fabricIndex = 254; } diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 874d636127ae6b..ca2f1419dd4abe 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -35,6 +35,7 @@ from chip.ChipStack import * import chip.FabricAdmin import copy +import secrets logger = logging.getLogger('PythonMatterControllerTEST') logger.setLevel(logging.INFO) @@ -269,6 +270,83 @@ def TestKeyExchange(self, ip: str, setuppin: int, nodeid: int): def TestUsedTestCommissioner(self): return self.devCtrl.GetTestCommissionerUsed() + def TestFailsafe(self, nodeid: int): + self.logger.info("Testing arm failsafe") + + self.logger.info("Setting failsafe on CASE connection") + err, resp = self.devCtrl.ZCLSend("GeneralCommissioning", "ArmFailSafe", nodeid, + 0, 0, dict(expiryLengthSeconds=60, breadcrumb=1), blocking=True) + if err != 0: + self.logger.error( + "Failed to send arm failsafe command error is {} with im response{}".format(err, resp)) + return False + + if resp.errorCode is not Clusters.GeneralCommissioning.Enums.CommissioningError.kOk: + self.logger.error( + "Incorrect response received from arm failsafe - wanted OK, received {}".format(resp)) + return False + + self.logger.info( + "Attempting to open basic commissioning window - this should fail since the failsafe is armed") + try: + res = asyncio.run(self.devCtrl.SendCommand( + nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180), timedRequestTimeoutMs=10000)) + # we actually want the exception here because we want to see a failure, so return False here + self.logger.error( + 'Incorrectly succeeded in opening basic commissioning window') + return False + except Exception as ex: + pass + + # TODO: pipe through the commissioning window opener so we can test enhanced properly. The pake verifier is just garbage because none of of the functions to calculate + # it or serialize it are available right now. However, this command should fail BEFORE that becomes an issue. + discriminator = 1111 + salt = secrets.token_bytes(16) + iterations = 2000 + # not the right size or the right contents, but it won't matter + verifier = secrets.token_bytes(32) + self.logger.info( + "Attempting to open enhanced commissioning window - this should fail since the failsafe is armed") + try: + res = asyncio.run(self.devCtrl.SendCommand(nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenCommissioningWindow( + commissioningTimeout=180, PAKEVerifier=verifier, discriminator=discriminator, iterations=iterations, salt=salt), timedRequestTimeoutMs=10000)) + # we actually want the exception here because we want to see a failure, so return False here + self.logger.error( + 'Incorrectly succeeded in opening enhanced commissioning window') + return False + except Exception as ex: + pass + + self.logger.info("Disarming failsafe on CASE connection") + err, resp = self.devCtrl.ZCLSend("GeneralCommissioning", "ArmFailSafe", nodeid, + 0, 0, dict(expiryLengthSeconds=0, breadcrumb=1), blocking=True) + if err != 0: + self.logger.error( + "Failed to send arm failsafe command error is {} with im response{}".format(err, resp)) + return False + + self.logger.info( + "Opening Commissioning Window - this should succeed since the failsafe was just disarmed") + try: + res = asyncio.run(self.devCtrl.SendCommand( + nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180), timedRequestTimeoutMs=10000)) + except Exception as ex: + self.logger.error( + 'Failed to open commissioning window after disarming failsafe') + return False + + self.logger.info( + "Attempting to arm failsafe over CASE - this should fail since the commissioning window is open") + err, resp = self.devCtrl.ZCLSend("GeneralCommissioning", "ArmFailSafe", nodeid, + 0, 0, dict(expiryLengthSeconds=60, breadcrumb=1), blocking=True) + if err != 0: + self.logger.error( + "Failed to send arm failsafe command error is {} with im response{}".format(err, resp)) + return False + if resp.errorCode is Clusters.GeneralCommissioning.Enums.CommissioningError.kBusyWithOtherAdmin: + return True + return False + async def TestMultiFabric(self, ip: str, setuppin: int, nodeid: int): self.logger.info("Opening Commissioning Window") diff --git a/src/controller/python/test/test_scripts/failsafe_tests.py b/src/controller/python/test/test_scripts/failsafe_tests.py new file mode 100755 index 00000000000000..fb21f0826b0523 --- /dev/null +++ b/src/controller/python/test/test_scripts/failsafe_tests.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Commissioning test. +import os +import sys +from optparse import OptionParser +from base import TestFail, TestTimeout, BaseTestHelper, FailIfNot, logger +from cluster_objects import NODE_ID, ClusterObjectTests +from network_commissioning import NetworkCommissioningTests +import asyncio + +# The thread network dataset tlv for testing, splited into T-L-V. + +TEST_THREAD_NETWORK_DATASET_TLV = "0e080000000000010000" + \ + "000300000c" + \ + "35060004001fffe0" + \ + "0208fedcba9876543210" + \ + "0708fd00000000001234" + \ + "0510ffeeddccbbaa99887766554433221100" + \ + "030e54657374696e674e6574776f726b" + \ + "0102d252" + \ + "041081cb3b2efa781cc778397497ff520fa50c0302a0ff" +# Network id, for the thread network, current a const value, will be changed to XPANID of the thread network. +TEST_THREAD_NETWORK_ID = "fedcba9876543210" +TEST_DISCRIMINATOR = 3840 + +ENDPOINT_ID = 0 +LIGHTING_ENDPOINT_ID = 1 +GROUP_ID = 0 + + +def main(): + optParser = OptionParser() + optParser.add_option( + "-t", + "--timeout", + action="store", + dest="testTimeout", + default=75, + type='int', + help="The program will return with timeout after specified seconds.", + metavar="", + ) + optParser.add_option( + "-a", + "--address", + action="store", + dest="deviceAddress", + default='', + type='str', + help="Address of the device", + metavar="", + ) + optParser.add_option( + "-p", + "--paa-trust-store-path", + action="store", + dest="paaTrustStorePath", + default='', + type='str', + help="Path that contains valid and trusted PAA Root Certificates.", + metavar="" + ) + + (options, remainingArgs) = optParser.parse_args(sys.argv[1:]) + + timeoutTicker = TestTimeout(options.testTimeout) + timeoutTicker.start() + + test = BaseTestHelper( + nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=False) + + logger.info("Testing discovery") + FailIfNot(test.TestDiscovery(discriminator=TEST_DISCRIMINATOR), + "Failed to discover any devices.") + + FailIfNot(test.SetNetworkCommissioningParameters(dataset=TEST_THREAD_NETWORK_DATASET_TLV), + "Failed to finish network commissioning") + + logger.info("Testing key exchange") + FailIfNot(test.TestKeyExchange(ip=options.deviceAddress, + setuppin=20202021, + nodeid=1), + "Failed to finish key exchange") + + FailIfNot(test.TestFailsafe(nodeid=1), "Failed failsafe test") + + timeoutTicker.stop() + + logger.info("Test finished") + + # TODO: Python device controller cannot be shutdown clean sometimes and will block on AsyncDNSResolverSockets shutdown. + # Call os._exit(0) to force close it. + os._exit(0) + + +if __name__ == "__main__": + try: + main() + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index 4dab3c77123b02..48b97e766b496e 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -52,7 +52,8 @@ class DefaultStorageKeyAllocator // FailSafeContext const char * FailSafeContextKey() { return Format("g/fsc"); } - // Access Control List + // Access Control + const char * AccessControlExtensionEntry(FabricIndex fabric) { return Format("f/%x/ac/1", fabric); } // TODO: We should probably store the fabric-specific parts of the ACL list // under keys starting with "f/%x/". diff --git a/src/test_driver/linux-cirque/FailsafeTest.py b/src/test_driver/linux-cirque/FailsafeTest.py new file mode 100755 index 00000000000000..7a6fd503b7ad8c --- /dev/null +++ b/src/test_driver/linux-cirque/FailsafeTest.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2021 Project CHIP Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import logging +import os +import pprint +import time +import sys + +from helper.CHIPTestBase import CHIPVirtualHome + +logger = logging.getLogger('MobileDeviceTest') +logger.setLevel(logging.INFO) + +sh = logging.StreamHandler() +sh.setFormatter( + logging.Formatter( + '%(asctime)s [%(name)s] %(levelname)s %(message)s')) +logger.addHandler(sh) + +CHIP_PORT = 5540 + +CIRQUE_URL = "http://localhost:5000" +CHIP_REPO = os.path.join(os.path.abspath( + os.path.dirname(__file__)), "..", "..", "..") +TEST_EXTPANID = "fedcba9876543210" +TEST_DISCRIMINATOR = 3840 +MATTER_DEVELOPMENT_PAA_ROOT_CERTS = "credentials/development/paa-root-certs" + +DEVICE_CONFIG = { + 'device0': { + 'type': 'MobileDevice', + 'base_image': 'connectedhomeip/chip-cirque-device-base', + 'capability': ['TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 100}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + }, + 'device1': { + 'type': 'CHIPEndDevice', + 'base_image': 'connectedhomeip/chip-cirque-device-base', + 'capability': ['Thread', 'TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 100}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + } +} + + +class TestFailsafe(CHIPVirtualHome): + def __init__(self, device_config): + super().__init__(CIRQUE_URL, device_config) + self.logger = logger + + def setup(self): + self.initialize_home() + + def test_routine(self): + self.run_controller_test() + + def run_controller_test(self): + ethernet_ip = [device['description']['ipv6_addr'] for device in self.non_ap_devices + if device['type'] == 'CHIPEndDevice'][0] + server_ids = [device['id'] for device in self.non_ap_devices + if device['type'] == 'CHIPEndDevice'] + req_ids = [device['id'] for device in self.non_ap_devices + if device['type'] == 'MobileDevice'] + + for server in server_ids: + self.execute_device_cmd(server, "CHIPCirqueDaemon.py -- run gdb -return-child-result -q -ex \"set pagination off\" -ex run -ex \"bt 25\" --args {} --thread --discriminator {}".format( + os.path.join(CHIP_REPO, "out/debug/standalone/chip-all-clusters-app"), TEST_DISCRIMINATOR)) + + self.reset_thread_devices(server_ids) + + req_device_id = req_ids[0] + + self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip-0.0-cp37-abi3-linux_x86_64.whl"))) + + command = "gdb -return-child-result -q -ex run -ex bt --args python3 {} -t 150 -a {} --paa-trust-store-path {}".format( + os.path.join( + CHIP_REPO, "src/controller/python/test/test_scripts/failsafe_tests.py"), + ethernet_ip, + os.path.join(CHIP_REPO, MATTER_DEVELOPMENT_PAA_ROOT_CERTS)) + ret = self.execute_device_cmd(req_device_id, command) + + self.assertEqual(ret['return_code'], '0', + "Test failed: non-zero return code") + + +if __name__ == "__main__": + sys.exit(TestFailsafe(DEVICE_CONFIG).run_test()) diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp index 896d137b6ee1bf..d462d03636fb60 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp @@ -4683,9 +4683,13 @@ CHIP_ERROR Type::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricInde CHIP_ERROR Type::DoEncode(TLV::TLVWriter & writer, TLV::Tag tag, const Optional & accessingFabricIndex) const { + bool includeSensitive = !accessingFabricIndex.HasValue() || (accessingFabricIndex.Value() == fabricIndex); TLV::TLVType outer; ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer)); - ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(to_underlying(Fields::kData)), data)); + if (includeSensitive) + { + ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(to_underlying(Fields::kData)), data)); + } if (accessingFabricIndex.HasValue()) { ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(to_underlying(Fields::kFabricIndex)), fabricIndex));