From 3f74ddfff9b43921193bdbb69f27ff23f37baf2f Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Wed, 17 Nov 2021 13:42:38 -0800 Subject: [PATCH] Add IM event processing function and callback interface in client side (#11615) --- src/app/ClusterInfo.h | 1 + src/app/ConcreteEventPath.h | 63 ++++++++++++ ...DeviceControllerInteractionModelDelegate.h | 4 +- src/app/EventHeader.h | 35 +++++++ src/app/EventLoggingTypes.h | 20 ++-- src/app/EventManagement.cpp | 64 ++---------- src/app/EventPathParams.h | 3 + src/app/MessageDef/EventDataIB.cpp | 99 +++++++++++++++++++ src/app/MessageDef/EventDataIB.h | 6 ++ src/app/MessageDef/EventReportIB.cpp | 16 ++- src/app/ReadClient.cpp | 34 ++++++- src/app/ReadClient.h | 41 ++++++-- src/app/tests/TestEventLogging.cpp | 6 +- src/app/tests/TestMessageDef.cpp | 7 +- src/app/tests/TestReadInteraction.cpp | 34 +++---- .../tests/integration/chip_im_initiator.cpp | 4 +- src/controller/ReadInteraction.h | 77 +++++++++++++-- src/controller/TypedReadCallback.h | 70 +++++++++++-- src/lib/core/CHIPError.cpp | 3 + src/lib/core/CHIPError.h | 10 ++ 20 files changed, 470 insertions(+), 127 deletions(-) create mode 100644 src/app/ConcreteEventPath.h create mode 100644 src/app/EventHeader.h diff --git a/src/app/ClusterInfo.h b/src/app/ClusterInfo.h index afefd092fe420b..9a282dd1e621b9 100644 --- a/src/app/ClusterInfo.h +++ b/src/app/ClusterInfo.h @@ -38,6 +38,7 @@ struct ClusterInfo private: // Allow AttributePathParams access these constants. friend struct AttributePathParams; + friend struct ConcreteEventPath; // The ClusterId, AttributeId and EventId are MEIs, // 0xFFFF is not a valid manufacturer code, thus 0xFFFF'FFFF is not a valid MEI diff --git a/src/app/ConcreteEventPath.h b/src/app/ConcreteEventPath.h new file mode 100644 index 00000000000000..525aaef2e82450 --- /dev/null +++ b/src/app/ConcreteEventPath.h @@ -0,0 +1,63 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace app { + +/** + * A representation of a concrete event path. + */ +struct ConcreteEventPath +{ + ConcreteEventPath(EndpointId aEndpointId, ClusterId aClusterId, EventId aEventId) : + mEndpointId(aEndpointId), mClusterId(aClusterId), mEventId(aEventId) + {} + + ConcreteEventPath() {} + + ConcreteEventPath & operator=(ConcreteEventPath && other) + { + if (&other == this) + return *this; + + mEndpointId = other.mEndpointId; + mClusterId = other.mClusterId; + mEventId = other.mEventId; + return *this; + } + + bool operator==(const ConcreteEventPath & other) const + { + return mEndpointId == other.mEndpointId && mClusterId == other.mClusterId && mEventId == other.mEventId; + } + + bool IsValidEventPath() const { return !HasWildcardEventId(); } + + inline bool HasWildcardEventId() const { return mEventId == ClusterInfo::kInvalidEventId; } + + EndpointId mEndpointId = 0; + ClusterId mClusterId = 0; + EventId mEventId = 0; +}; +} // namespace app +} // namespace chip diff --git a/src/app/DeviceControllerInteractionModelDelegate.h b/src/app/DeviceControllerInteractionModelDelegate.h index 01a61173a15306..95106c0d0f5855 100644 --- a/src/app/DeviceControllerInteractionModelDelegate.h +++ b/src/app/DeviceControllerInteractionModelDelegate.h @@ -68,7 +68,9 @@ class DeviceControllerInteractionModelDelegate : public chip::app::ReadClient::C void OnDone(app::WriteClient * apWriteClient) override {} - void OnEventData(const app::ReadClient * apReadClient, TLV::TLVReader & aEventList) override {} + void OnEventData(const app::ReadClient * apReadClient, const app::EventHeader & aEventHeader, TLV::TLVReader * apData, + const app::StatusIB * apStatus) override + {} void OnAttributeData(const app::ReadClient * apReadClient, const app::ConcreteAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) override diff --git a/src/app/EventHeader.h b/src/app/EventHeader.h new file mode 100644 index 00000000000000..c3ba210bfbfa5d --- /dev/null +++ b/src/app/EventHeader.h @@ -0,0 +1,35 @@ +/* + * + * 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. + */ + +#pragma once + +#include "ConcreteEventPath.h" +#include "EventLoggingTypes.h" +#include + +namespace chip { +namespace app { +struct EventHeader +{ + ConcreteEventPath mPath; + EventNumber mEventNumber = 0; + PriorityLevel mPriorityLevel = PriorityLevel::Invalid; + Timestamp mTimestamp; +}; +} // namespace app +} // namespace chip diff --git a/src/app/EventLoggingTypes.h b/src/app/EventLoggingTypes.h index 2d047bbdb6dd0d..d852e015d5f760 100644 --- a/src/app/EventLoggingTypes.h +++ b/src/app/EventLoggingTypes.h @@ -104,15 +104,14 @@ struct EventSchema /** * @brief - * The struct that provides an application set system or UTC timestamp. + * The struct that provides an application set System or Epoch timestamp. */ struct Timestamp { enum class Type { - kInvalid = 0, - kSystem, - kUTC + kSystem = 0, + kEpoch }; Timestamp() {} Timestamp(Type aType) : mType(aType) { mValue = 0; } @@ -120,7 +119,7 @@ struct Timestamp Timestamp(System::Clock::Timestamp aValue) : mType(Type::kSystem), mValue(aValue.count()) {} static Timestamp UTC(uint64_t aValue) { - Timestamp timestamp(Type::kUTC, aValue); + Timestamp timestamp(Type::kEpoch, aValue); return timestamp; } static Timestamp System(System::Clock::Timestamp aValue) @@ -129,7 +128,10 @@ struct Timestamp return timestamp; } - Type mType = Type::kInvalid; + bool IsSystem() { return mType == Type::kSystem; } + bool IsEpoch() { return mType == Type::kEpoch; } + + Type mType = Type::kSystem; uint64_t mValue = 0; }; @@ -144,9 +146,9 @@ class EventOptions kUrgent = 0, kNotUrgent, }; - EventOptions(void) : mTimestamp(Timestamp::Type::kInvalid), mpEventSchema(nullptr), mUrgent(Type::kNotUrgent) {} + EventOptions(void) : mTimestamp(Timestamp::Type::kSystem), mpEventSchema(nullptr), mUrgent(Type::kNotUrgent) {} - EventOptions(Type aType) : mTimestamp(Timestamp::Type::kInvalid), mpEventSchema(nullptr), mUrgent(aType) {} + EventOptions(Type aType) : mTimestamp(Timestamp::Type::kSystem), mpEventSchema(nullptr), mUrgent(aType) {} EventOptions(Timestamp aTimestamp) : mTimestamp(aTimestamp), mpEventSchema(nullptr), mUrgent(Type::kNotUrgent) {} @@ -167,7 +169,7 @@ struct EventLoadOutContext { EventLoadOutContext(TLV::TLVWriter & aWriter, PriorityLevel aPriority, EventNumber aStartingEventNumber) : mWriter(aWriter), mPriority(aPriority), mStartingEventNumber(aStartingEventNumber), - mCurrentSystemTime(Timestamp::Type::kSystem), mCurrentEventNumber(0), mCurrentUTCTime(Timestamp::Type::kUTC), mFirst(true) + mCurrentSystemTime(Timestamp::Type::kSystem), mCurrentEventNumber(0), mCurrentUTCTime(Timestamp::Type::kEpoch), mFirst(true) {} TLV::TLVWriter & mWriter; diff --git a/src/app/EventManagement.cpp b/src/app/EventManagement.cpp index 67d82329c0eb02..1657b839068e48 100644 --- a/src/app/EventManagement.cpp +++ b/src/app/EventManagement.cpp @@ -304,40 +304,16 @@ CHIP_ERROR EventManagement::ConstructEvent(EventLoadOutContext * apContext, Even EventReportIB::Builder eventReportBuilder; EventDataIB::Builder eventDataIBBuilder; EventPathIB::Builder eventPathBuilder; - EventStatusIB::Builder eventStatusIBBuilder; - StatusIB::Builder statusIBBuilder; - StatusIB status; uint64_t deltatime = 0; VerifyOrExit(apContext->mCurrentEventNumber >= apContext->mStartingEventNumber, /* no-op: don't write event, but advance current event Number */); VerifyOrExit(apOptions != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrExit(apOptions->mTimestamp.mType != Timestamp::Type::kInvalid, err = CHIP_ERROR_INVALID_ARGUMENT); eventReportBuilder.Init(&(apContext->mWriter)); // TODO: Update IsUrgent, issue 11386 // TODO: Update statusIB, issue 11388 - eventStatusIBBuilder = eventReportBuilder.CreateEventStatus(); - eventPathBuilder = eventStatusIBBuilder.CreatePath(); - err = eventStatusIBBuilder.GetError(); - SuccessOrExit(err); - eventPathBuilder.Node(apOptions->mpEventSchema->mNodeId) - .Endpoint(apOptions->mpEventSchema->mEndpointId) - .Cluster(apOptions->mpEventSchema->mClusterId) - .Event(apOptions->mpEventSchema->mEventId) - .IsUrgent(false) - .EndOfEventPathIB(); - err = eventPathBuilder.GetError(); - SuccessOrExit(err); - statusIBBuilder = eventStatusIBBuilder.CreateErrorStatus(); - err = eventStatusIBBuilder.GetError(); - SuccessOrExit(err); - statusIBBuilder.EncodeStatusIB(status); - eventStatusIBBuilder.EndOfEventStatusIB(); - err = statusIBBuilder.GetError(); - SuccessOrExit(err); - eventDataIBBuilder = eventReportBuilder.CreateEventData(); eventPathBuilder = eventDataIBBuilder.CreatePath(); err = eventDataIBBuilder.GetError(); @@ -466,6 +442,12 @@ CHIP_ERROR EventManagement::CopyAndAdjustDeltaTime(const TLVReader & aReader, si CopyAndAdjustDeltaTimeContext * ctx = static_cast(apContext); TLVReader reader(aReader); + if (aReader.GetTag() == TLV::ContextTag(to_underlying(EventDataIB::Tag::kPath))) + { + err = + ctx->mpWriter->Put(TLV::ContextTag(to_underlying(EventDataIB::Tag::kEventNumber)), ctx->mpContext->mCurrentEventNumber); + } + // TODO: Add UTC timestamp support if (aReader.GetTag() == TLV::ContextTag(to_underlying(EventDataIB::Tag::kDeltaSystemTimestamp))) { @@ -485,17 +467,6 @@ CHIP_ERROR EventManagement::CopyAndAdjustDeltaTime(const TLVReader & aReader, si err = ctx->mpWriter->CopyElement(reader); } - // First event in the sequence gets a event number neatly packaged - // right after the priority to keep tags ordered - if (aReader.GetTag() == TLV::ContextTag(to_underlying(EventDataIB::Tag::kPriority))) - { - if (ctx->mpContext->mFirst) - { - err = ctx->mpWriter->Put(TLV::ContextTag(to_underlying(EventDataIB::Tag::kEventNumber)), - ctx->mpContext->mCurrentEventNumber); - } - } - return err; } @@ -621,23 +592,6 @@ CHIP_ERROR EventManagement::CopyEvent(const TLVReader & aReader, TLVWriter & aWr ReturnErrorOnFailure(reader.EnterContainer(containerType)); ReturnErrorOnFailure(aWriter.StartContainer(AnonymousTag, kTLVType_Structure, containerType)); - ReturnErrorOnFailure(reader.Next()); - ReturnErrorOnFailure(reader.EnterContainer(containerType1)); - ReturnErrorOnFailure(aWriter.StartContainer(TLV::ContextTag(to_underlying(EventReportIB::Tag::kEventStatus)), - kTLVType_Structure, containerType1)); - ReturnErrorOnFailure(reader.Next()); - do - { - ReturnErrorOnFailure(aWriter.CopyElement(reader)); - } while (CHIP_NO_ERROR == (err = reader.Next())); - if (err == CHIP_END_OF_TLV) - { - err = CHIP_NO_ERROR; - } - ReturnErrorOnFailure(err); - ReturnErrorOnFailure(reader.ExitContainer(containerType1)); - ReturnErrorOnFailure(aWriter.EndContainer(containerType1)); - ReturnErrorOnFailure(reader.Next()); ReturnErrorOnFailure(reader.EnterContainer(containerType1)); ReturnErrorOnFailure( @@ -685,8 +639,7 @@ CHIP_ERROR EventManagement::EventIterator(const TLVReader & aReader, size_t aDep innerReader.Init(aReader); ReturnErrorOnFailure(innerReader.EnterContainer(tlvType)); ReturnErrorOnFailure(innerReader.Next()); - // Skip EventStatus Element - ReturnErrorOnFailure(innerReader.Next()); + ReturnErrorOnFailure(innerReader.EnterContainer(tlvType1)); err = TLV::Utilities::Iterate(innerReader, FetchEventParameters, &event, false /*recurse*/); if (event.mFieldsToRead != kRequiredEventField) @@ -836,8 +789,7 @@ CHIP_ERROR EventManagement::EvictEvent(CHIPCircularTLVBuffer & apBuffer, void * TLVType containerType1; ReturnErrorOnFailure(aReader.EnterContainer(containerType)); ReturnErrorOnFailure(aReader.Next()); - // Skip EventStatus - ReturnErrorOnFailure(aReader.Next()); + ReturnErrorOnFailure(aReader.EnterContainer(containerType1)); EventEnvelopeContext context; constexpr bool recurse = false; diff --git a/src/app/EventPathParams.h b/src/app/EventPathParams.h index 6e34b5a7a2482d..3efeca59efd57c 100644 --- a/src/app/EventPathParams.h +++ b/src/app/EventPathParams.h @@ -27,6 +27,9 @@ struct EventPathParams EventPathParams(NodeId aNodeId, EndpointId aEndpointId, ClusterId aClusterId, EventId aEventId, bool aIsUrgent) : mNodeId(aNodeId), mEndpointId(aEndpointId), mClusterId(aClusterId), mEventId(aEventId), mIsUrgent(aIsUrgent) {} + EventPathParams(EndpointId aEndpointId, ClusterId aClusterId, EventId aEventId) : + EventPathParams(0, aEndpointId, aClusterId, aEventId, false) + {} EventPathParams() {} bool IsSamePath(const EventPathParams & other) const { diff --git a/src/app/MessageDef/EventDataIB.cpp b/src/app/MessageDef/EventDataIB.cpp index 108bf25bbbb46a..9d7931450865d9 100644 --- a/src/app/MessageDef/EventDataIB.cpp +++ b/src/app/MessageDef/EventDataIB.cpp @@ -380,6 +380,105 @@ CHIP_ERROR EventDataIB::Parser::GetData(TLV::TLVReader * const apReader) const return mReader.FindElementWithTag(TLV::ContextTag(to_underlying(Tag::kData)), *apReader); } +CHIP_ERROR EventDataIB::Parser::ProcessEventPath(EventPathIB::Parser & aEventPath, ConcreteEventPath & aConcreteEventPath) +{ + // The ReportData must contain a concrete event path + CHIP_ERROR err = aEventPath.GetEndpoint(&(aConcreteEventPath.mEndpointId)); + VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_IM_MALFORMED_EVENT_PATH); + + err = aEventPath.GetCluster(&(aConcreteEventPath.mClusterId)); + VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_IM_MALFORMED_EVENT_PATH); + + err = aEventPath.GetEvent(&(aConcreteEventPath.mEventId)); + VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_IM_MALFORMED_EVENT_PATH); + + VerifyOrReturnError(aConcreteEventPath.IsValidEventPath(), CHIP_ERROR_IM_MALFORMED_EVENT_PATH); + return CHIP_NO_ERROR; +} + +CHIP_ERROR EventDataIB::Parser::ProcessEventTimestamp(EventHeader & aEventHeader) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + uint64_t timeStampVal = 0; + bool hasSystemTimestamp = false; + bool hasEpochTimestamp = false; + bool hasDeltaSystemTimestamp = false; + bool hasDeltaEpochTimestamp = false; + + err = GetDeltaSystemTimestamp(&timeStampVal); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + else if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(aEventHeader.mTimestamp.IsSystem(), CHIP_ERROR_IM_MALFORMED_EVENT_DATA_ELEMENT); + aEventHeader.mTimestamp.mValue += timeStampVal; + hasDeltaSystemTimestamp = true; + } + ReturnErrorOnFailure(err); + + err = GetDeltaEpochTimestamp(&timeStampVal); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + else if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(aEventHeader.mTimestamp.IsEpoch(), CHIP_ERROR_IM_MALFORMED_EVENT_DATA_ELEMENT); + aEventHeader.mTimestamp.mValue += timeStampVal; + hasDeltaEpochTimestamp = true; + } + ReturnErrorOnFailure(err); + + err = GetSystemTimestamp(&timeStampVal); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + else if (err == CHIP_NO_ERROR) + { + aEventHeader.mTimestamp.mType = Timestamp::Type::kSystem; + aEventHeader.mTimestamp.mValue = timeStampVal; + hasSystemTimestamp = true; + } + ReturnErrorOnFailure(err); + + err = GetEpochTimestamp(&timeStampVal); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + else if (err == CHIP_NO_ERROR) + { + aEventHeader.mTimestamp.mType = Timestamp::Type::kEpoch; + aEventHeader.mTimestamp.mValue = timeStampVal; + hasEpochTimestamp = true; + } + + if ((hasSystemTimestamp && !hasEpochTimestamp && !hasDeltaSystemTimestamp && !hasDeltaEpochTimestamp) || + (!hasSystemTimestamp && hasEpochTimestamp && !hasDeltaSystemTimestamp && !hasDeltaEpochTimestamp) || + (!hasSystemTimestamp && !hasEpochTimestamp && hasDeltaSystemTimestamp && !hasDeltaEpochTimestamp) || + (!hasSystemTimestamp && !hasEpochTimestamp && !hasDeltaSystemTimestamp && hasDeltaEpochTimestamp)) + { + return CHIP_NO_ERROR; + } + return CHIP_ERROR_IM_MALFORMED_EVENT_DATA_ELEMENT; +} + +CHIP_ERROR EventDataIB::Parser::DecodeEventHeader(EventHeader & aEventHeader) +{ + uint8_t priorityLevel = 0; + EventPathIB::Parser path; + ReturnErrorOnFailure(GetPath(&path)); + ReturnErrorOnFailure(ProcessEventPath(path, aEventHeader.mPath)); + ReturnErrorOnFailure(GetEventNumber(&(aEventHeader.mEventNumber))); + ReturnErrorOnFailure(GetPriority(&priorityLevel)); + aEventHeader.mPriorityLevel = static_cast(priorityLevel); + ReturnErrorOnFailure(ProcessEventTimestamp(aEventHeader)); + return CHIP_NO_ERROR; +} + EventPathIB::Builder & EventDataIB::Builder::CreatePath() { // skip if error has already been set diff --git a/src/app/MessageDef/EventDataIB.h b/src/app/MessageDef/EventDataIB.h index 700e0da810ead1..0e0f662becbeab 100644 --- a/src/app/MessageDef/EventDataIB.h +++ b/src/app/MessageDef/EventDataIB.h @@ -27,6 +27,7 @@ #include "StructBuilder.h" #include "StructParser.h" #include +#include #include #include #include @@ -156,9 +157,14 @@ class Parser : public StructParser */ CHIP_ERROR GetData(TLV::TLVReader * const apReader) const; + CHIP_ERROR DecodeEventHeader(EventHeader & aEventHeader); + protected: // A recursively callable function to parse a data element and pretty-print it. CHIP_ERROR ParseData(TLV::TLVReader & aReader, int aDepth) const; + + CHIP_ERROR ProcessEventPath(EventPathIB::Parser & aEventPath, ConcreteEventPath & aConcreteEventPath); + CHIP_ERROR ProcessEventTimestamp(EventHeader & aEventHeader); }; class Builder : public StructBuilder diff --git a/src/app/MessageDef/EventReportIB.cpp b/src/app/MessageDef/EventReportIB.cpp index a64d3e1fbbf916..8dac9c789db944 100644 --- a/src/app/MessageDef/EventReportIB.cpp +++ b/src/app/MessageDef/EventReportIB.cpp @@ -84,9 +84,21 @@ CHIP_ERROR EventReportIB::Parser::CheckSchemaValidity() const if (CHIP_END_OF_TLV == err) { - const int RequiredFields = (1 << to_underlying(Tag::kEventData)) | (1 << to_underlying(Tag::kEventStatus)); + // check for at most field: + const int CheckDataField = 1 << to_underlying(Tag::kEventData); + const int CheckStatusField = (1 << to_underlying(Tag::kEventStatus)); - if ((TagPresenceMask & RequiredFields) == RequiredFields) + if ((TagPresenceMask & CheckDataField) == CheckDataField && (TagPresenceMask & CheckStatusField) == CheckStatusField) + { + // kEventData and kEventStatus both exist + err = CHIP_ERROR_IM_MALFORMED_EVENT_REPORT_IB; + } + else if ((TagPresenceMask & CheckDataField) != CheckDataField && (TagPresenceMask & CheckStatusField) != CheckStatusField) + { + // kEventData and kErrorStatus not exist + err = CHIP_ERROR_IM_MALFORMED_EVENT_REPORT_IB; + } + else { err = CHIP_NO_ERROR; } diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index 39011bca46dc27..15afb3cc79a72f 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -374,7 +374,8 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) { chip::TLV::TLVReader EventReportsReader; EventReports.GetReader(&EventReportsReader); - mpCallback->OnEventData(this, EventReportsReader); + err = ProcessEventReportIBs(EventReportsReader); + SuccessOrExit(err); } err = report.GetAttributeReportIBs(&attributeReportIBs); @@ -506,6 +507,37 @@ CHIP_ERROR ReadClient::ProcessAttributeReportIBs(TLV::TLVReader & aAttributeRepo return err; } +CHIP_ERROR ReadClient::ProcessEventReportIBs(TLV::TLVReader & aEventReportIBsReader) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + while (CHIP_NO_ERROR == (err = aEventReportIBsReader.Next())) + { + TLV::TLVReader dataReader; + EventReportIB::Parser report; + EventDataIB::Parser data; + EventHeader header; + + TLV::TLVReader reader = aEventReportIBsReader; + ReturnErrorOnFailure(report.Init(reader)); + + ReturnErrorOnFailure(report.GetEventData(&data)); + + header.mTimestamp = mEventTimestamp; + ReturnErrorOnFailure(data.DecodeEventHeader(header)); + mEventTimestamp = header.mTimestamp; + + ReturnErrorOnFailure(data.GetData(&dataReader)); + + mpCallback->OnEventData(this, header, &dataReader, nullptr); + } + + if (CHIP_END_OF_TLV == err) + { + err = CHIP_NO_ERROR; + } + return err; +} + CHIP_ERROR ReadClient::RefreshLivenessCheckTimer() { CancelLivenessCheckTimer(); diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index 9fa7e3d755adb1..1d8a52de120c09 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -23,8 +23,8 @@ */ #pragma once - #include +#include #include #include #include @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -59,17 +60,22 @@ class ReadClient : public Messaging::ExchangeDelegate { public: virtual ~Callback() = default; + /** - * Notification that a list of events is received on the given read client. * The ReadClient object MUST continue to exist after this call is completed. * - * @param[in] apReadClient The read client which initialized the read transaction. - * @param[in] aEventReports TLV reader positioned at the list that contains the events. The - * implementation of EventStreamReceived is expected to call Next() on the reader to - * advance it to the first element of the list, then process the elements from beginning to - * the end. The callee is expected to consume all events. + * This callback will be called when receiving event data received in the Read and Subscribe interactions + * + * @param[in] apReadClient: The read client object that initiated the read or subscribe transaction. + * @param[in] aEventHeader: The event header in report response. + * @param[in] apData: A TLVReader positioned right on the payload of the event. This will be set to null if the apStatus is + * not null. + * @param[in] apStatus: Event-specific status, containing an InteractionModel::Status code as well as an optional + * cluster-specific status code. */ - virtual void OnEventData(const ReadClient * apReadClient, TLV::TLVReader & aEventReports) {} + virtual void OnEventData(const ReadClient * apReadClient, const EventHeader & aEventHeader, TLV::TLVReader * apData, + const StatusIB * apStatus) + {} /** * OnResponse will be called when a report data response has been received and processed for the given path. @@ -242,6 +248,7 @@ class ReadClient : public Messaging::ExchangeDelegate CHIP_ERROR GenerateAttributePathList(AttributePathIBs::Builder & aAttributePathIBsBuilder, AttributePathParams * apAttributePathParamsList, size_t aAttributePathParamsListSize); CHIP_ERROR ProcessAttributeReportIBs(TLV::TLVReader & aAttributeDataIBsReader); + CHIP_ERROR ProcessEventReportIBs(TLV::TLVReader & aEventReportIBsReader); void ClearExchangeContext() { mpExchangeCtx = nullptr; } static void OnLivenessTimeoutCallback(System::Layer * apSystemLayer, void * apAppState); @@ -254,6 +261,23 @@ class ReadClient : public Messaging::ExchangeDelegate CHIP_ERROR AbortExistingExchangeContext(); const char * GetStateStr() const; + /** + * Validate that the Event ID and Cluster ID in the header match that of the type information present in the object and + * decode the data. The template parameter T is generally expected to be a ClusterName::Events::EventName::Type struct + * + * @param [in] aEventHeader The header of the event being validated. + * @param [in] aEvent The event data. + * @param [in] aReader The tlv reader. + */ + template + CHIP_ERROR DecodeEvent(const EventHeader & aEventHeader, const EventDataT & aEvent, TLV::TLVReader & aReader) + { + VerifyOrReturnError((aEventHeader.mPath.mEventId == aEvent.GetEventId()) && + (aEventHeader.mPath.mClusterId == aEvent.GetClusterId()), + CHIP_ERROR_INVALID_ARGUMENT); + return DataModel::Decode(aReader, aEvent); + } + /** * Internal shutdown method that we use when we know what's going on with * our exchange and don't need to manually close it. @@ -272,6 +296,7 @@ class ReadClient : public Messaging::ExchangeDelegate NodeId mPeerNodeId = kUndefinedNodeId; FabricIndex mFabricIndex = kUndefinedFabricIndex; InteractionType mInteractionType = InteractionType::Read; + Timestamp mEventTimestamp; }; }; // namespace app diff --git a/src/app/tests/TestEventLogging.cpp b/src/app/tests/TestEventLogging.cpp index 68bc9956c3a5fd..0511a183f56d1b 100644 --- a/src/app/tests/TestEventLogging.cpp +++ b/src/app/tests/TestEventLogging.cpp @@ -50,9 +50,9 @@ static const uint32_t kLivenessChangeEvent = 1; static const chip::EndpointId kTestEndpointId = 2; static const chip::TLV::Tag kLivenessDeviceStatus = chip::TLV::ContextTag(1); -static uint8_t gDebugEventBuffer[256]; -static uint8_t gInfoEventBuffer[256]; -static uint8_t gCritEventBuffer[256]; +static uint8_t gDebugEventBuffer[128]; +static uint8_t gInfoEventBuffer[128]; +static uint8_t gCritEventBuffer[128]; static chip::app::CircularEventBuffer gCircularEventBuffer[3]; class TestContext : public chip::Test::AppContext diff --git a/src/app/tests/TestMessageDef.cpp b/src/app/tests/TestMessageDef.cpp index b83bf0739cc407..e3781a92dc9da7 100644 --- a/src/app/tests/TestMessageDef.cpp +++ b/src/app/tests/TestMessageDef.cpp @@ -430,10 +430,6 @@ void ParseEventStatusIB(nlTestSuite * apSuite, EventStatusIB::Parser & aEventSta void BuildEventReportIB(nlTestSuite * apSuite, EventReportIB::Builder & aEventReportIBBuilder) { - EventStatusIB::Builder eventStatusIBBuilder = aEventReportIBBuilder.CreateEventStatus(); - NL_TEST_ASSERT(apSuite, aEventReportIBBuilder.GetError() == CHIP_NO_ERROR); - BuildEventStatusIB(apSuite, eventStatusIBBuilder); - EventDataIB::Builder eventDataIBBuilder = aEventReportIBBuilder.CreateEventData(); NL_TEST_ASSERT(apSuite, aEventReportIBBuilder.GetError() == CHIP_NO_ERROR); BuildEventDataIB(apSuite, eventDataIBBuilder); @@ -452,8 +448,7 @@ void ParseEventReportIB(nlTestSuite * apSuite, EventReportIB::Parser & aEventRep err = aEventReportIBParser.CheckSchemaValidity(); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); #endif - err = aEventReportIBParser.GetEventStatus(&eventStatusParser); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = aEventReportIBParser.GetEventData(&eventDataParser); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); } diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index 1650980bea9332..beb1b979108010 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -158,34 +158,22 @@ void GenerateSubscribeResponse(nlTestSuite * apSuite, void * apContext, chip::Sy class MockInteractionModelApp : public chip::app::ReadClient::Callback, public chip::app::InteractionModelDelegate { public: - void OnEventData(const chip::app::ReadClient * apReadClient, chip::TLV::TLVReader & apEventReportsReader) override + void OnEventData(const chip::app::ReadClient * apReadClient, const chip::app::EventHeader & aEventHeader, + chip::TLV::TLVReader * apData, const chip::app::StatusIB * apStatus) override { - CHIP_ERROR err = CHIP_NO_ERROR; - chip::TLV::TLVReader reader; - int numDataElementIndex = 0; - reader.Init(apEventReportsReader); - while (CHIP_NO_ERROR == (err = reader.Next())) + static int numDataElementIndex = 0; + + if (numDataElementIndex == 0) { - uint8_t priorityLevel = 0; - chip::app::EventReportIB::Parser eventReport; - chip::app::EventDataIB::Parser eventData; - VerifyOrReturn(eventReport.Init(reader) == CHIP_NO_ERROR); - VerifyOrReturn(eventReport.GetEventData(&eventData) == CHIP_NO_ERROR); - VerifyOrReturn(eventData.GetPriority(&priorityLevel) == CHIP_NO_ERROR); - if (numDataElementIndex == 0) - { - VerifyOrReturn(priorityLevel == chip::to_underlying(chip::app::PriorityLevel::Critical)); - } - else if (numDataElementIndex == 1) - { - VerifyOrReturn(priorityLevel == chip::to_underlying(chip::app::PriorityLevel::Info)); - } - ++numDataElementIndex; + VerifyOrReturn(aEventHeader.mPriorityLevel == chip::app::PriorityLevel::Critical); } - if (CHIP_END_OF_TLV == err) + else if (numDataElementIndex == 1) { - mGotEventResponse = true; + VerifyOrReturn(aEventHeader.mPriorityLevel == chip::app::PriorityLevel::Info); } + + ++numDataElementIndex; + mGotEventResponse = true; } void OnAttributeData(const chip::app::ReadClient * apReadClient, const chip::app::ConcreteAttributePath & aPath, diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp index 22d1b54fd165f8..24da2e89b28b7a 100644 --- a/src/app/tests/integration/chip_im_initiator.cpp +++ b/src/app/tests/integration/chip_im_initiator.cpp @@ -132,7 +132,9 @@ class MockInteractionModelApp : public chip::app::InteractionModelDelegate, public ::chip::app::ReadClient::Callback { public: - void OnEventData(const chip::app::ReadClient * apReadClient, chip::TLV::TLVReader & apEventReportsReader) override {} + void OnEventData(const chip::app::ReadClient * apReadClient, const chip::app::EventHeader & aEventHeader, + chip::TLV::TLVReader * apData, const chip::app::StatusIB * apStatus) override + {} void OnSubscriptionEstablished(const chip::app::ReadClient * apReadClient) override { if (apReadClient->IsSubscriptionType()) diff --git a/src/controller/ReadInteraction.h b/src/controller/ReadInteraction.h index bc9b4aaf59aec7..686cb45295ff32 100644 --- a/src/controller/ReadInteraction.h +++ b/src/controller/ReadInteraction.h @@ -44,8 +44,8 @@ namespace Controller { template CHIP_ERROR ReadAttribute(Messaging::ExchangeManager * aExchangeMgr, const SessionHandle sessionHandle, EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, - typename TypedReadCallback::OnSuccessCallbackType onSuccessCb, - typename TypedReadCallback::OnErrorCallbackType onErrorCb) + typename TypedReadAttributeCallback::OnSuccessCallbackType onSuccessCb, + typename TypedReadAttributeCallback::OnErrorCallbackType onErrorCb) { app::AttributePathParams attributePath(endpointId, clusterId, attributeId); app::ReadPrepareParams readParams(sessionHandle); @@ -56,12 +56,63 @@ CHIP_ERROR ReadAttribute(Messaging::ExchangeManager * aExchangeMgr, const Sessio readParams.mpAttributePathParamsList = &attributePath; readParams.mAttributePathParamsListSize = 1; - auto onDone = [](app::ReadClient * apReadClient, TypedReadCallback * callback) { + auto onDone = [](app::ReadClient * apReadClient, TypedReadAttributeCallback * callback) { chip::Platform::Delete(callback); }; - auto callback = chip::Platform::MakeUnique>(clusterId, attributeId, onSuccessCb, - onErrorCb, onDone); + auto callback = chip::Platform::MakeUnique>(clusterId, attributeId, + onSuccessCb, onErrorCb, onDone); + VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY); + + ReturnErrorOnFailure(engine->NewReadClient(&readClient, app::ReadClient::InteractionType::Read, callback.get())); + + err = readClient->SendReadRequest(readParams); + if (err != CHIP_NO_ERROR) + { + readClient->Shutdown(); + return err; + } + + // + // At this point, we'll get a callback through the OnDone callback above regardless of success or failure + // of the read operation to permit us to free up the callback object. So, release ownership of the callback + // object now to prevent it from being reclaimed at the end of this scoped block. + // + callback.release(); + return err; +} + +/* + * A typed read event function that takes as input a template parameter that encapsulates the type information + * for a given attribute as well as callbacks for success and failure and either returns a decoded cluster-object representation + * of the requested attribute through the provided success callback or calls the provided failure callback. + * + * The EventTypeInfo is generally expected to be a ClusterName::Events::EventName::TypeInfo struct, but any + * object that contains type information exposed through a 'DecodableType' type declaration as well as GetClusterId() and + * GetEventId() methods is expected to work. + */ +template +CHIP_ERROR ReadEvent(Messaging::ExchangeManager * apExchangeMgr, const SessionHandle sessionHandle, EndpointId endpointId, + ClusterId clusterId, EventId eventId, + typename TypedReadEventCallback::OnSuccessCallbackType onSuccessCb, + typename TypedReadEventCallback::OnErrorCallbackType onErrorCb) +{ + app::EventPathParams eventPath(endpointId, clusterId, eventId); + app::ReadPrepareParams readParams(sessionHandle); + app::ReadClient * readClient = nullptr; + app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); + CHIP_ERROR err = CHIP_NO_ERROR; + + readParams.mpEventPathParamsList = &eventPath; + readParams.mEventPathParamsListSize = 1; + + auto onDone = [](app::ReadClient * apReadClient, TypedReadEventCallback * callback) { + chip::Platform::Delete(callback); + }; + + auto callback = chip::Platform::MakeUnique>(clusterId, eventId, onSuccessCb, + onErrorCb, onDone); + VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY); ReturnErrorOnFailure(engine->NewReadClient(&readClient, app::ReadClient::InteractionType::Read, callback.get())); @@ -83,14 +134,24 @@ CHIP_ERROR ReadAttribute(Messaging::ExchangeManager * aExchangeMgr, const Sessio } template -CHIP_ERROR ReadAttribute(Messaging::ExchangeManager * aExchangeMgr, const SessionHandle sessionHandle, EndpointId endpointId, - typename TypedReadCallback::OnSuccessCallbackType onSuccessCb, - typename TypedReadCallback::OnErrorCallbackType onErrorCb) +CHIP_ERROR +ReadAttribute(Messaging::ExchangeManager * aExchangeMgr, const SessionHandle sessionHandle, EndpointId endpointId, + typename TypedReadAttributeCallback::OnSuccessCallbackType onSuccessCb, + typename TypedReadAttributeCallback::OnErrorCallbackType onErrorCb) { return ReadAttribute(aExchangeMgr, sessionHandle, endpointId, AttributeTypeInfo::GetClusterId(), AttributeTypeInfo::GetAttributeId(), onSuccessCb, onErrorCb); } +template +CHIP_ERROR ReadEvent(Messaging::ExchangeManager * aExchangeMgr, const SessionHandle sessionHandle, EndpointId endpointId, + typename TypedReadEventCallback::OnSuccessCallbackType onSuccessCb, + typename TypedReadEventCallback::OnErrorCallbackType onErrorCb) +{ + return ReadAttribute(aExchangeMgr, sessionHandle, endpointId, + EventTypeInfo::GetClusterId(), EventTypeInfo::GetEventId(), + onSuccessCb, onErrorCb); +} } // namespace Controller } // namespace chip diff --git a/src/controller/TypedReadCallback.h b/src/controller/TypedReadCallback.h index 54e641a7d8287d..c4015d11335641 100644 --- a/src/controller/TypedReadCallback.h +++ b/src/controller/TypedReadCallback.h @@ -36,29 +36,29 @@ namespace Controller { * cluster object. */ template -class TypedReadCallback final : public app::ReadClient::Callback +class TypedReadAttributeCallback final : public app::ReadClient::Callback { public: using OnSuccessCallbackType = std::function; using OnErrorCallbackType = std::function; - using OnDoneCallbackType = std::function; + using OnDoneCallbackType = std::function; - TypedReadCallback(ClusterId aClusterId, AttributeId aAttributeId, OnSuccessCallbackType aOnSuccess, - OnErrorCallbackType aOnError, OnDoneCallbackType aOnDone) : + TypedReadAttributeCallback(ClusterId aClusterId, AttributeId aAttributeId, OnSuccessCallbackType aOnSuccess, + OnErrorCallbackType aOnError, OnDoneCallbackType aOnDone) : mClusterId(aClusterId), mAttributeId(aAttributeId), mOnSuccess(aOnSuccess), mOnError(aOnError), mOnDone(aOnDone) {} private: void OnAttributeData(const app::ReadClient * apReadClient, const app::ConcreteAttributePath & aPath, TLV::TLVReader * apData, - const app::StatusIB & status) override + const app::StatusIB & aStatus) override { CHIP_ERROR err = CHIP_NO_ERROR; DecodableAttributeType value; - VerifyOrExit(status.mStatus == Protocols::InteractionModel::Status::Success, err = CHIP_ERROR_IM_STATUS_CODE_RECEIVED); + VerifyOrExit(aStatus.mStatus == Protocols::InteractionModel::Status::Success, err = CHIP_ERROR_IM_STATUS_CODE_RECEIVED); VerifyOrExit(aPath.mClusterId == mClusterId && aPath.mAttributeId == mAttributeId, err = CHIP_ERROR_SCHEMA_MISMATCH); VerifyOrExit(apData != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); @@ -71,10 +71,10 @@ class TypedReadCallback final : public app::ReadClient::Callback if (err != CHIP_NO_ERROR) { // - // Override status to indicate an error if something bad happened above. + // Override aStatus to indicate an error if something bad happened above. // - Protocols::InteractionModel::Status imStatus = status.mStatus; - if (status.mStatus == Protocols::InteractionModel::Status::Success) + Protocols::InteractionModel::Status imStatus = aStatus.mStatus; + if (aStatus.mStatus == Protocols::InteractionModel::Status::Success) { imStatus = Protocols::InteractionModel::Status::Failure; } @@ -97,5 +97,57 @@ class TypedReadCallback final : public app::ReadClient::Callback OnDoneCallbackType mOnDone; }; +template +class TypedReadEventCallback final : public app::ReadClient::Callback +{ +public: + using OnSuccessCallbackType = std::function; + using OnErrorCallbackType = std::function; + using OnDoneCallbackType = std::function; + + TypedReadEventCallback(ClusterId aClusterId, EventId aEventId, OnSuccessCallbackType aOnSuccess, OnErrorCallbackType aOnError, + OnDoneCallbackType aOnDone) : + mClusterId(aClusterId), + mEventId(aEventId), mOnSuccess(aOnSuccess), mOnError(aOnError), mOnDone(aOnDone) + {} + +private: + void OnEventData(const app::ReadClient * apReadClient, const app::EventHeader & aEventHeader, TLV::TLVReader * apData, + const app::StatusIB * apStatus) override + { + CHIP_ERROR err = CHIP_NO_ERROR; + DecodableEventTypeInfo value; + VerifyOrExit(aEventHeader.mPath.mClusterId == DecodableEventTypeInfo::GetClusterId() && + aEventHeader.mPath.mEventId == DecodableEventTypeInfo::GetEventId(), + CHIP_ERROR_SCHEMA_MISMATCH); + VerifyOrExit(apData != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + + err = app::DataModel::Decode(*apData, value); + SuccessOrExit(err); + + mOnSuccess(aEventHeader, value); + + exit: + if (err != CHIP_NO_ERROR) + { + mOnError(&aEventHeader, Protocols::InteractionModel::Status::Failure, err); + } + } + + void OnError(const app::ReadClient * apReadClient, CHIP_ERROR aError) override + { + mOnError(nullptr, Protocols::InteractionModel::Status::Failure, aError); + } + + void OnDone(app::ReadClient * apReadClient) override { mOnDone(apReadClient, this); } + + ClusterId mClusterId; + EventId mEventId; + OnSuccessCallbackType mOnSuccess; + OnErrorCallbackType mOnError; + OnDoneCallbackType mOnDone; +}; + } // namespace Controller } // namespace chip diff --git a/src/lib/core/CHIPError.cpp b/src/lib/core/CHIPError.cpp index 9e41fa862f9e1f..fbb0b04dc5e426 100644 --- a/src/lib/core/CHIPError.cpp +++ b/src/lib/core/CHIPError.cpp @@ -683,6 +683,9 @@ bool FormatCHIPError(char * buf, uint16_t bufSize, CHIP_ERROR err) case CHIP_ERROR_IM_MALFORMED_SUBSCRIBE_RESPONSE_MESSAGE.AsInteger(): desc = "Malformed Interaction Model Subscribe Response Message"; break; + case CHIP_ERROR_IM_MALFORMED_EVENT_REPORT_IB.AsInteger(): + desc = "Malformed Interaction Model Event Report IB"; + break; } #endif // !CHIP_CONFIG_SHORT_ERROR_STR diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index b2f7bc541f4224..45f8f710915b3a 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -2305,6 +2305,16 @@ using CHIP_ERROR = ::chip::ChipError; * the required elements */ #define CHIP_ERROR_IM_MALFORMED_SUBSCRIBE_RESPONSE_MESSAGE CHIP_CORE_ERROR(0xd4) + +/** + * @def CHIP_ERROR_IM_MALFORMED_EVENT_REPORT_IB + * + * @brief + * The EventReportIB is malformed: it either does not contain + * the required elements + */ +#define CHIP_ERROR_IM_MALFORMED_EVENT_REPORT_IB CHIP_CORE_ERROR(0xd5) + /** * @} */