blob: 5913aa29d1c76ad6909ccbc77e68530f807b937b [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 <app/ReadHandler.h>
#include <app/reporting/Engine.h>
namespace chip {
namespace app {
CHIP_ERROR ReadHandler::Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate,
Messaging::ExchangeContext * apExchangeContext, InteractionType aInteractionType)
{
CHIP_ERROR err = CHIP_NO_ERROR;
// Error if already initialized.
VerifyOrReturnError(IsFree(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mpExchangeCtx == nullptr, err = CHIP_ERROR_INCORRECT_STATE);
mpExchangeMgr = apExchangeMgr;
mpExchangeCtx = apExchangeContext;
mSuppressResponse = true;
mpAttributeClusterInfoList = nullptr;
mpEventClusterInfoList = nullptr;
mCurrentPriority = PriorityLevel::Invalid;
mInitialReport = true;
MoveToState(HandlerState::Initialized);
mpDelegate = apDelegate;
mSubscriptionId = 0;
mHoldReport = false;
mDirty = false;
mActiveSubscription = false;
mInteractionType = aInteractionType;
mInitiatorNodeId = apExchangeContext->GetSessionHandle().GetPeerNodeId();
mFabricIndex = apExchangeContext->GetSessionHandle().GetFabricIndex();
if (apExchangeContext != nullptr)
{
apExchangeContext->SetDelegate(this);
}
return err;
}
void ReadHandler::Shutdown(ShutdownOptions aOptions)
{
if (IsSubscriptionType())
{
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer(
OnRefreshSubscribeTimerSyncCallback, this);
if (mpDelegate != nullptr)
{
mpDelegate->SubscriptionTerminated(this);
}
}
if (aOptions == ShutdownOptions::AbortCurrentExchange)
{
if (mpExchangeCtx != nullptr)
{
mpExchangeCtx->Abort();
mpExchangeCtx = nullptr;
}
}
if (IsAwaitingReportResponse())
{
InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm();
}
InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpAttributeClusterInfoList);
InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpEventClusterInfoList);
mSubscriptionId = 0;
mMinIntervalFloorSeconds = 0;
mMaxIntervalCeilingSeconds = 0;
mInteractionType = InteractionType::Read;
mpExchangeCtx = nullptr;
MoveToState(HandlerState::Uninitialized);
mpAttributeClusterInfoList = nullptr;
mpEventClusterInfoList = nullptr;
mCurrentPriority = PriorityLevel::Invalid;
mInitialReport = false;
mpDelegate = nullptr;
mHoldReport = false;
mDirty = false;
mActiveSubscription = false;
mInitiatorNodeId = kUndefinedNodeId;
}
CHIP_ERROR ReadHandler::OnReadInitialRequest(System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferHandle response;
if (IsSubscriptionType())
{
err = ProcessSubscribeRequest(std::move(aPayload));
}
else
{
err = ProcessReadRequest(std::move(aPayload));
}
if (err != CHIP_NO_ERROR)
{
Shutdown();
}
return err;
}
CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Protocols::InteractionModel::Status statusCode;
StatusResponseMessage::Parser response;
System::PacketBufferTLVReader reader;
reader.Init(std::move(aPayload));
reader.Next();
err = response.Init(reader);
SuccessOrExit(err);
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
err = response.CheckSchemaValidity();
SuccessOrExit(err);
#endif
err = response.GetStatus(statusCode);
SuccessOrExit(err);
ChipLogProgress(DataManagement, "In state %s, receive status response, status code is %" PRIu16, GetStateStr(),
to_underlying(statusCode));
VerifyOrExit((statusCode == Protocols::InteractionModel::Status::Success), err = CHIP_ERROR_INVALID_ARGUMENT);
switch (mState)
{
case HandlerState::AwaitingReportResponse:
if (IsSubscriptionType())
{
InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm();
if (IsInitialReport())
{
err = SendSubscribeResponse();
mpExchangeCtx = nullptr;
SuccessOrExit(err);
mActiveSubscription = true;
}
else
{
MoveToState(HandlerState::GeneratingReports);
mpExchangeCtx = nullptr;
}
}
else
{
Shutdown();
}
break;
case HandlerState::GeneratingReports:
case HandlerState::Initialized:
case HandlerState::Uninitialized:
default:
err = CHIP_ERROR_INCORRECT_STATE;
break;
}
exit:
if (err != CHIP_NO_ERROR)
{
Shutdown();
}
return err;
}
CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload)
{
VerifyOrReturnLogError(IsReportable(), CHIP_ERROR_INCORRECT_STATE);
if (IsInitialReport())
{
mSessionHandle.SetValue(mpExchangeCtx->GetSessionHandle());
}
else
{
VerifyOrReturnLogError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE);
mpExchangeCtx = mpExchangeMgr->NewContext(mSessionHandle.Value(), this);
mpExchangeCtx->SetResponseTimeout(kImMessageTimeout);
}
VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE);
MoveToState(HandlerState::AwaitingReportResponse);
CHIP_ERROR err = mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(aPayload),
Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse));
if (err == CHIP_NO_ERROR)
{
if (IsSubscriptionType() && !IsInitialReport())
{
err = RefreshSubscribeSyncTimer();
}
}
ClearDirty();
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;
}
CHIP_ERROR ReadHandler::OnUnknownMsgType(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload)
{
ChipLogDetail(DataManagement, "Msg type %d not supported", aPayloadHeader.GetMessageType());
Shutdown();
return CHIP_ERROR_INVALID_MESSAGE_TYPE;
}
void ReadHandler::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
{
ChipLogProgress(DataManagement, "Time out! failed to receive status response from Exchange: " ChipLogFormatExchange,
ChipLogValueExchange(apExchangeContext));
Shutdown();
}
CHIP_ERROR ReadHandler::ProcessReadRequest(System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferTLVReader reader;
ReadRequestMessage::Parser readRequestParser;
EventPaths::Parser eventPathListParser;
AttributePaths::Parser attributePathListParser;
reader.Init(std::move(aPayload));
err = reader.Next();
SuccessOrExit(err);
err = readRequestParser.Init(reader);
SuccessOrExit(err);
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
err = readRequestParser.CheckSchemaValidity();
SuccessOrExit(err);
#endif
err = readRequestParser.GetAttributePathList(&attributePathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else
{
SuccessOrExit(err);
err = ProcessAttributePathList(attributePathListParser);
}
SuccessOrExit(err);
err = readRequestParser.GetEventPaths(&eventPathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else
{
SuccessOrExit(err);
err = ProcessEventPaths(eventPathListParser);
}
SuccessOrExit(err);
// if we have exhausted this container
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
MoveToState(HandlerState::GeneratingReports);
err = InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
SuccessOrExit(err);
// mpExchangeCtx can be null here due to
// https://github.com/project-chip/connectedhomeip/issues/8031
if (mpExchangeCtx)
{
mpExchangeCtx->WillSendMessage();
}
// There must be no code after the WillSendMessage() call that can cause
// this method to return a failure.
exit:
return err;
}
CHIP_ERROR ReadHandler::ProcessAttributePathList(AttributePaths::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);
VerifyOrExit(TLV::kTLVType_List == reader.GetType(), err = CHIP_ERROR_WRONG_TLV_TYPE);
ClusterInfo clusterInfo;
AttributePathIB::Parser path;
err = path.Init(reader);
SuccessOrExit(err);
// TODO: Support wildcard paths here
// 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.
err = path.GetEndpoint(&(clusterInfo.mEndpointId));
if (err == CHIP_NO_ERROR)
{
VerifyOrExit(!clusterInfo.HasWildcardEndpointId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
SuccessOrExit(err);
err = path.GetCluster(&(clusterInfo.mClusterId));
if (err == CHIP_NO_ERROR)
{
VerifyOrExit(!clusterInfo.HasWildcardClusterId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
SuccessOrExit(err);
err = path.GetAttribute(&(clusterInfo.mFieldId));
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
VerifyOrExit(!clusterInfo.HasWildcardAttributeId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
SuccessOrExit(err);
err = path.GetListIndex(&(clusterInfo.mListIndex));
if (CHIP_NO_ERROR == err)
{
VerifyOrExit(!clusterInfo.HasWildcardAttributeId() && !clusterInfo.HasWildcardListIndex(),
err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
}
else if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
SuccessOrExit(err);
err = InteractionModelEngine::GetInstance()->PushFront(mpAttributeClusterInfoList, clusterInfo);
SuccessOrExit(err);
mInitialReport = true;
}
// if we have exhausted this container
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
exit:
return err;
}
CHIP_ERROR ReadHandler::ProcessEventPaths(EventPaths::Parser & aEventPathsParser)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TLV::TLVReader reader;
aEventPathsParser.GetReader(&reader);
while (CHIP_NO_ERROR == (err = reader.Next()))
{
VerifyOrExit(TLV::AnonymousTag == reader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG);
VerifyOrExit(TLV::kTLVType_List == reader.GetType(), err = CHIP_ERROR_WRONG_TLV_TYPE);
ClusterInfo clusterInfo;
EventPathIB::Parser path;
err = path.Init(reader);
SuccessOrExit(err);
err = path.GetNode(&(clusterInfo.mNodeId));
SuccessOrExit(err);
err = path.GetEndpoint(&(clusterInfo.mEndpointId));
SuccessOrExit(err);
err = path.GetCluster(&(clusterInfo.mClusterId));
SuccessOrExit(err);
err = path.GetEvent(&(clusterInfo.mEventId));
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
SuccessOrExit(err);
err = InteractionModelEngine::GetInstance()->PushFront(mpEventClusterInfoList, clusterInfo);
SuccessOrExit(err);
}
// if we have exhausted this container
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
exit:
return err;
}
const char * ReadHandler::GetStateStr() const
{
#if CHIP_DETAIL_LOGGING
switch (mState)
{
case HandlerState::Uninitialized:
return "Uninitialized";
case HandlerState::Initialized:
return "Initialized";
case HandlerState::GeneratingReports:
return "GeneratingReports";
case HandlerState::AwaitingReportResponse:
return "AwaitingReportResponse";
}
#endif // CHIP_DETAIL_LOGGING
return "N/A";
}
void ReadHandler::MoveToState(const HandlerState aTargetState)
{
mState = aTargetState;
ChipLogDetail(DataManagement, "IM RH moving to [%s]", GetStateStr());
}
bool ReadHandler::CheckEventClean(EventManagement & aEventManager)
{
if (mCurrentPriority == PriorityLevel::Invalid)
{
// Upload is not in middle, previous mLastScheduledEventNumber is not valid, Check for new events from Critical high
// priority to Debug low priority, and set a checkpoint when there is dirty events
for (int index = ArraySize(mSelfProcessedEvents) - 1; index >= 0; index--)
{
EventNumber lastEventNumber = aEventManager.GetLastEventNumber(static_cast<PriorityLevel>(index));
if ((lastEventNumber != 0) && (lastEventNumber >= mSelfProcessedEvents[index]))
{
// We have more events. snapshot last event IDs
aEventManager.SetScheduledEventEndpoint(&(mLastScheduledEventNumber[0]));
// initialize the next dirty priority level to transfer
MoveToNextScheduledDirtyPriority();
return false;
}
}
return true;
}
else
{
// Upload is in middle, previous mLastScheduledEventNumber is still valid, recheck via MoveToNextScheduledDirtyPriority,
// if finally mCurrentPriority is invalid, it means no more event
MoveToNextScheduledDirtyPriority();
return mCurrentPriority == PriorityLevel::Invalid;
}
}
void ReadHandler::MoveToNextScheduledDirtyPriority()
{
for (int i = ArraySize(mSelfProcessedEvents) - 1; i >= 0; i--)
{
if ((mLastScheduledEventNumber[i] != 0) && mSelfProcessedEvents[i] <= mLastScheduledEventNumber[i])
{
mCurrentPriority = static_cast<PriorityLevel>(i);
return;
}
}
mCurrentPriority = PriorityLevel::Invalid;
}
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;
ReturnLogErrorOnFailure(response.Init(&writer));
response.SubscriptionId(mSubscriptionId)
.MinIntervalFloorSeconds(mMinIntervalFloorSeconds)
.MaxIntervalCeilingSeconds(mMaxIntervalCeilingSeconds)
.EndOfSubscribeResponseMessage();
ReturnLogErrorOnFailure(response.GetError());
ReturnLogErrorOnFailure(writer.Finalize(&packet));
VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnLogErrorOnFailure(RefreshSubscribeSyncTimer());
mInitialReport = false;
MoveToState(HandlerState::GeneratingReports);
if (mpDelegate != nullptr)
{
mpDelegate->SubscriptionEstablished(this);
}
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));
ReturnLogErrorOnFailure(reader.Next());
SubscribeRequestMessage::Parser subscribeRequestParser;
ReturnLogErrorOnFailure(subscribeRequestParser.Init(reader));
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
ReturnLogErrorOnFailure(subscribeRequestParser.CheckSchemaValidity());
#endif
AttributePaths::Parser attributePathListParser;
CHIP_ERROR err = subscribeRequestParser.GetAttributePathList(&attributePathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnLogErrorOnFailure(ProcessAttributePathList(attributePathListParser));
}
ReturnLogErrorOnFailure(err);
EventPaths::Parser eventPathListParser;
err = subscribeRequestParser.GetEventPaths(&eventPathListParser);
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
else if (err == CHIP_NO_ERROR)
{
ReturnLogErrorOnFailure(ProcessEventPaths(eventPathListParser));
}
ReturnLogErrorOnFailure(err);
ReturnLogErrorOnFailure(subscribeRequestParser.GetMinIntervalSeconds(&mMinIntervalFloorSeconds));
ReturnLogErrorOnFailure(subscribeRequestParser.GetMaxIntervalSeconds(&mMaxIntervalCeilingSeconds));
ReturnLogErrorOnFailure(Crypto::DRBG_get_bytes(reinterpret_cast<uint8_t *>(&mSubscriptionId), sizeof(mSubscriptionId)));
MoveToState(HandlerState::GeneratingReports);
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
// mpExchangeCtx can be null here due to
// https://github.com/project-chip/connectedhomeip/issues/8031
if (mpExchangeCtx)
{
mpExchangeCtx->WillSendMessage();
}
return CHIP_NO_ERROR;
}
void ReadHandler::OnRefreshSubscribeTimerSyncCallback(System::Layer * apSystemLayer, void * apAppState)
{
ReadHandler * aReadHandler = static_cast<ReadHandler *>(apAppState);
aReadHandler->mHoldReport = false;
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
}
CHIP_ERROR ReadHandler::RefreshSubscribeSyncTimer()
{
ChipLogProgress(DataManagement, "ReadHandler::Refresh Subscribe Sync Timer with %d seconds", mMinIntervalFloorSeconds);
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer(
OnRefreshSubscribeTimerSyncCallback, this);
mHoldReport = true;
return InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer(
System::Clock::Seconds16(mMinIntervalFloorSeconds), OnRefreshSubscribeTimerSyncCallback, this);
}
} // namespace app
} // namespace chip