blob: b14f2c4bc23e6abea32c469f6bbe445cc08ed80e [file] [log] [blame]
/*
*
* 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.
*/
/**
* @file
* This file defines the initiator side of a CHIP Write Interaction.
*
*/
#include "lib/core/CHIPError.h"
#include <app/AppBuildConfig.h>
#include <app/InteractionModelEngine.h>
#include <app/TimedRequest.h>
#include <app/WriteClient.h>
namespace chip {
namespace app {
CHIP_ERROR WriteClient::Init(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback,
const Optional<uint16_t> & aTimedWriteTimeoutMs)
{
VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mpExchangeMgr == nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE);
System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
VerifyOrReturnError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY);
mMessageWriter.Init(std::move(packet));
ReturnErrorOnFailure(mWriteRequestBuilder.Init(&mMessageWriter));
mWriteRequestBuilder.TimedRequest(aTimedWriteTimeoutMs.HasValue());
ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
mWriteRequestBuilder.CreateWriteRequests();
ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
ClearExistingExchangeContext();
mpExchangeMgr = apExchangeMgr;
mpCallback = apCallback;
mTimedWriteTimeoutMs = aTimedWriteTimeoutMs;
MoveToState(State::Initialized);
return CHIP_NO_ERROR;
}
void WriteClient::Shutdown()
{
VerifyOrReturn(mState != State::Uninitialized);
ClearExistingExchangeContext();
ShutdownInternal();
}
void WriteClient::ShutdownInternal()
{
mMessageWriter.Reset();
mpExchangeMgr = nullptr;
mpExchangeCtx = nullptr;
ClearState();
mpCallback->OnDone(this);
}
void WriteClient::ClearExistingExchangeContext()
{
// Discard any existing exchange context. Effectively we can only have one IM exchange with
// a single node at any one time.
if (mpExchangeCtx != nullptr)
{
mpExchangeCtx->Abort();
mpExchangeCtx = nullptr;
}
}
CHIP_ERROR WriteClient::ProcessWriteResponseMessage(System::PacketBufferHandle && payload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferTLVReader reader;
TLV::TLVReader attributeStatusesReader;
WriteResponseMessage::Parser writeResponse;
AttributeStatusIBs::Parser attributeStatusesParser;
reader.Init(std::move(payload));
err = reader.Next();
SuccessOrExit(err);
err = writeResponse.Init(reader);
SuccessOrExit(err);
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
err = writeResponse.CheckSchemaValidity();
SuccessOrExit(err);
#endif
err = writeResponse.GetWriteResponses(&attributeStatusesParser);
SuccessOrExit(err);
attributeStatusesParser.GetReader(&attributeStatusesReader);
while (CHIP_NO_ERROR == (err = attributeStatusesReader.Next()))
{
VerifyOrExit(TLV::AnonymousTag == attributeStatusesReader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG);
AttributeStatusIB::Parser element;
err = element.Init(attributeStatusesReader);
SuccessOrExit(err);
err = ProcessAttributeStatusIB(element);
SuccessOrExit(err);
}
// if we have exhausted this container
if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
exit:
return err;
}
CHIP_ERROR WriteClient::PrepareAttribute(const AttributePathParams & attributePathParams)
{
VerifyOrReturnError(attributePathParams.IsValidAttributePath(), CHIP_ERROR_INVALID_PATH_LIST);
AttributeDataIBs::Builder & writeRequests = mWriteRequestBuilder.GetWriteRequests();
AttributeDataIB::Builder & attributeDataIB = writeRequests.CreateAttributeDataIBBuilder();
ReturnErrorOnFailure(writeRequests.GetError());
// TODO: Add attribute version support
attributeDataIB.DataVersion(0);
ReturnErrorOnFailure(attributeDataIB.GetError());
AttributePathIB::Builder & path = attributeDataIB.CreatePath();
ReturnErrorOnFailure(path.Encode(attributePathParams));
return CHIP_NO_ERROR;
}
CHIP_ERROR WriteClient::FinishAttribute()
{
AttributeDataIB::Builder & attributeDataIB = mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder();
attributeDataIB.EndOfAttributeDataIB();
ReturnErrorOnFailure(attributeDataIB.GetError());
MoveToState(State::AddAttribute);
return CHIP_NO_ERROR;
}
TLV::TLVWriter * WriteClient::GetAttributeDataIBTLVWriter()
{
return mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder().GetWriter();
}
CHIP_ERROR WriteClient::FinalizeMessage(System::PacketBufferHandle & aPacket)
{
VerifyOrReturnError(mState == State::AddAttribute, CHIP_ERROR_INCORRECT_STATE);
AttributeDataIBs::Builder & attributeDataIBsBuilder = mWriteRequestBuilder.GetWriteRequests().EndOfAttributeDataIBs();
ReturnErrorOnFailure(attributeDataIBsBuilder.GetError());
mWriteRequestBuilder.IsFabricFiltered(false).EndOfWriteRequestMessage();
ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
ReturnErrorOnFailure(mMessageWriter.Finalize(&aPacket));
return CHIP_NO_ERROR;
}
const char * WriteClient::GetStateStr() const
{
#if CHIP_DETAIL_LOGGING
switch (mState)
{
case State::Uninitialized:
return "Uninitialized";
case State::Initialized:
return "Initialized";
case State::AddAttribute:
return "AddAttribute";
case State::AwaitingTimedStatus:
return "AwaitingTimedStatus";
case State::AwaitingResponse:
return "AwaitingResponse";
case State::ResponseReceived:
return "ResponseReceived";
}
#endif // CHIP_DETAIL_LOGGING
return "N/A";
}
void WriteClient::MoveToState(const State aTargetState)
{
mState = aTargetState;
ChipLogDetail(DataManagement, "WriteClient moving to [%10.10s]", GetStateStr());
}
void WriteClient::ClearState()
{
MoveToState(State::Uninitialized);
}
CHIP_ERROR WriteClient::SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout)
{
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(mState == State::AddAttribute, err = CHIP_ERROR_INCORRECT_STATE);
err = FinalizeMessage(mPendingWriteData);
SuccessOrExit(err);
// Discard any existing exchange context. Effectively we can only have one exchange per WriteClient
// at any one time.
ClearExistingExchangeContext();
// Create a new exchange context.
mpExchangeCtx = mpExchangeMgr->NewContext(session, this);
VerifyOrExit(mpExchangeCtx != nullptr, err = CHIP_ERROR_NO_MEMORY);
mpExchangeCtx->SetResponseTimeout(timeout);
if (mTimedWriteTimeoutMs.HasValue())
{
err = TimedRequest::Send(mpExchangeCtx, mTimedWriteTimeoutMs.Value());
SuccessOrExit(err);
MoveToState(State::AwaitingTimedStatus);
}
else
{
err = SendWriteRequest();
SuccessOrExit(err);
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(DataManagement, "Write client failed to SendWriteRequest");
ClearExistingExchangeContext();
}
else
{
// TODO: Ideally this would happen async, but to make sure that we
// handle this object dying (e.g. due to IM enging shutdown) while the
// async bits are pending we'd need to malloc some state bit that we can
// twiddle if we die. For now just do the OnDone callback sync.
if (session.IsGroupSession())
{
// Always shutdown on Group communication
ChipLogDetail(DataManagement, "Closing on group Communication ");
// onDone is called
ShutdownInternal();
}
}
return err;
}
CHIP_ERROR WriteClient::SendWriteRequest()
{
using namespace Protocols::InteractionModel;
using namespace Messaging;
// kExpectResponse is ignored by ExchangeContext in case of groupcast
ReturnErrorOnFailure(
mpExchangeCtx->SendMessage(MsgType::WriteRequest, std::move(mPendingWriteData), SendMessageFlags::kExpectResponse));
MoveToState(State::AwaitingResponse);
return CHIP_NO_ERROR;
}
CHIP_ERROR WriteClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload)
{
if (mState == State::AwaitingResponse)
{
MoveToState(State::ResponseReceived);
}
CHIP_ERROR err = CHIP_NO_ERROR;
StatusIB status(Protocols::InteractionModel::Status::Failure);
// Assert that the exchange context matches the client's current context.
// This should never fail because even if SendWriteRequest is called
// back-to-back, the second call will call Close() on the first exchange,
// which clears the OnMessageReceived callback.
VerifyOrExit(apExchangeContext == mpExchangeCtx, err = CHIP_ERROR_INCORRECT_STATE);
if (mState == State::AwaitingTimedStatus)
{
err = HandleTimedStatus(aPayloadHeader, std::move(aPayload), status);
// Skip all other processing here (which is for the response to the
// write request), no matter whether err is success or not.
goto exit;
}
if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteResponse))
{
err = ProcessWriteResponseMessage(std::move(aPayload));
SuccessOrExit(err);
}
else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse))
{
err = StatusResponse::ProcessStatusResponse(std::move(aPayload), status);
SuccessOrExit(err);
}
else
{
err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
}
exit:
if (mpCallback != nullptr)
{
if (err != CHIP_NO_ERROR)
{
mpCallback->OnError(this, status, err);
}
}
if (mState != State::AwaitingResponse)
{
ShutdownInternal();
}
// Else we got a response to a Timed Request and just sent the write.
return err;
}
void WriteClient::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
{
ChipLogProgress(DataManagement, "Time out! failed to receive write response from Exchange: " ChipLogFormatExchange,
ChipLogValueExchange(apExchangeContext));
if (mpCallback != nullptr)
{
mpCallback->OnError(this, StatusIB(Protocols::InteractionModel::Status::Failure), CHIP_ERROR_TIMEOUT);
}
ShutdownInternal();
}
CHIP_ERROR WriteClient::ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB)
{
CHIP_ERROR err = CHIP_NO_ERROR;
AttributePathIB::Parser attributePath;
StatusIB statusIB;
StatusIB::Parser StatusIBParser;
AttributePathParams attributePathParams;
err = aAttributeStatusIB.GetPath(&attributePath);
SuccessOrExit(err);
err = attributePath.GetCluster(&(attributePathParams.mClusterId));
SuccessOrExit(err);
err = attributePath.GetEndpoint(&(attributePathParams.mEndpointId));
SuccessOrExit(err);
err = attributePath.GetAttribute(&(attributePathParams.mAttributeId));
SuccessOrExit(err);
err = attributePath.GetListIndex(&(attributePathParams.mListIndex));
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
// TODO: (#11423) Attribute paths has a pattern of invalid paths, should add a function for checking invalid paths here.
// NOTE: We don't support wildcard write for now, reject all wildcard paths.
VerifyOrExit(!attributePathParams.HasAttributeWildcard() && attributePathParams.IsValidAttributePath(),
err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
err = aAttributeStatusIB.GetErrorStatus(&(StatusIBParser));
if (CHIP_NO_ERROR == err)
{
err = StatusIBParser.DecodeStatusIB(statusIB);
SuccessOrExit(err);
if (mpCallback != nullptr)
{
ConcreteAttributePath path(attributePathParams.mEndpointId, attributePathParams.mClusterId,
attributePathParams.mAttributeId);
mpCallback->OnResponse(this, path, statusIB);
}
}
exit:
return err;
}
CHIP_ERROR WriteClientHandle::SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout)
{
CHIP_ERROR err = mpWriteClient->SendWriteRequest(session, timeout);
// Transferring ownership of the underlying WriteClient to the IM layer. IM will manage its lifetime.
// For groupcast writes, there is no transfer of ownership since the interaction is done upon transmission of the action
if (err == CHIP_NO_ERROR)
{
// Release the WriteClient without closing it.
mpWriteClient = nullptr;
}
else
{
SetWriteClient(nullptr);
}
return err;
}
CHIP_ERROR WriteClient::HandleTimedStatus(const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
StatusIB & aStatusIB)
{
ReturnErrorOnFailure(TimedRequest::HandleResponse(aPayloadHeader, std::move(aPayload), aStatusIB));
return SendWriteRequest();
}
} // namespace app
} // namespace chip