blob: f409fc0aceb7d68122e0d709b9f44b30e18ba6d1 [file] [log] [blame]
/*
*
* Copyright (c) 2020 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.
*/
/**
* @file
* This file defines read handler for a CHIP Interaction Data model
*
*/
#include <app/AppBuildConfig.h>
#include <app/InteractionModelEngine.h>
#include <app/MessageDef/EventPathIB.h>
#include <app/MessageDef/StatusResponseMessage.h>
#include <app/MessageDef/SubscribeRequestMessage.h>
#include <app/MessageDef/SubscribeResponseMessage.h>
#include <messaging/ExchangeContext.h>
#include <app/ReadHandler.h>
#include <app/reporting/Engine.h>
namespace chip {
namespace app {
ReadHandler::ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeContext * apExchangeContext,
InteractionType aInteractionType) :
mManagementCallback(apCallback)
{
mpExchangeMgr = apExchangeContext->GetExchangeMgr();
mpExchangeCtx = apExchangeContext;
mInteractionType = aInteractionType;
mInitiatorNodeId = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId();
mSubjectDescriptor = apExchangeContext->GetSessionHandle()->GetSubjectDescriptor();
mLastWrittenEventsBytes = 0;
if (apExchangeContext != nullptr)
{
apExchangeContext->SetDelegate(this);
}
}
void ReadHandler::Abort(bool aCalledFromDestructor)
{
//
// If the exchange context hasn't already been gracefully closed
// (signaled by setting it to null), then we need to forcibly
// tear it down.
//
if (mpExchangeCtx != nullptr)
{
// We might be a delegate for this exchange, and we don't want the
// OnExchangeClosing notification in that case. Null out the delegate
// to avoid that.
//
// TODO: This makes all sorts of assumptions about what the delegate is
// (notice the "might" above!) that might not hold in practice. We
// really need a better solution here....
mpExchangeCtx->SetDelegate(nullptr);
mpExchangeCtx->Abort();
mpExchangeCtx = nullptr;
}
if (!aCalledFromDestructor)
{
Close();
}
}
ReadHandler::~ReadHandler()
{
auto * appCallback = mManagementCallback.GetAppCallback();
if (mActiveSubscription && appCallback)
{
appCallback->OnSubscriptionTerminated(*this);
}
Abort(true);
if (IsType(InteractionType::Subscribe))
{
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer(
OnUnblockHoldReportCallback, this);
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer(
OnRefreshSubscribeTimerSyncCallback, this);
}
if (IsAwaitingReportResponse())
{
InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm();
}
InteractionModelEngine::GetInstance()->ReleaseAttributePathList(mpAttributePathList);
InteractionModelEngine::GetInstance()->ReleaseEventPathList(mpEventPathList);
InteractionModelEngine::GetInstance()->ReleaseDataVersionFilterList(mpDataVersionFilterList);
}
void ReadHandler::Close()
{
if (mpExchangeCtx != nullptr)
{
mpExchangeCtx->SetDelegate(nullptr);
mpExchangeCtx = nullptr;
}
MoveToState(HandlerState::AwaitingDestruction);
mManagementCallback.OnDone(*this);
}
CHIP_ERROR ReadHandler::OnInitialRequest(System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferHandle response;
if (IsType(InteractionType::Subscribe))
{
err = ProcessSubscribeRequest(std::move(aPayload));
}
else
{
err = ProcessReadRequest(std::move(aPayload));
}
if (err != CHIP_NO_ERROR)
{
Close();
}
else
{
// Force us to be in a dirty state so we get processed by the reporting
mForceDirty = true;
}
return err;
}
CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
err = StatusResponse::ProcessStatusResponse(std::move(aPayload));
SuccessOrExit(err);
switch (mState)
{
case HandlerState::AwaitingReportResponse:
if (IsChunkedReport())
{
MoveToState(HandlerState::GeneratingReports);
mpExchangeCtx->WillSendMessage();
// Trigger ReportingEngine run for sending next chunk of data.
SuccessOrExit(err = InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun());
}
else if (IsType(InteractionType::Subscribe))
{
if (IsPriming())
{
err = SendSubscribeResponse();
mpExchangeCtx = nullptr;
SuccessOrExit(err);
mActiveSubscription = true;
auto * appCallback = mManagementCallback.GetAppCallback();
if (appCallback)
{
appCallback->OnSubscriptionEstablished(*this);
}
}
else
{
MoveToState(HandlerState::GeneratingReports);
mpExchangeCtx = nullptr;
}
}
else
{
Close();
}
break;
case HandlerState::GeneratingReports:
case HandlerState::Idle:
default:
err = CHIP_ERROR_INCORRECT_STATE;
break;
}
exit:
if (err != CHIP_NO_ERROR)
{
Close();
}
return err;
}
CHIP_ERROR ReadHandler::SendStatusReport(Protocols::InteractionModel::Status aStatus)
{
VerifyOrReturnLogError(IsReportable(), CHIP_ERROR_INCORRECT_STATE);
if (IsPriming() || IsChunkedReport())
{
mSessionHandle.Grab(mpExchangeCtx->GetSessionHandle());
}
else
{
VerifyOrReturnLogError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(mSessionHandle, CHIP_ERROR_INCORRECT_STATE);
mpExchangeCtx = mpExchangeMgr->NewContext(mSessionHandle.Get(), this);
}
VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE);
return StatusResponse::Send(aStatus, mpExchangeCtx,
/* aExpectResponse = */ false);
}
CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, bool aMoreChunks)
{
VerifyOrReturnLogError(IsReportable(), CHIP_ERROR_INCORRECT_STATE);
if (IsPriming() || IsChunkedReport())
{
mSessionHandle.Grab(mpExchangeCtx->GetSessionHandle());
}
else
{
VerifyOrReturnLogError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(mSessionHandle, CHIP_ERROR_INCORRECT_STATE);
mpExchangeCtx = mpExchangeMgr->NewContext(mSessionHandle.Get(), this);
}
VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE);
if (!IsReporting())
{
mCurrentReportsBeginGeneration = InteractionModelEngine::GetInstance()->GetReportingEngine().GetDirtySetGeneration();
}
mIsChunkedReport = aMoreChunks;
bool noResponseExpected = IsType(InteractionType::Read) && !mIsChunkedReport;
if (!noResponseExpected)
{
MoveToState(HandlerState::AwaitingReportResponse);
}
mpExchangeCtx->SetResponseTimeout(kImMessageTimeout);
CHIP_ERROR err =
mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(aPayload),
Messaging::SendFlags(noResponseExpected ? Messaging::SendMessageFlags::kNone
: Messaging::SendMessageFlags::kExpectResponse));
if (err == CHIP_NO_ERROR && noResponseExpected)
{
mpExchangeCtx = nullptr;
InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm();
}
if (err == CHIP_NO_ERROR)
{
if (IsType(InteractionType::Subscribe) && !IsPriming())
{
err = RefreshSubscribeSyncTimer();
}
}
if (!aMoreChunks)
{
mPreviousReportsBeginGeneration = mCurrentReportsBeginGeneration;
ClearDirty();
InteractionModelEngine::GetInstance()->ReleaseDataVersionFilterList(mpDataVersionFilterList);
}
return err;
}
CHIP_ERROR ReadHandler::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse))
{
err = OnStatusResponse(apExchangeContext, std::move(aPayload));
}
else
{
err = OnUnknownMsgType(apExchangeContext, aPayloadHeader, std::move(aPayload));
}
return err;
}
bool ReadHandler::IsFromSubscriber(Messaging::ExchangeContext & apExchangeContext) const
{
return (IsType(InteractionType::Subscribe) &&
GetInitiatorNodeId() == apExchangeContext.GetSessionHandle()->AsSecureSession()->GetPeerNodeId() &&
GetAccessingFabricIndex() == apExchangeContext.GetSessionHandle()->GetFabricIndex());
}
CHIP_ERROR ReadHandler::OnUnknownMsgType(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload)
{
ChipLogDetail(DataManagement, "Msg type %d not supported", aPayloadHeader.GetMessageType());
Close();
return CHIP_ERROR_INVALID_MESSAGE_TYPE;
}
void ReadHandler::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
{
ChipLogError(DataManagement, "Time out! failed to receive status response from Exchange: " ChipLogFormatExchange,
ChipLogValueExchange(apExchangeContext));
Close();
}
CHIP_ERROR ReadHandler::ProcessReadRequest(System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferTLVReader reader;
ReadRequestMessage::Parser readRequestParser;
EventPathIBs::Parser eventPathListParser;
EventFilterIBs::Parser eventFilterIBsParser;
AttributePathIBs::Parser attributePathListParser;
reader.Init(std::move(aPayload));
ReturnErrorOnFailure(readRequestParser.Init(reader));
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
ReturnErrorOnFailure(readRequestParser.CheckSchemaValidity());
#endif
err = readRequestParser.GetAttributeRequests(&attributePathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(ProcessAttributePathList(attributePathListParser));
DataVersionFilterIBs::Parser dataVersionFilterListParser;
err = readRequestParser.GetDataVersionFilters(&dataVersionFilterListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser));
}
}
ReturnErrorOnFailure(err);
err = readRequestParser.GetEventRequests(&eventPathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(err);
ReturnErrorOnFailure(ProcessEventPaths(eventPathListParser));
err = readRequestParser.GetEventFilters(&eventFilterIBsParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(ProcessEventFilters(eventFilterIBsParser));
}
}
ReturnErrorOnFailure(err);
ReturnErrorOnFailure(readRequestParser.GetIsFabricFiltered(&mIsFabricFiltered));
ReturnErrorOnFailure(readRequestParser.ExitContainer());
MoveToState(HandlerState::GeneratingReports);
ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun());
mpExchangeCtx->WillSendMessage();
// There must be no code after the WillSendMessage() call that can cause
// this method to return a failure.
return CHIP_NO_ERROR;
}
CHIP_ERROR ReadHandler::ProcessAttributePathList(AttributePathIBs::Parser & aAttributePathListParser)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TLV::TLVReader reader;
aAttributePathListParser.GetReader(&reader);
while (CHIP_NO_ERROR == (err = reader.Next()))
{
VerifyOrExit(TLV::AnonymousTag() == reader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG);
AttributePathParams attribute;
AttributePathIB::Parser path;
err = path.Init(reader);
SuccessOrExit(err);
// TODO: MEIs (ClusterId and AttributeId) have a invalid pattern instead of a single invalid value, need to add separate
// functions for checking if we have received valid values.
// TODO: Wildcard cluster id with non-global attributes or wildcard attribute paths should be rejected.
err = path.GetEndpoint(&(attribute.mEndpointId));
if (err == CHIP_NO_ERROR)
{
VerifyOrExit(!attribute.HasWildcardEndpointId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
else if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
SuccessOrExit(err);
err = path.GetCluster(&(attribute.mClusterId));
if (err == CHIP_NO_ERROR)
{
VerifyOrExit(!attribute.HasWildcardClusterId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
else if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
SuccessOrExit(err);
err = path.GetAttribute(&(attribute.mAttributeId));
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
VerifyOrExit(!attribute.HasWildcardAttributeId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
SuccessOrExit(err);
err = path.GetListIndex(&(attribute.mListIndex));
if (CHIP_NO_ERROR == err)
{
VerifyOrExit(!attribute.HasWildcardAttributeId() && !attribute.HasWildcardListIndex(),
err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
else if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
SuccessOrExit(err);
err = InteractionModelEngine::GetInstance()->PushFrontAttributePathList(mpAttributePathList, attribute);
SuccessOrExit(err);
}
// if we have exhausted this container
if (CHIP_END_OF_TLV == err)
{
mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributePathList);
err = CHIP_NO_ERROR;
}
exit:
return err;
}
CHIP_ERROR ReadHandler::ProcessDataVersionFilterList(DataVersionFilterIBs::Parser & aDataVersionFilterListParser)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TLV::TLVReader reader;
aDataVersionFilterListParser.GetReader(&reader);
while (CHIP_NO_ERROR == (err = reader.Next()))
{
VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
DataVersionFilter versionFilter;
ClusterPathIB::Parser path;
DataVersionFilterIB::Parser filter;
ReturnErrorOnFailure(filter.Init(reader));
DataVersion version = 0;
ReturnErrorOnFailure(filter.GetDataVersion(&version));
versionFilter.mDataVersion.SetValue(version);
ReturnErrorOnFailure(filter.GetPath(&path));
ReturnErrorOnFailure(path.GetEndpoint(&(versionFilter.mEndpointId)));
ReturnErrorOnFailure(path.GetCluster(&(versionFilter.mClusterId)));
VerifyOrReturnError(versionFilter.IsValidDataVersionFilter(), CHIP_ERROR_IM_MALFORMED_DATA_VERSION_FILTER_IB);
ReturnErrorOnFailure(
InteractionModelEngine::GetInstance()->PushFrontDataVersionFilterList(mpDataVersionFilterList, versionFilter));
}
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
return err;
}
CHIP_ERROR ReadHandler::ProcessEventPaths(EventPathIBs::Parser & aEventPathsParser)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TLV::TLVReader reader;
aEventPathsParser.GetReader(&reader);
while (CHIP_NO_ERROR == (err = reader.Next()))
{
VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
EventPathParams event;
EventPathIB::Parser path;
ReturnErrorOnFailure(path.Init(reader));
err = path.GetEndpoint(&(event.mEndpointId));
if (err == CHIP_NO_ERROR)
{
VerifyOrReturnError(!event.HasWildcardEndpointId(), err = CHIP_ERROR_IM_MALFORMED_EVENT_PATH);
}
else if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
ReturnErrorOnFailure(err);
err = path.GetCluster(&(event.mClusterId));
if (err == CHIP_NO_ERROR)
{
VerifyOrReturnError(!event.HasWildcardClusterId(), err = CHIP_ERROR_IM_MALFORMED_EVENT_PATH);
}
else if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
ReturnErrorOnFailure(err);
err = path.GetEvent(&(event.mEventId));
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
VerifyOrReturnError(!event.HasWildcardEventId(), err = CHIP_ERROR_IM_MALFORMED_EVENT_PATH);
}
ReturnErrorOnFailure(err);
err = path.GetIsUrgent(&(event.mIsUrgentEvent));
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
ReturnErrorOnFailure(err);
ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->PushFrontEventPathParamsList(mpEventPathList, event));
}
// if we have exhausted this container
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
return err;
}
CHIP_ERROR ReadHandler::ProcessEventFilters(EventFilterIBs::Parser & aEventFiltersParser)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TLV::TLVReader reader;
aEventFiltersParser.GetReader(&reader);
while (CHIP_NO_ERROR == (err = reader.Next()))
{
VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
EventFilterIB::Parser filter;
ReturnErrorOnFailure(filter.Init(reader));
// this is for current node, and would have only one event filter.
ReturnErrorOnFailure(filter.GetEventMin(&(mEventMin)));
}
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
return err;
}
const char * ReadHandler::GetStateStr() const
{
#if CHIP_DETAIL_LOGGING
switch (mState)
{
case HandlerState::Idle:
return "Idle";
case HandlerState::AwaitingDestruction:
return "AwaitingDestruction";
case HandlerState::GeneratingReports:
return "GeneratingReports";
case HandlerState::AwaitingReportResponse:
return "AwaitingReportResponse";
}
#endif // CHIP_DETAIL_LOGGING
return "N/A";
}
void ReadHandler::MoveToState(const HandlerState aTargetState)
{
if (IsAwaitingReportResponse() && aTargetState != HandlerState::AwaitingReportResponse)
{
InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm();
}
mState = aTargetState;
ChipLogDetail(DataManagement, "IM RH moving to [%s]", GetStateStr());
}
bool ReadHandler::CheckEventClean(EventManagement & aEventManager)
{
if (mIsChunkedReport)
{
if ((mLastScheduledEventNumber != 0) && (mEventMin <= mLastScheduledEventNumber))
{
return false;
}
}
else
{
EventNumber lastEventNumber = aEventManager.GetLastEventNumber();
if ((lastEventNumber != 0) && (mEventMin <= lastEventNumber))
{
// We have more events. snapshot last event number
aEventManager.SetScheduledEventInfo(mLastScheduledEventNumber, mLastWrittenEventsBytes);
return false;
}
}
return true;
}
CHIP_ERROR ReadHandler::SendSubscribeResponse()
{
System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
VerifyOrReturnLogError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY);
System::PacketBufferTLVWriter writer;
writer.Init(std::move(packet));
SubscribeResponseMessage::Builder response;
ReturnErrorOnFailure(response.Init(&writer));
response.SubscriptionId(mSubscriptionId)
.MinIntervalFloorSeconds(mMinIntervalFloorSeconds)
.MaxIntervalCeilingSeconds(mMaxIntervalCeilingSeconds)
.EndOfSubscribeResponseMessage();
ReturnErrorOnFailure(response.GetError());
ReturnErrorOnFailure(writer.Finalize(&packet));
VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(RefreshSubscribeSyncTimer());
mIsPrimingReports = false;
MoveToState(HandlerState::GeneratingReports);
return mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::SubscribeResponse, std::move(packet));
}
CHIP_ERROR ReadHandler::ProcessSubscribeRequest(System::PacketBufferHandle && aPayload)
{
System::PacketBufferTLVReader reader;
reader.Init(std::move(aPayload));
SubscribeRequestMessage::Parser subscribeRequestParser;
ReturnErrorOnFailure(subscribeRequestParser.Init(reader));
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
ReturnErrorOnFailure(subscribeRequestParser.CheckSchemaValidity());
#endif
AttributePathIBs::Parser attributePathListParser;
CHIP_ERROR err = subscribeRequestParser.GetAttributeRequests(&attributePathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(ProcessAttributePathList(attributePathListParser));
DataVersionFilterIBs::Parser dataVersionFilterListParser;
err = subscribeRequestParser.GetDataVersionFilters(&dataVersionFilterListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser));
}
}
ReturnErrorOnFailure(err);
EventPathIBs::Parser eventPathListParser;
err = subscribeRequestParser.GetEventRequests(&eventPathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(ProcessEventPaths(eventPathListParser));
EventFilterIBs::Parser eventFilterIBsParser;
err = subscribeRequestParser.GetEventFilters(&eventFilterIBsParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(ProcessEventFilters(eventFilterIBsParser));
}
}
ReturnErrorOnFailure(err);
ReturnErrorOnFailure(subscribeRequestParser.GetMinIntervalFloorSeconds(&mMinIntervalFloorSeconds));
ReturnErrorOnFailure(subscribeRequestParser.GetMaxIntervalCeilingSeconds(&mMaxIntervalCeilingSeconds));
VerifyOrReturnError(mMinIntervalFloorSeconds <= mMaxIntervalCeilingSeconds, CHIP_ERROR_INVALID_ARGUMENT);
//
// Notify the application (if requested) of the impending subscription and check whether we should still proceed to set it up.
// This also provides the application an opportunity to modify the negotiated min/max intervals set above.
//
auto * appCallback = mManagementCallback.GetAppCallback();
if (appCallback)
{
if (appCallback->OnSubscriptionRequested(*this, *mpExchangeCtx->GetSessionHandle()->AsSecureSession()) != CHIP_NO_ERROR)
{
return CHIP_ERROR_TRANSACTION_CANCELED;
}
}
ChipLogProgress(DataManagement, "Final negotiated min/max parameters: Min = %ds, Max = %ds", mMinIntervalFloorSeconds,
mMaxIntervalCeilingSeconds);
ReturnErrorOnFailure(subscribeRequestParser.GetIsFabricFiltered(&mIsFabricFiltered));
ReturnErrorOnFailure(Crypto::DRBG_get_bytes(reinterpret_cast<uint8_t *>(&mSubscriptionId), sizeof(mSubscriptionId)));
ReturnErrorOnFailure(subscribeRequestParser.ExitContainer());
MoveToState(HandlerState::GeneratingReports);
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
mpExchangeCtx->WillSendMessage();
return CHIP_NO_ERROR;
}
void ReadHandler::OnUnblockHoldReportCallback(System::Layer * apSystemLayer, void * apAppState)
{
VerifyOrReturn(apAppState != nullptr);
ReadHandler * readHandler = static_cast<ReadHandler *>(apAppState);
ChipLogDetail(DataManagement, "Unblock report hold after min %d seconds", readHandler->mMinIntervalFloorSeconds);
readHandler->mHoldReport = false;
if (readHandler->IsDirty())
{
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
}
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer(
System::Clock::Seconds16(readHandler->mMaxIntervalCeilingSeconds - readHandler->mMinIntervalFloorSeconds),
OnRefreshSubscribeTimerSyncCallback, readHandler);
}
void ReadHandler::OnRefreshSubscribeTimerSyncCallback(System::Layer * apSystemLayer, void * apAppState)
{
VerifyOrReturn(apAppState != nullptr);
ReadHandler * readHandler = static_cast<ReadHandler *>(apAppState);
readHandler->mHoldSync = false;
ChipLogDetail(DataManagement, "Refresh subscribe timer sync after %d seconds",
readHandler->mMaxIntervalCeilingSeconds - readHandler->mMinIntervalFloorSeconds);
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
}
CHIP_ERROR ReadHandler::RefreshSubscribeSyncTimer()
{
ChipLogDetail(DataManagement, "Refresh Subscribe Sync Timer with max %d seconds", mMaxIntervalCeilingSeconds);
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer(
OnUnblockHoldReportCallback, this);
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer(
OnRefreshSubscribeTimerSyncCallback, this);
mHoldReport = true;
mHoldSync = true;
ReturnErrorOnFailure(
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer(
System::Clock::Seconds16(mMinIntervalFloorSeconds), OnUnblockHoldReportCallback, this));
return CHIP_NO_ERROR;
}
void ReadHandler::ResetPathIterator()
{
mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributePathList);
mAttributeEncoderState = AttributeValueEncoder::AttributeEncodeState();
}
void ReadHandler::SetDirty(const AttributePathParams & aAttributeChanged)
{
ConcreteAttributePath path;
mDirtyGeneration = InteractionModelEngine::GetInstance()->GetReportingEngine().GetDirtySetGeneration();
// We won't reset the path iterator for every SetDirty call to reduce the number of full data reports.
// The iterator will be reset after finishing each report session.
//
// Here we just reset the iterator to the beginning of the current cluster, if the dirty path affects it.
// This will ensure the reports are consistent within a single cluster generated from a single path in the request.
// TODO (#16699): Currently we can only gurentee the reports generated from a single path in the request are consistent. The
// data might be inconsistent if the user send a request with two paths from the same cluster. We need to clearify the behavior
// or make it consistent.
if (mAttributePathExpandIterator.Get(path) &&
(aAttributeChanged.HasWildcardEndpointId() || aAttributeChanged.mEndpointId == path.mEndpointId) &&
(aAttributeChanged.HasWildcardClusterId() || aAttributeChanged.mClusterId == path.mClusterId))
{
ChipLogDetail(DataManagement,
"The dirty path intersects the cluster we are currently reporting; reset the iterator to the beginning of "
"that cluster");
// If we're currently in the middle of generating reports for a given cluster and that in turn is marked dirty, let's reset
// our iterator to point back to the beginning of that cluster. This ensures that the receiver will get a coherent view of
// the state of the cluster as present on the server
mAttributePathExpandIterator.ResetCurrentCluster();
mAttributeEncoderState = AttributeValueEncoder::AttributeEncodeState();
}
}
} // namespace app
} // namespace chip