blob: b4379efad33e25be6b1f13efe00d3518b93f5d9b [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 <app/AppBuildConfig.h>
#include <app/InteractionModelEngine.h>
#include <app/WriteClient.h>
namespace chip {
namespace app {
CHIP_ERROR WriteClient::Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate,
uint64_t aApplicationIdentifier)
{
VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mpExchangeMgr == nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE);
AttributeDataList::Builder attributeDataListBuilder;
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));
attributeDataListBuilder = mWriteRequestBuilder.CreateAttributeDataListBuilder();
ReturnErrorOnFailure(attributeDataListBuilder.GetError());
ClearExistingExchangeContext();
mpExchangeMgr = apExchangeMgr;
mpDelegate = apDelegate;
mAttributeStatusIndex = 0;
mAppIdentifier = aApplicationIdentifier;
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;
mpDelegate = nullptr;
mAttributeStatusIndex = 0;
ClearState();
}
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 attributeStatusListReader;
WriteResponse::Parser writeResponse;
AttributeStatusList::Parser attributeStatusListParser;
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.GetAttributeStatusList(&attributeStatusListParser);
SuccessOrExit(err);
attributeStatusListParser.GetReader(&attributeStatusListReader);
while (CHIP_NO_ERROR == (err = attributeStatusListReader.Next()))
{
VerifyOrExit(TLV::AnonymousTag == attributeStatusListReader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG);
VerifyOrExit(TLV::kTLVType_Structure == attributeStatusListReader.GetType(), err = CHIP_ERROR_WRONG_TLV_TYPE);
AttributeStatusElement::Parser element;
err = element.Init(attributeStatusListReader);
SuccessOrExit(err);
err = ProcessAttributeStatusElement(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)
{
CHIP_ERROR err = CHIP_NO_ERROR;
AttributeDataElement::Builder attributeDataElement =
mWriteRequestBuilder.GetAttributeDataListBuilder().CreateAttributeDataElementBuilder();
SuccessOrExit(attributeDataElement.GetError());
err = ConstructAttributePath(attributePathParams, attributeDataElement);
exit:
return err;
}
CHIP_ERROR WriteClient::FinishAttribute()
{
CHIP_ERROR err = CHIP_NO_ERROR;
AttributeDataElement::Builder attributeDataElement =
mWriteRequestBuilder.GetAttributeDataListBuilder().GetAttributeDataElementBuilder();
// TODO: Add attribute version support
attributeDataElement.DataVersion(0);
attributeDataElement.EndOfAttributeDataElement();
SuccessOrExit(err = attributeDataElement.GetError());
MoveToState(State::AddAttribute);
exit:
return err;
}
TLV::TLVWriter * WriteClient::GetAttributeDataElementTLVWriter()
{
return mWriteRequestBuilder.GetAttributeDataListBuilder().GetAttributeDataElementBuilder().GetWriter();
}
CHIP_ERROR WriteClient::ConstructAttributePath(const AttributePathParams & aAttributePathParams,
AttributeDataElement::Builder aAttributeDataElement)
{
AttributePath::Builder attributePath = aAttributeDataElement.CreateAttributePathBuilder();
if (aAttributePathParams.mFlags.Has(AttributePathParams::Flags::kFieldIdValid))
{
attributePath.FieldId(aAttributePathParams.mFieldId);
}
if (aAttributePathParams.mFlags.Has(AttributePathParams::Flags::kListIndexValid))
{
attributePath.ListIndex(aAttributePathParams.mListIndex);
}
attributePath.NodeId(aAttributePathParams.mNodeId)
.ClusterId(aAttributePathParams.mClusterId)
.EndpointId(aAttributePathParams.mEndpointId)
.EndOfAttributePath();
return attributePath.GetError();
}
CHIP_ERROR WriteClient::FinalizeMessage(System::PacketBufferHandle & aPacket)
{
CHIP_ERROR err = CHIP_NO_ERROR;
AttributeDataList::Builder attributeDataListBuilder;
VerifyOrExit(mState == State::AddAttribute, err = CHIP_ERROR_INCORRECT_STATE);
attributeDataListBuilder = mWriteRequestBuilder.GetAttributeDataListBuilder().EndOfAttributeDataList();
err = attributeDataListBuilder.GetError();
SuccessOrExit(err);
mWriteRequestBuilder.EndOfWriteRequest();
err = mWriteRequestBuilder.GetError();
SuccessOrExit(err);
err = mMessageWriter.Finalize(&aPacket);
SuccessOrExit(err);
exit:
return err;
}
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::AwaitingResponse:
return "AwaitingResponse";
}
#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(NodeId aNodeId, FabricIndex aFabricIndex, Optional<SessionHandle> apSecureSession,
uint32_t timeout)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferHandle packet;
VerifyOrExit(mState == State::AddAttribute, err = CHIP_ERROR_INCORRECT_STATE);
err = FinalizeMessage(packet);
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(apSecureSession.ValueOr(SessionHandle(aNodeId, 0, 0, aFabricIndex)), this);
VerifyOrExit(mpExchangeCtx != nullptr, err = CHIP_ERROR_NO_MEMORY);
mpExchangeCtx->SetResponseTimeout(timeout);
err = mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::WriteRequest, std::move(packet),
Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse));
SuccessOrExit(err);
MoveToState(State::AwaitingResponse);
exit:
if (err != CHIP_NO_ERROR)
{
ClearExistingExchangeContext();
}
return err;
}
CHIP_ERROR WriteClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
// 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.
VerifyOrDie(apExchangeContext == mpExchangeCtx);
// Verify that the message is an Write Response.
// If not, close the exchange and free the payload.
if (!aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteResponse))
{
ExitNow();
}
err = ProcessWriteResponseMessage(std::move(aPayload));
exit:
if (mpDelegate != nullptr)
{
if (err != CHIP_NO_ERROR)
{
mpDelegate->WriteResponseError(this, err);
}
else
{
mpDelegate->WriteResponseProcessed(this);
}
}
ShutdownInternal();
return err;
}
void WriteClient::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
{
ChipLogProgress(DataManagement, "Time out! failed to receive write response from Exchange: " ChipLogFormatExchange,
ChipLogValueExchange(apExchangeContext));
if (mpDelegate != nullptr)
{
mpDelegate->WriteResponseError(this, CHIP_ERROR_TIMEOUT);
}
ShutdownInternal();
}
CHIP_ERROR WriteClient::ProcessAttributeStatusElement(AttributeStatusElement::Parser & aAttributeStatusElement)
{
CHIP_ERROR err = CHIP_NO_ERROR;
AttributePath::Parser attributePath;
Protocols::SecureChannel::GeneralStatusCode generalCode = Protocols::SecureChannel::GeneralStatusCode::kSuccess;
uint32_t protocolId = 0;
uint16_t protocolCode = 0;
StatusElement::Parser statusElementParser;
AttributePathParams attributePathParams;
mAttributeStatusIndex++;
err = aAttributeStatusElement.GetAttributePath(&attributePath);
SuccessOrExit(err);
err = attributePath.GetNodeId(&(attributePathParams.mNodeId));
SuccessOrExit(err);
err = attributePath.GetClusterId(&(attributePathParams.mClusterId));
SuccessOrExit(err);
err = attributePath.GetEndpointId(&(attributePathParams.mEndpointId));
SuccessOrExit(err);
err = attributePath.GetFieldId(&(attributePathParams.mFieldId));
if (CHIP_NO_ERROR == err)
{
attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid);
}
else if (CHIP_END_OF_TLV == err)
{
err = CHIP_NO_ERROR;
}
SuccessOrExit(err);
err = attributePath.GetListIndex(&(attributePathParams.mListIndex));
if (CHIP_NO_ERROR == err)
{
VerifyOrExit(attributePathParams.mFlags.Has(AttributePathParams::Flags::kFieldIdValid),
err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
attributePathParams.mFlags.Set(AttributePathParams::Flags::kListIndexValid);
}
err = aAttributeStatusElement.GetStatusElement(&(statusElementParser));
if (CHIP_NO_ERROR == err)
{
err = statusElementParser.DecodeStatusElement(&generalCode, &protocolId, &protocolCode);
SuccessOrExit(err);
if (mpDelegate != nullptr)
{
mpDelegate->WriteResponseStatus(this, generalCode, protocolId, protocolCode, attributePathParams,
mAttributeStatusIndex);
}
}
exit:
if (err != CHIP_NO_ERROR && mpDelegate != nullptr)
{
mpDelegate->WriteResponseProtocolError(this, mAttributeStatusIndex);
}
return err;
}
CHIP_ERROR WriteClientHandle::SendWriteRequest(NodeId aNodeId, FabricIndex aFabricIndex, Optional<SessionHandle> apSecureSession,
uint32_t timeout)
{
CHIP_ERROR err = mpWriteClient->SendWriteRequest(aNodeId, aFabricIndex, apSecureSession, timeout);
if (err == CHIP_NO_ERROR)
{
// On success, the InteractionModelEngine will be responible to take care of the lifecycle of the WriteClient, so we release
// the WriteClient without closing it.
mpWriteClient = nullptr;
}
else
{
SetWriteClient(nullptr);
}
return err;
}
} // namespace app
} // namespace chip