blob: 3752a3e65aef08c72d97c8e46b876e2a6f0d6087 [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.
*/
#include "lib/core/TLV.h"
#include "lib/core/TLVTags.h"
#include "lib/core/TLVTypes.h"
#include "protocols/interaction_model/Constants.h"
#include "system/SystemPacketBuffer.h"
#include "system/TLVPacketBufferBackingStore.h"
#include <app/BufferedReadCallback.h>
#include <app/InteractionModelEngine.h>
#include <lib/support/ScopedBuffer.h>
namespace chip {
namespace app {
void BufferedReadCallback::OnReportBegin()
{
mCallback.OnReportBegin();
}
void BufferedReadCallback::OnReportEnd()
{
CHIP_ERROR err = DispatchBufferedData(mBufferedPath, StatusIB(), true);
if (err != CHIP_NO_ERROR)
{
mCallback.OnError(err);
return;
}
mCallback.OnReportEnd();
}
CHIP_ERROR BufferedReadCallback::GenerateListTLV(TLV::ScopedBufferTLVReader & aReader)
{
TLV::TLVType outerType;
Platform::ScopedMemoryBuffer<uint8_t> backingBuffer;
//
// To generate the final reconstituted list, we need to allocate a contiguous
// buffer than can hold the entirety of its contents. To do so, we need to figure out
// how big a buffer to allocate. This requires walking the buffered list items and computing their TLV sizes,
// summing them all up and adding a bit of slop to account for the TLV array the list elements will go into.
//
// The alternative was to use a PacketBufferTLVWriter backed by chained packet buffers to
// write out the list - this would have removed the need for this first pass. However,
// we cannot actually back a TLVReader with a chained buffer since that violates the ability
// for us to create readers off-of readers. Each reader would assume exclusive ownership of the chained
// buffer and mutate the state within TLVPacketBufferBackingStore, preventing shared use.
//
// To avoid that, a single contiguous buffer is the best likely approach for now.
//
size_t totalBufSize = 0;
for (const auto & packetBuffer : mBufferedList)
{
totalBufSize += packetBuffer->TotalLength();
}
//
// Size of the start container and end container are just 1 byte each, but, let's just be safe.
//
totalBufSize += 4;
backingBuffer.Calloc(totalBufSize);
VerifyOrReturnError(backingBuffer.Get() != nullptr, CHIP_ERROR_NO_MEMORY);
TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), totalBufSize);
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerType));
for (auto & bufHandle : mBufferedList)
{
System::PacketBufferTLVReader reader;
reader.Init(std::move(bufHandle));
ReturnErrorOnFailure(reader.Next());
ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), reader));
}
ReturnErrorOnFailure(writer.EndContainer(outerType));
writer.Finalize(backingBuffer);
aReader.Init(std::move(backingBuffer), totalBufSize);
return CHIP_NO_ERROR;
}
CHIP_ERROR BufferedReadCallback::BufferListItem(TLV::TLVReader & reader)
{
System::PacketBufferTLVWriter writer;
System::PacketBufferHandle handle;
//
// We conservatively allocate a packet buffer as big as an IPv6 MTU (since we're buffering
// data received over the wire, which should always fit within that).
//
// We could have snapshotted the reader at its current position, advanced it past the current element
// and computed the delta in its read point to figure out the size of the element before allocating
// our target buffer. However, the reader's current position is already set past the control octet
// and the tag. Consequently, the computed size is always going to omit the sizes of these two parts of the
// TLV element. Since the tag can vary in size, for now, let's just do the safe thing. In the future, if this is a problem,
// we can improve this.
//
handle = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
VerifyOrReturnError(!handle.IsNull(), CHIP_ERROR_NO_MEMORY);
writer.Init(std::move(handle), false);
ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), reader));
ReturnErrorOnFailure(writer.Finalize(&handle));
// Compact the buffer down to a more reasonably sized packet buffer
// if we can.
//
handle.RightSize();
mBufferedList.push_back(std::move(handle));
return CHIP_NO_ERROR;
}
CHIP_ERROR BufferedReadCallback::BufferData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData)
{
if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll)
{
TLV::TLVType outerContainer;
VerifyOrReturnError(apData->GetType() == TLV::kTLVType_Array, CHIP_ERROR_INVALID_TLV_ELEMENT);
mBufferedList.clear();
ReturnErrorOnFailure(apData->EnterContainer(outerContainer));
CHIP_ERROR err;
while ((err = apData->Next()) == CHIP_NO_ERROR)
{
ReturnErrorOnFailure(BufferListItem(*apData));
}
if (err == CHIP_END_OF_TLV)
{
err = CHIP_NO_ERROR;
}
ReturnErrorOnFailure(err);
ReturnErrorOnFailure(apData->ExitContainer(outerContainer));
}
else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
{
ReturnErrorOnFailure(BufferListItem(*apData));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR BufferedReadCallback::DispatchBufferedData(const ConcreteAttributePath & aPath, const StatusIB & aStatusIB,
bool aEndOfReport)
{
if (aPath == mBufferedPath)
{
//
// If we encountered the same list again and it's not the last DataIB, then
// we need to continue to buffer up this list's data, so return immediately without dispatching
// the existing buffered up contents.
//
if (!aEndOfReport)
{
return CHIP_NO_ERROR;
}
//
// If we had previously buffered up data for this list and now we have encountered
// an error for this list, that error takes precedence and the buffered data is now
// rendered invalid. Return immediately without dispatching the existing buffered up contents.
//
if (aStatusIB.mStatus != Protocols::InteractionModel::Status::Success)
{
return CHIP_NO_ERROR;
}
}
if (!mBufferedPath.IsListOperation())
{
return CHIP_NO_ERROR;
}
StatusIB statusIB;
TLV::ScopedBufferTLVReader reader;
ReturnErrorOnFailure(GenerateListTLV(reader));
//
// Update the list operation to now reflect the delivery of the entire list
// i.e a replace all operation.
//
mBufferedPath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
//
// Advance the reader forward to the list itself
//
ReturnErrorOnFailure(reader.Next());
mCallback.OnAttributeData(mBufferedPath, &reader, statusIB);
//
// Clear out our buffered contents to free up allocated buffers, and reset the buffered path.
//
mBufferedList.clear();
mBufferedPath = ConcreteDataAttributePath();
return CHIP_NO_ERROR;
}
void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
const StatusIB & aStatus)
{
CHIP_ERROR err;
//
// First, let's dispatch to our registered callback any buffered up list data from previous calls.
//
err = DispatchBufferedData(aPath, aStatus);
SuccessOrExit(err);
//
// We buffer up list data (only if the status was successful)
//
if (aPath.IsListOperation() && aStatus.mStatus == Protocols::InteractionModel::Status::Success)
{
err = BufferData(aPath, apData);
SuccessOrExit(err);
}
else
{
mCallback.OnAttributeData(aPath, apData, aStatus);
}
//
// Update our latched buffered path.
//
mBufferedPath = aPath;
exit:
if (err != CHIP_NO_ERROR)
{
mCallback.OnError(err);
}
}
} // namespace app
} // namespace chip