/* * * 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 #include #include 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 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