Use DataModel::Provider provided command information to handle command validation (#35897)
* A first pass to implement better invoke logic decoupling
* Fix some includes
* Fix some includes
* More include fixes
* Fix dependency to make things build
* Fixed tests
* Fix includes
* Restyle
* Fix naming typo
* Fix tizen build
* Fix condition typo
* Keep group command logic to use continue instead of status return ... there is no status to return
* Minor update to kick restyler
* one more decoupling update: sligtly more inefficient, however likely this is not important
* Renamed based on code review feedback
* Restyled by clang-format
* Fix some code review comments
* Review comment: default privilege to operate
* Fix up renames
* Fix hex prefix
* Restyled by clang-format
* Fix cirque after log formatting change. Load bearing log ....
* Revert file change that I did not mean to change
* Update src/app/CommandHandlerImpl.cpp
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Remove separate member for accessing fabric index
* Restyled by clang-format
---------
Co-authored-by: Andrei Litvin <andreilitvin@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/examples/lighting-app/tizen/src/DBusInterface.cpp b/examples/lighting-app/tizen/src/DBusInterface.cpp
index 36c0f19..5484c7d 100644
--- a/examples/lighting-app/tizen/src/DBusInterface.cpp
+++ b/examples/lighting-app/tizen/src/DBusInterface.cpp
@@ -42,9 +42,12 @@
{
public:
using Status = Protocols::InteractionModel::Status;
- void OnDone(CommandHandlerImpl & apCommandObj) {}
- void DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & apPayload) {}
- Status CommandExists(const ConcreteCommandPath & aCommandPath) { return Status::Success; }
+
+ void OnDone(CommandHandlerImpl & apCommandObj) override {}
+ void DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
+ TLV::TLVReader & apPayload) override
+ {}
+ Status ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) override { return Status::Success; }
};
DBusInterface::DBusInterface(chip::EndpointId endpointId) : mEndpointId(endpointId)
diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn
index 98e7225..f3565cf 100644
--- a/src/app/BUILD.gn
+++ b/src/app/BUILD.gn
@@ -417,6 +417,7 @@
"${chip_root}/src/access:types",
"${chip_root}/src/app/MessageDef",
"${chip_root}/src/app/data-model",
+ "${chip_root}/src/app/data-model-provider",
"${chip_root}/src/app/util:callbacks",
"${chip_root}/src/lib/support",
"${chip_root}/src/messaging",
diff --git a/src/app/CommandHandlerImpl.cpp b/src/app/CommandHandlerImpl.cpp
index 366199a..2142af6 100644
--- a/src/app/CommandHandlerImpl.cpp
+++ b/src/app/CommandHandlerImpl.cpp
@@ -22,6 +22,7 @@
#include <app/MessageDef/StatusIB.h>
#include <app/RequiredPrivilege.h>
#include <app/StatusResponse.h>
+#include <app/data-model-provider/OperationTypes.h>
#include <app/util/MatterCallbacks.h>
#include <credentials/GroupDataProvider.h>
#include <lib/core/CHIPConfig.h>
@@ -390,55 +391,16 @@
VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
{
- Status commandExists = mpCallback->CommandExists(concretePath);
- if (commandExists != Status::Success)
+ DataModel::InvokeRequest request;
+
+ request.path = concretePath;
+ request.subjectDescriptor = GetSubjectDescriptor();
+ request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke());
+
+ Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request);
+ if (preCheckStatus != Status::Success)
{
- ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint 0x%x",
- ChipLogValueMEI(concretePath.mCommandId), ChipLogValueMEI(concretePath.mClusterId),
- concretePath.mEndpointId);
- return FallibleAddStatus(concretePath, commandExists) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
- }
- }
-
- {
- Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
- Access::RequestPath requestPath{ .cluster = concretePath.mClusterId,
- .endpoint = concretePath.mEndpointId,
- .requestType = Access::RequestType::kCommandInvokeRequest,
- .entityId = concretePath.mCommandId };
- Access::Privilege requestPrivilege = RequiredPrivilege::ForInvokeCommand(concretePath);
- err = Access::GetAccessControl().Check(subjectDescriptor, requestPath, requestPrivilege);
- if (err != CHIP_NO_ERROR)
- {
- if ((err != CHIP_ERROR_ACCESS_DENIED) && (err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL))
- {
- return FallibleAddStatus(concretePath, Status::Failure) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
- }
- // TODO: when wildcard invokes are supported, handle them to discard rather than fail with status
- Status status = err == CHIP_ERROR_ACCESS_DENIED ? Status::UnsupportedAccess : Status::AccessRestricted;
- return FallibleAddStatus(concretePath, status) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
- }
- }
-
- if (CommandNeedsTimedInvoke(concretePath.mClusterId, concretePath.mCommandId) && !IsTimedInvoke())
- {
- // TODO: when wildcard invokes are supported, discard a
- // wildcard-expanded path instead of returning a status.
- return FallibleAddStatus(concretePath, Status::NeedsTimedInteraction) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
- }
-
- if (CommandIsFabricScoped(concretePath.mClusterId, concretePath.mCommandId))
- {
- // SPEC: Else if the command in the path is fabric-scoped and there is no accessing fabric,
- // a CommandStatusIB SHALL be generated with the UNSUPPORTED_ACCESS Status Code.
-
- // Fabric-scoped commands are not allowed before a specific accessing fabric is available.
- // This is mostly just during a PASE session before AddNOC.
- if (GetAccessingFabricIndex() == kUndefinedFabricIndex)
- {
- // TODO: when wildcard invokes are supported, discard a
- // wildcard-expanded path instead of returning a status.
- return FallibleAddStatus(concretePath, Status::UnsupportedAccess) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
+ return FallibleAddStatus(concretePath, preCheckStatus) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
}
}
@@ -516,11 +478,19 @@
// once up front and discard all the paths at once. Ordering with respect
// to ACL and command presence checks does not matter, because the behavior
// is the same for all of them: ignore the path.
+#if !CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
+
+ // Without data model interface, we can query individual commands.
+ // Data model interface queries commands by a full path so we need endpointID as well.
+ //
+ // Since this is a performance update and group commands are never timed,
+ // missing this should not be that noticeable.
if (CommandNeedsTimedInvoke(clusterId, commandId))
{
// Group commands are never timed.
return Status::Success;
}
+#endif
// No check for `CommandIsFabricScoped` unlike in `ProcessCommandDataIB()` since group commands
// always have an accessing fabric, by definition.
@@ -542,30 +512,21 @@
const ConcreteCommandPath concretePath(mapping.endpoint_id, clusterId, commandId);
- if (mpCallback->CommandExists(concretePath) != Status::Success)
{
- ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint 0x%x",
- ChipLogValueMEI(commandId), ChipLogValueMEI(clusterId), mapping.endpoint_id);
+ DataModel::InvokeRequest request;
- continue;
- }
+ request.path = concretePath;
+ request.subjectDescriptor = GetSubjectDescriptor();
+ request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke());
- {
- Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
- Access::RequestPath requestPath{ .cluster = concretePath.mClusterId,
- .endpoint = concretePath.mEndpointId,
- .requestType = Access::RequestType::kCommandInvokeRequest,
- .entityId = concretePath.mCommandId };
- Access::Privilege requestPrivilege = RequiredPrivilege::ForInvokeCommand(concretePath);
- err = Access::GetAccessControl().Check(subjectDescriptor, requestPath, requestPrivilege);
- if (err != CHIP_NO_ERROR)
+ Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request);
+ if (preCheckStatus != Status::Success)
{
- // NOTE: an expected error is CHIP_ERROR_ACCESS_DENIED, but there could be other unexpected errors;
- // therefore, keep processing subsequent commands, and if any errors continue, those subsequent
- // commands will likewise fail.
+ // Command failed for a specific path, but keep trying the rest of the paths.
continue;
}
}
+
if ((err = DataModelCallbacks::GetInstance()->PreCommandReceived(concretePath, GetSubjectDescriptor())) == CHIP_NO_ERROR)
{
TLV::TLVReader dataReader(commandDataReader);
diff --git a/src/app/CommandHandlerImpl.h b/src/app/CommandHandlerImpl.h
index ddd4996..2603b96 100644
--- a/src/app/CommandHandlerImpl.h
+++ b/src/app/CommandHandlerImpl.h
@@ -21,6 +21,7 @@
#include <app/CommandPathRegistry.h>
#include <app/MessageDef/InvokeRequestMessage.h>
#include <app/MessageDef/InvokeResponseMessage.h>
+#include <app/data-model-provider/OperationTypes.h>
#include <lib/core/TLV.h>
#include <lib/core/TLVDebug.h>
#include <lib/support/BitFlags.h>
@@ -50,21 +51,29 @@
*/
virtual void OnDone(CommandHandlerImpl & apCommandObj) = 0;
+ /**
+ * Perform pre-validation that the command dispatch can be performed. In particular:
+ * - check command existence/validity
+ * - validate ACL
+ * - validate timed-invoke and fabric-scoped requirements
+ *
+ * Returns Status::Success if the command can be dispatched, otherwise it will
+ * return the status to be forwarded to the client on failure.
+ *
+ * Possible error return codes:
+ * - UnsupportedEndpoint/UnsupportedCluster/UnsupportedCommand if the command path is invalid
+ * - NeedsTimedInteraction
+ * - UnsupportedAccess (ACL failure or fabric scoped without a valid fabric index)
+ * - AccessRestricted
+ */
+ virtual Protocols::InteractionModel::Status ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) = 0;
+
/*
* Upon processing of a CommandDataIB, this method is invoked to dispatch the command
* to the right server-side handler provided by the application.
*/
virtual void DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
TLV::TLVReader & apPayload) = 0;
-
- /*
- * Check to see if a command implementation exists for a specific
- * concrete command path. If it does, Success will be returned. If
- * not, one of UnsupportedEndpoint, UnsupportedCluster, or
- * UnsupportedCommand will be returned, depending on how the command
- * fails to exist.
- */
- virtual Protocols::InteractionModel::Status CommandExists(const ConcreteCommandPath & aCommandPath) = 0;
};
struct InvokeResponseParameters
diff --git a/src/app/CommandResponseSender.cpp b/src/app/CommandResponseSender.cpp
index f23786e..0b02851 100644
--- a/src/app/CommandResponseSender.cpp
+++ b/src/app/CommandResponseSender.cpp
@@ -132,10 +132,10 @@
mpCommandHandlerCallback->DispatchCommand(apCommandObj, aCommandPath, apPayload);
}
-Status CommandResponseSender::CommandExists(const ConcreteCommandPath & aCommandPath)
+Protocols::InteractionModel::Status CommandResponseSender::ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request)
{
- VerifyOrReturnValue(mpCommandHandlerCallback, Protocols::InteractionModel::Status::UnsupportedCommand);
- return mpCommandHandlerCallback->CommandExists(aCommandPath);
+ VerifyOrReturnValue(mpCommandHandlerCallback, Protocols::InteractionModel::Status::Failure);
+ return mpCommandHandlerCallback->ValidateCommandCanBeDispatched(request);
}
CHIP_ERROR CommandResponseSender::SendCommandResponse()
diff --git a/src/app/CommandResponseSender.h b/src/app/CommandResponseSender.h
index 0e03a87..f0b8f11 100644
--- a/src/app/CommandResponseSender.h
+++ b/src/app/CommandResponseSender.h
@@ -64,7 +64,7 @@
void DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
TLV::TLVReader & apPayload) override;
- Protocols::InteractionModel::Status CommandExists(const ConcreteCommandPath & aCommandPath) override;
+ Protocols::InteractionModel::Status ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) override;
/**
* Gets the inner exchange context object, without ownership.
diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp
index 59888fe..5f0e392 100644
--- a/src/app/InteractionModelEngine.cpp
+++ b/src/app/InteractionModelEngine.cpp
@@ -32,6 +32,9 @@
#include <app/AppConfig.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/RequiredPrivilege.h>
+#include <app/data-model-provider/ActionReturnStatus.h>
+#include <app/data-model-provider/MetadataTypes.h>
+#include <app/data-model-provider/OperationTypes.h>
#include <app/util/IMClusterCommandHandler.h>
#include <app/util/af-types.h>
#include <app/util/ember-compatibility-functions.h>
@@ -42,10 +45,9 @@
#include <lib/support/CHIPFaultInjection.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/FibonacciUtils.h>
+#include <protocols/interaction_model/StatusCode.h>
#if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
-#include <app/data-model-provider/ActionReturnStatus.h>
-
// TODO: defaulting to codegen should eventually be an application choice and not
// hard-coded in the interaction model
#include <app/codegen-data-model-provider/Instance.h>
@@ -1652,6 +1654,8 @@
DataModel::InvokeRequest request;
request.path = aCommandPath;
+ request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, apCommandObj.IsTimedInvoke());
+ request.subjectDescriptor = apCommandObj.GetSubjectDescriptor();
std::optional<DataModel::ActionReturnStatus> status = GetDataModelProvider()->Invoke(request, apPayload, &apCommandObj);
@@ -1684,7 +1688,91 @@
#endif // CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
}
-Protocols::InteractionModel::Status InteractionModelEngine::CommandExists(const ConcreteCommandPath & aCommandPath)
+Protocols::InteractionModel::Status InteractionModelEngine::ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request)
+{
+
+ Status status = CheckCommandExistence(request.path);
+
+ if (status != Status::Success)
+ {
+ ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint %u",
+ ChipLogValueMEI(request.path.mCommandId), ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId);
+ return status;
+ }
+
+ status = CheckCommandAccess(request);
+ VerifyOrReturnValue(status == Status::Success, status);
+
+ return CheckCommandFlags(request);
+}
+
+Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(const DataModel::InvokeRequest & aRequest)
+{
+ if (!aRequest.subjectDescriptor.has_value())
+ {
+ return Status::UnsupportedAccess; // we require a subject for invoke
+ }
+
+ Access::RequestPath requestPath{ .cluster = aRequest.path.mClusterId,
+ .endpoint = aRequest.path.mEndpointId,
+ .requestType = Access::RequestType::kCommandInvokeRequest,
+ .entityId = aRequest.path.mCommandId };
+#if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
+ std::optional<DataModel::CommandInfo> commandInfo = mDataModelProvider->GetAcceptedCommandInfo(aRequest.path);
+ Access::Privilege minimumRequiredPrivilege =
+ commandInfo.has_value() ? commandInfo->invokePrivilege : Access::Privilege::kOperate;
+#else
+ Access::Privilege minimumRequiredPrivilege = RequiredPrivilege::ForInvokeCommand(aRequest.path);
+#endif
+ CHIP_ERROR err = Access::GetAccessControl().Check(*aRequest.subjectDescriptor, requestPath, minimumRequiredPrivilege);
+ if (err != CHIP_NO_ERROR)
+ {
+ if ((err != CHIP_ERROR_ACCESS_DENIED) && (err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL))
+ {
+ return Status::Failure;
+ }
+ return err == CHIP_ERROR_ACCESS_DENIED ? Status::UnsupportedAccess : Status::AccessRestricted;
+ }
+
+ return Status::Success;
+}
+
+Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandFlags(const DataModel::InvokeRequest & aRequest)
+{
+#if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
+ std::optional<DataModel::CommandInfo> commandInfo = mDataModelProvider->GetAcceptedCommandInfo(aRequest.path);
+ // This is checked by previous validations, so it should not happen
+ VerifyOrDie(commandInfo.has_value());
+
+ const bool commandNeedsTimedInvoke = commandInfo->flags.Has(DataModel::CommandQualityFlags::kTimed);
+ const bool commandIsFabricScoped = commandInfo->flags.Has(DataModel::CommandQualityFlags::kFabricScoped);
+#else
+ const bool commandNeedsTimedInvoke = CommandNeedsTimedInvoke(aRequest.path.mClusterId, aRequest.path.mCommandId);
+ const bool commandIsFabricScoped = CommandIsFabricScoped(aRequest.path.mClusterId, aRequest.path.mCommandId);
+#endif
+
+ if (commandNeedsTimedInvoke && !aRequest.invokeFlags.Has(DataModel::InvokeFlags::kTimed))
+ {
+ return Status::NeedsTimedInteraction;
+ }
+
+ if (commandIsFabricScoped)
+ {
+ // SPEC: Else if the command in the path is fabric-scoped and there is no accessing fabric,
+ // a CommandStatusIB SHALL be generated with the UNSUPPORTED_ACCESS Status Code.
+
+ // Fabric-scoped commands are not allowed before a specific accessing fabric is available.
+ // This is mostly just during a PASE session before AddNOC.
+ if (aRequest.GetAccessingFabricIndex() == kUndefinedFabricIndex)
+ {
+ return Status::UnsupportedAccess;
+ }
+ }
+
+ return Status::Success;
+}
+
+Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistence(const ConcreteCommandPath & aCommandPath)
{
#if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
auto provider = GetDataModelProvider();
diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h
index b52b4eb..ef4b041 100644
--- a/src/app/InteractionModelEngine.h
+++ b/src/app/InteractionModelEngine.h
@@ -49,6 +49,7 @@
#include <app/TimedHandler.h>
#include <app/WriteClient.h>
#include <app/WriteHandler.h>
+#include <app/data-model-provider/OperationTypes.h>
#include <app/data-model-provider/Provider.h>
#include <app/icd/server/ICDServerConfig.h>
#include <app/reporting/Engine.h>
@@ -508,7 +509,7 @@
void DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
TLV::TLVReader & apPayload) override;
- Protocols::InteractionModel::Status CommandExists(const ConcreteCommandPath & aCommandPath) override;
+ Protocols::InteractionModel::Status ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) override;
bool HasActiveRead();
@@ -612,6 +613,10 @@
void ShutdownMatchingSubscriptions(const Optional<FabricIndex> & aFabricIndex = NullOptional,
const Optional<NodeId> & aPeerNodeId = NullOptional);
+ Status CheckCommandExistence(const ConcreteCommandPath & aCommandPath);
+ Status CheckCommandAccess(const DataModel::InvokeRequest & aRequest);
+ Status CheckCommandFlags(const DataModel::InvokeRequest & aRequest);
+
/**
* Check if the given attribute path is a valid path in the data model provider.
*/
diff --git a/src/app/data-model-provider/OperationTypes.h b/src/app/data-model-provider/OperationTypes.h
index 8758e02..6bfffcc 100644
--- a/src/app/data-model-provider/OperationTypes.h
+++ b/src/app/data-model-provider/OperationTypes.h
@@ -19,6 +19,7 @@
#include <access/SubjectDescriptor.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteCommandPath.h>
+#include <lib/core/DataModelTypes.h>
#include <lib/support/BitFlags.h>
#include <cstdint>
@@ -55,6 +56,15 @@
///
/// NOTE: once kInternal flag is removed, this will become non-optional
std::optional<chip::Access::SubjectDescriptor> subjectDescriptor;
+
+ /// Accessing fabric index is the subjectDescriptor fabric index (if any).
+ /// This is a readability convenience function.
+ ///
+ /// Returns kUndefinedFabricIndex if no subject descriptor is available
+ FabricIndex GetAccessingFabricIndex() const
+ {
+ return subjectDescriptor.has_value() ? subjectDescriptor->fabricIndex : kUndefinedFabricIndex;
+ }
};
enum class ReadFlags : uint32_t
diff --git a/src/app/tests/TestCommandInteraction.cpp b/src/app/tests/TestCommandInteraction.cpp
index e4465c1..a44d76d 100644
--- a/src/app/tests/TestCommandInteraction.cpp
+++ b/src/app/tests/TestCommandInteraction.cpp
@@ -25,7 +25,6 @@
#include <cinttypes>
#include <optional>
-#include <lib/core/StringBuilderAdapters.h>
#include <pw_unit_test/framework.h>
#include <app/AppConfig.h>
@@ -38,6 +37,7 @@
#include <lib/core/CHIPCore.h>
#include <lib/core/ErrorStr.h>
#include <lib/core/Optional.h>
+#include <lib/core/StringBuilderAdapters.h>
#include <lib/core/TLV.h>
#include <lib/core/TLVDebug.h>
#include <lib/core/TLVUtilities.h>
@@ -372,9 +372,21 @@
{
DispatchSingleClusterCommand(aCommandPath, apPayload, &apCommandObj);
}
- Protocols::InteractionModel::Status CommandExists(const ConcreteCommandPath & aCommandPath)
+
+ Protocols::InteractionModel::Status ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) override
{
- return ServerClusterCommandExists(aCommandPath);
+ using Protocols::InteractionModel::Status;
+
+ Status status = ServerClusterCommandExists(request.path);
+ if (status != Status::Success)
+ {
+ return status;
+ }
+
+ // NOTE: IM does more validation here, however for now we do minimal options
+ // to pass the test.
+
+ return Status::Success;
}
void ResetCounter() { onFinalCalledTimes = 0; }
diff --git a/src/test_driver/linux-cirque/MobileDeviceTest.py b/src/test_driver/linux-cirque/MobileDeviceTest.py
index a206dc3..c8a73ba 100755
--- a/src/test_driver/linux-cirque/MobileDeviceTest.py
+++ b/src/test_driver/linux-cirque/MobileDeviceTest.py
@@ -126,7 +126,7 @@
"Toggle ep1 on/off from state 0 to 1",
"Received command for Endpoint=1 Cluster=0x0000_0006 Command=0x0000_0000",
"Toggle ep1 on/off from state 1 to 0",
- "No command 0x0000_0001 in Cluster 0x0000_0006 on Endpoint 0xe9"]),
+ "No command 0x0000_0001 in Cluster 0x0000_0006 on Endpoint 233"]),
"Datamodel test failed: cannot find matching string from device {}".format(device_id))