diff --git a/examples/tv-app/tv-common/clusters/messages/MessagesManager.cpp b/examples/tv-app/tv-common/clusters/messages/MessagesManager.cpp index 11068dc1d720c8..6ffbb9a3476258 100644 --- a/examples/tv-app/tv-common/clusters/messages/MessagesManager.cpp +++ b/examples/tv-app/tv-common/clusters/messages/MessagesManager.cpp @@ -18,64 +18,94 @@ #include "MessagesManager.h" #include +#include using namespace std; +using namespace chip; using namespace chip::app; using namespace chip::app::Clusters::Messages; -using Message = chip::app::Clusters::Messages::Structs::MessageStruct::Type; +using Message = chip::app::Clusters::Messages::Structs::MessageStruct::Type; +using MessageResponseOption = chip::app::Clusters::Messages::Structs::MessageResponseOptionStruct::Type; // Commands -void MessagesManager::HandlePresentMessagesRequest( - const chip::ByteSpan & messageId, const MessagePriorityEnum & priority, - const chip::BitMask & messageControl, const chip::app::DataModel::Nullable & startTime, - const chip::app::DataModel::Nullable & duration, const chip::CharSpan & messageText, - const chip::Optional> & responses) +CHIP_ERROR MessagesManager::HandlePresentMessagesRequest( + const ByteSpan & messageId, const MessagePriorityEnum & priority, const BitMask & messageControl, + const DataModel::Nullable & startTime, const DataModel::Nullable & duration, const CharSpan & messageText, + const Optional> & responses) { - Message message{ - // TODO: Enable id - chip::ByteSpan(), priority, messageControl, startTime, duration, - // TODO: Enable text - chip::CharSpan() - // TODO: Convert responses to Optional> message.responses = responses; - }; + ChipLogProgress(Zcl, "HandlePresentMessagesRequest message:%s", std::string(messageText.data(), messageText.size()).c_str()); + + auto cachedMessage = CachedMessage(messageId, priority, messageControl, startTime, duration, + std::string(messageText.data(), messageText.size())); + if (responses.HasValue()) + { + auto iter = responses.Value().begin(); + while (iter.Next()) + { + auto & response = iter.GetValue(); + + CachedMessageOption option(response.messageResponseID.Value(), + std::string(response.label.Value().data(), response.label.Value().size())); + + cachedMessage.AddOption(option); + } + } + + mCachedMessages.push_back(cachedMessage); - mMessages.push_back(message); // Add your code to present Message + ChipLogProgress(Zcl, "HandlePresentMessagesRequest complete"); + return CHIP_NO_ERROR; } -void MessagesManager::HandleCancelMessagesRequest(const chip::app::DataModel::DecodableList & messageIds) +CHIP_ERROR MessagesManager::HandleCancelMessagesRequest(const DataModel::DecodableList & messageIds) { - // TODO: Cancel Message + auto iter = messageIds.begin(); + while (iter.Next()) + { + auto & id = iter.GetValue(); + + mCachedMessages.remove_if([id](CachedMessage & entry) { return entry.MessageIdMatches(id); }); + // per spec, the command succeeds even when the message id does not match an existing message + } + return CHIP_NO_ERROR; } // Attributes -CHIP_ERROR MessagesManager::HandleGetMessages(chip::app::AttributeValueEncoder & aEncoder) +CHIP_ERROR MessagesManager::HandleGetMessages(AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { - for (Message & entry : mMessages) + for (CachedMessage & entry : mCachedMessages) { - ReturnErrorOnFailure(encoder.Encode(entry)); + ReturnErrorOnFailure(encoder.Encode(entry.GetMessage())); } return CHIP_NO_ERROR; }); } -CHIP_ERROR MessagesManager::HandleGetActiveMessageIds(chip::app::AttributeValueEncoder & aEncoder) +CHIP_ERROR MessagesManager::HandleGetActiveMessageIds(AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { - for (Message & entry : mMessages) + for (CachedMessage & entry : mCachedMessages) { - ReturnErrorOnFailure(encoder.Encode(entry.messageID)); + ReturnErrorOnFailure(encoder.Encode(entry.GetMessage().messageID)); } return CHIP_NO_ERROR; }); } // Global Attributes -uint32_t MessagesManager::GetFeatureMap(chip::EndpointId endpoint) +uint32_t MessagesManager::GetFeatureMap(EndpointId endpoint) { - uint32_t featureMap = 0; - Attributes::FeatureMap::Get(endpoint, &featureMap); + BitMask FeatureMap; + FeatureMap.Set(Feature::kReceivedConfirmation); + FeatureMap.Set(Feature::kConfirmationResponse); + FeatureMap.Set(Feature::kConfirmationReply); + FeatureMap.Set(Feature::kProtectedMessages); + + uint32_t featureMap = FeatureMap.Raw(); + ChipLogProgress(Zcl, "GetFeatureMap featureMap=%d", featureMap); + // forcing to all features since this implementation supports all + // Attributes::FeatureMap::Get(endpoint, &featureMap); return featureMap; } diff --git a/examples/tv-app/tv-common/clusters/messages/MessagesManager.h b/examples/tv-app/tv-common/clusters/messages/MessagesManager.h index 97ff5096b8ef7d..325cd56fc788af 100644 --- a/examples/tv-app/tv-common/clusters/messages/MessagesManager.h +++ b/examples/tv-app/tv-common/clusters/messages/MessagesManager.h @@ -21,12 +21,109 @@ #include #include +#include + +struct CachedMessageOption +{ + CachedMessageOption(uint32_t id, std::string label) : + mLabel(label), mOption{ chip::MakeOptional(id), chip::MakeOptional(chip::CharSpan::fromCharString(mLabel.c_str())) } + {} + + CachedMessageOption(const CachedMessageOption & option) : + mLabel(option.mLabel), + mOption{ option.mOption.messageResponseID, chip::MakeOptional(chip::CharSpan::fromCharString(mLabel.c_str())) } + {} + + CachedMessageOption & operator=(const CachedMessageOption & option) = delete; + + chip::app::Clusters::Messages::Structs::MessageResponseOptionStruct::Type GetMessageOption() { return mOption; } + + ~CachedMessageOption() {} + +protected: + std::string mLabel; + chip::app::Clusters::Messages::Structs::MessageResponseOptionStruct::Type mOption; +}; + +struct CachedMessage +{ + CachedMessage(const CachedMessage & message) : + mPriority(message.mPriority), mMessageControl(message.mMessageControl), mStartTime(message.mStartTime), + mDuration(message.mDuration), mMessageText(message.mMessageText), mOptions(message.mOptions) + { + memcpy(mMessageIdBuffer, message.mMessageIdBuffer, sizeof(mMessageIdBuffer)); + + for (CachedMessageOption & entry : mOptions) + { + mResponseOptions.push_back(entry.GetMessageOption()); + } + } + + CachedMessage & operator=(const CachedMessage & message) = delete; + + CachedMessage(const chip::ByteSpan & messageId, const chip::app::Clusters::Messages::MessagePriorityEnum & priority, + const chip::BitMask & messageControl, + const chip::app::DataModel::Nullable & startTime, + const chip::app::DataModel::Nullable & duration, std::string messageText) : + mPriority(priority), + mMessageControl(messageControl), mStartTime(startTime), mDuration(duration), mMessageText(messageText) + { + memcpy(mMessageIdBuffer, messageId.data(), sizeof(mMessageIdBuffer)); + } + + bool MessageIdMatches(const chip::ByteSpan & id) { return chip::ByteSpan(mMessageIdBuffer).data_equal(id); } + + void AddOption(CachedMessageOption option) + { + mOptions.push_back(option); + mResponseOptions.push_back(option.GetMessageOption()); + } + + chip::app::Clusters::Messages::Structs::MessageStruct::Type GetMessage() + { + if (mResponseOptions.size() > 0) + { + chip::app::DataModel::List options( + mResponseOptions.data(), mResponseOptions.size()); + chip::app::Clusters::Messages::Structs::MessageStruct::Type message{ chip::ByteSpan(mMessageIdBuffer), + mPriority, + mMessageControl, + mStartTime, + mDuration, + chip::CharSpan::fromCharString( + mMessageText.c_str()), + chip::MakeOptional(options) }; + return message; + } + chip::app::Clusters::Messages::Structs::MessageStruct::Type message{ chip::ByteSpan(mMessageIdBuffer), + mPriority, + mMessageControl, + mStartTime, + mDuration, + chip::CharSpan::fromCharString(mMessageText.c_str()) }; + return message; + } + + ~CachedMessage() {} + +protected: + const chip::app::Clusters::Messages::MessagePriorityEnum mPriority; + const chip::BitMask mMessageControl; + const chip::app::DataModel::Nullable mStartTime; + const chip::app::DataModel::Nullable mDuration; + + std::string mMessageText; + uint8_t mMessageIdBuffer[chip::app::Clusters::Messages::kMessageIdLength]; + + std::vector mResponseOptions; + std::list mOptions; +}; class MessagesManager : public chip::app::Clusters::Messages::Delegate { public: // Commands - void HandlePresentMessagesRequest( + CHIP_ERROR HandlePresentMessagesRequest( const chip::ByteSpan & messageId, const chip::app::Clusters::Messages::MessagePriorityEnum & priority, const chip::BitMask & messageControl, const chip::app::DataModel::Nullable & startTime, const chip::app::DataModel::Nullable & duration, @@ -34,7 +131,7 @@ class MessagesManager : public chip::app::Clusters::Messages::Delegate const chip::Optional< chip::app::DataModel::DecodableList> & responses) override; - void HandleCancelMessagesRequest(const chip::app::DataModel::DecodableList & messageIds) override; + CHIP_ERROR HandleCancelMessagesRequest(const chip::app::DataModel::DecodableList & messageIds) override; // Attributes CHIP_ERROR HandleGetMessages(chip::app::AttributeValueEncoder & aEncoder) override; @@ -44,5 +141,5 @@ class MessagesManager : public chip::app::Clusters::Messages::Delegate uint32_t GetFeatureMap(chip::EndpointId endpoint) override; protected: - std::list mMessages; + std::list mCachedMessages; }; diff --git a/src/app/clusters/messages-server/messages-delegate.h b/src/app/clusters/messages-server/messages-delegate.h index b836a6b34c3ab2..aba771aad23953 100644 --- a/src/app/clusters/messages-server/messages-delegate.h +++ b/src/app/clusters/messages-server/messages-delegate.h @@ -29,19 +29,23 @@ namespace app { namespace Clusters { namespace Messages { -using MessageResponseOption = chip::app::Clusters::Messages::Structs::MessageResponseOptionStruct::Type; +constexpr static size_t kMessageIdLength = 16; +constexpr static size_t kMessageTextLengthMax = 256; +constexpr static size_t kMessageMaxOptionCount = 4; +constexpr static size_t kMessageResponseIdMin = 1; +constexpr static size_t kMessageResponseLabelMaxLength = 32; class Delegate { public: // Commands - virtual void - HandlePresentMessagesRequest(const ByteSpan & messageId, const MessagePriorityEnum & priority, - const chip::BitMask & messageControl, - const DataModel::Nullable & startTime, const DataModel::Nullable & duration, - const CharSpan & messageText, - const chip::Optional> & responses) = 0; - virtual void HandleCancelMessagesRequest(const DataModel::DecodableList & messageIds) = 0; + virtual CHIP_ERROR HandlePresentMessagesRequest( + const ByteSpan & messageId, const MessagePriorityEnum & priority, + const chip::BitMask & messageControl, const DataModel::Nullable & startTime, + const DataModel::Nullable & duration, const CharSpan & messageText, + const chip::Optional> & + responses) = 0; + virtual CHIP_ERROR HandleCancelMessagesRequest(const DataModel::DecodableList & messageIds) = 0; // Attributes virtual CHIP_ERROR HandleGetMessages(app::AttributeValueEncoder & aEncoder) = 0; diff --git a/src/app/clusters/messages-server/messages-server.cpp b/src/app/clusters/messages-server/messages-server.cpp index 33a403e64b3d86..66594ae732a522 100644 --- a/src/app/clusters/messages-server/messages-server.cpp +++ b/src/app/clusters/messages-server/messages-server.cpp @@ -182,9 +182,58 @@ bool emberAfMessagesClusterPresentMessagesRequestCallback( auto & responses = commandData.responses; Delegate * delegate = GetDelegate(endpoint); - VerifyOrExit(isDelegateNull(delegate, endpoint) != true, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(isDelegateNull(delegate, endpoint) != true, status = Status::NotFound); + + VerifyOrExit(messageId.size() == kMessageIdLength, + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback invalid message id length"); + status = Status::ConstraintError); + + VerifyOrExit(messageText.size() <= kMessageTextLengthMax, + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback invalid message text length"); + status = Status::ConstraintError); + + if (responses.HasValue()) + { + size_t size = 0; + err = responses.Value().ComputeSize(&size); + VerifyOrExit(err == CHIP_NO_ERROR, + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback size check failed"); + status = Status::ConstraintError); + + VerifyOrExit( + delegate->HasFeature(endpoint, Feature::kConfirmationResponse), + ChipLogProgress( + Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback responses sent but response feature not supported"); + status = Status::InvalidCommand); + + VerifyOrExit(size <= kMessageMaxOptionCount, + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback too many options"); + status = Status::ConstraintError); + + auto iter = responses.Value().begin(); + while (iter.Next()) + { + auto & response = iter.GetValue(); + + // response feature is checked above + VerifyOrExit(response.messageResponseID.HasValue() && response.label.HasValue(), + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback missing response id or label"); + status = Status::InvalidCommand); + + VerifyOrExit(response.messageResponseID.Value() >= kMessageResponseIdMin, + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback responseID value check failed"); + status = Status::ConstraintError); - delegate->HandlePresentMessagesRequest(messageId, priority, messageControl, startTime, duration, messageText, responses); + VerifyOrExit(response.label.Value().size() <= kMessageResponseLabelMaxLength, + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback label length check failed"); + status = Status::ConstraintError); + } + VerifyOrExit(iter.GetStatus() == CHIP_NO_ERROR, + ChipLogProgress(Zcl, "emberAfMessagesClusterPresentMessagesRequestCallback TLV parsing error"); + status = Status::InvalidAction); + } + + err = delegate->HandlePresentMessagesRequest(messageId, priority, messageControl, startTime, duration, messageText, responses); exit: if (err != CHIP_NO_ERROR) @@ -214,7 +263,21 @@ bool emberAfMessagesClusterCancelMessagesRequestCallback( Delegate * delegate = GetDelegate(endpoint); VerifyOrExit(isDelegateNull(delegate, endpoint) != true, err = CHIP_ERROR_INCORRECT_STATE); - delegate->HandleCancelMessagesRequest(messageIds); + { + auto iter = messageIds.begin(); + while (iter.Next()) + { + auto & id = iter.GetValue(); + VerifyOrExit(id.size() >= kMessageIdLength, + ChipLogProgress(Zcl, "emberAfMessagesClusterCancelMessagesRequestCallback message id size check failed"); + status = Status::ConstraintError); + } + VerifyOrExit(iter.GetStatus() == CHIP_NO_ERROR, + ChipLogProgress(Zcl, "emberAfMessagesClusterCancelMessagesRequestCallback TLV parsing error"); + status = Status::InvalidAction); + } + + err = delegate->HandleCancelMessagesRequest(messageIds); exit: if (err != CHIP_NO_ERROR)