blob: 6d0572fd31d0401476169610a4b4fff8baf770e8 [file] [log] [blame]
/*
*
* Copyright (c) 2015-2017 Nest Labs, Inc.
* 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 implements an updating encoder for the Weave TLV
* (Tag-Length-Value) encoding format.
*
*/
#include <Weave/Core/WeaveCore.h>
#include <Weave/Core/WeaveEncoding.h>
#include <Weave/Core/WeaveTLV.h>
#include <Weave/Support/CodeUtils.h>
namespace nl {
namespace Weave {
namespace TLV {
using namespace nl::Weave::Encoding;
/**
* Initialize a TLVUpdater object to edit a single input buffer.
*
* On calling this method, the TLV data in the buffer is moved to the end of the
* buffer and a private TLVReader object is initialized on this relocated
* buffer. A private TLVWriter object is also initialized on the free space that
* is now available at the beginning. Applications can use the TLVUpdater object
* to parse the TLV data and modify/delete existing elements or add new elements
* to the encoding.
*
* @param[in] data A pointer to a buffer containing the TLV data to be edited.
* @param[in] dataLen The length of the TLV data in the buffer.
* @param[in] maxLen The total length of the buffer.
*
* @retval #WEAVE_NO_ERROR If the method succeeded.
* @retval #WEAVE_ERROR_INVALID_ARGUMENT If the buffer address is invalid.
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL If the buffer is too small.
*
*/
WEAVE_ERROR TLVUpdater::Init(uint8_t *buf, uint32_t dataLen, uint32_t maxLen)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint32_t freeLen;
VerifyOrExit(buf != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(maxLen >= dataLen, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
// memmove the buffer data to end of the buffer
freeLen = maxLen - dataLen;
memmove(buf + freeLen, buf, dataLen);
// Init reader
mUpdaterReader.Init(buf + freeLen, dataLen);
// Init writer
mUpdaterWriter.Init(buf, freeLen);
mUpdaterWriter.SetCloseContainerReserved(false);
mElementStartAddr = buf + freeLen;
exit:
return err;
}
/**
* Initialize a TLVUpdater object using a TLVReader.
*
* On calling this method, TLV data in the buffer pointed to by the TLVReader
* is moved from the current read point to the end of the buffer. A new
* private TLVReader object is initialized to read from this new location, while
* a new private TLVWriter object is initialized to write to the freed up buffer
* space.
*
* Note that if the TLVReader is already positioned "on" an element, it is first
* backed-off to the start of that element. Also note that this backing off
* works well with container elements, i.e., if the TLVReader was already used
* to call EnterContainer(), then there is nothing to back-off. But if the
* TLVReader was positioned on the container element and EnterContainer() was
* not yet called, then the TLVReader object is backed-off to the start of the
* container head.
*
* The input TLVReader object will be destroyed before returning and the
* application must not make use of the same on return.
*
* @param[in,out] aReader Reference to a TLVReader object that will be
* destroyed before returning.
* @param[in] freeLen The length of free space (in bytes) available
* in the pre-encoded data buffer.
*
* @retval #WEAVE_NO_ERROR If the method succeeded.
* @retval #WEAVE_ERROR_INVALID_ARGUMENT If the buffer address is invalid.
* @retval #WEAVE_ERROR_NOT_IMPLEMENTED If reader was initialized on a chain
* of buffers.
*/
WEAVE_ERROR TLVUpdater::Init(TLVReader& aReader, uint32_t freeLen)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint8_t *buf = const_cast<uint8_t *>(aReader.GetReadPoint());
uint32_t remainingDataLen = aReader.GetRemainingLength();
uint32_t readDataLen = aReader.GetLengthRead();
// TLVUpdater does not support chain of buffers yet
VerifyOrExit(aReader.mBufHandle == 0, err = WEAVE_ERROR_NOT_IMPLEMENTED);
// TLVReader should point to a non-NULL buffer
VerifyOrExit(buf != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
// If reader is already on an element, reset it to start of element
if (aReader.ElementType() != kTLVElementType_NotSpecified)
{
uint8_t elemHeadLen;
err = aReader.GetElementHeadLength(elemHeadLen);
SuccessOrExit(err);
buf -= elemHeadLen;
remainingDataLen += elemHeadLen;
readDataLen -= elemHeadLen;
}
// memmove the buffer data to end of the buffer
memmove(buf + freeLen, buf, remainingDataLen);
// Initialize the internal reader object
mUpdaterReader.mBufHandle = 0;
mUpdaterReader.mReadPoint = buf + freeLen;
mUpdaterReader.mBufEnd = buf + freeLen + remainingDataLen;
mUpdaterReader.mLenRead = readDataLen;
mUpdaterReader.mMaxLen = aReader.mMaxLen;
mUpdaterReader.mControlByte = kTLVControlByte_NotSpecified;
mUpdaterReader.mElemTag = AnonymousTag;
mUpdaterReader.mElemLenOrVal = 0;
mUpdaterReader.mContainerType = aReader.mContainerType;
mUpdaterReader.SetContainerOpen(false);
mUpdaterReader.ImplicitProfileId = aReader.ImplicitProfileId;
mUpdaterReader.AppData = aReader.AppData;
mUpdaterReader.GetNextBuffer = NULL;
// Initialize the internal writer object
mUpdaterWriter.mBufHandle = 0;
mUpdaterWriter.mBufStart = buf - readDataLen;
mUpdaterWriter.mWritePoint = buf;
mUpdaterWriter.mRemainingLen = freeLen;
mUpdaterWriter.mLenWritten = readDataLen;
mUpdaterWriter.mMaxLen = readDataLen + freeLen;
mUpdaterWriter.mContainerType = aReader.mContainerType;
mUpdaterWriter.SetContainerOpen(false);
mUpdaterWriter.SetCloseContainerReserved(false);
mUpdaterWriter.ImplicitProfileId = aReader.ImplicitProfileId;
mUpdaterWriter.GetNewBuffer = NULL;
mUpdaterWriter.FinalizeBuffer = NULL;
// Cache element start address for internal use
mElementStartAddr = buf + freeLen;
// Clear the input reader object before returning. The user can no longer
// use the original TLVReader object anymore.
aReader.Init((const uint8_t *) NULL, 0);
exit:
return err;
}
/**
* Set the Implicit Profile ID for the TLVUpdater object.
*
* This method sets the implicit profile ID for the TLVUpdater object. When the
* updater is asked to encode a new element, if the profile ID of the tag
* associated with the new element matches the value of the @p profileId, the
* updater will encode the tag in implicit form, thereby omitting the profile ID
* in the process.
*
* @param[in] profileId The profile id of tags that should be encoded in
* implicit form.
*/
void TLVUpdater::SetImplicitProfileId(uint32_t profileId)
{
mUpdaterReader.ImplicitProfileId = profileId;
mUpdaterWriter.ImplicitProfileId = profileId;
}
/**
* Skip the current element and advance the TLVUpdater object to the next
* element in the input TLV.
*
* The Next() method skips the current element in the input TLV and advances the
* TLVUpdater's reader to the next element that resides in the same containment
* context. In particular, if the reader is positioned at the outer most level
* of a TLV encoding, calling Next() will advance it to the next, top most
* element. If the reader is positioned within a TLV container element (a
* structure, array or path), calling Next() will advance it to the next member
* element of the container.
*
* Since Next() constrains reader motion to the current containment context,
* calling Next() when the reader is positioned on a container element will
* advance @em over the container, skipping its member elements (and the members
* of any nested containers) until it reaches the first element after the
* container.
*
* When there are no further elements within a particular containment context
* the Next() method will return a #WEAVE_END_OF_TLV error and the position of
* the reader will remain unchanged.
*
* @note The Next() method implicitly skips the current element. Hence, the
* TLVUpdater's private writer state variables will be adjusted to account for
* the new freed space (made available by skipping). This means that the
* application is expected to call Next() on the TLVUpdater object after a Get()
* whose value the application does @em not write back (which from the
* TLVUpdater's view is equivalent to skipping that element).
*
* @note Applications are also expected to call Next() when they are at the end
* of a container, and want to add new elements there. This is particularly
* important in situations where there is a fixed schema. Applications that have
* fixed schemas and know where the container end is cannot just add new
* elements at the end, because the TLVUpdater writer's state will not reflect
* the correct free space available for the Put() operation. Hence, applications
* must call Next() (and possibly also test for WEAVE_END_OF_TLV) before adding
* elements at the end of a container.
*
* @retval #WEAVE_NO_ERROR If the TLVUpdater reader was
* successfully positioned on a new
* element.
* @retval other Returns the Weave or platform error
* codes returned by the TLVReader::Skip()
* and TLVReader::Next() method.
*
*/
WEAVE_ERROR TLVUpdater::Next()
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// Skip current element if the reader is already positioned on an element
err = mUpdaterReader.Skip();
SuccessOrExit(err);
AdjustInternalWriterFreeSpace();
// Move the reader to next element
err = mUpdaterReader.Next();
SuccessOrExit(err);
exit:
return err;
}
/**
* Copies the current element from input TLV to output TLV.
*
* The Move() method copies the current element on which the TLVUpdater's reader
* is positioned on, to the TLVUpdater's writer. The application should call
* Next() and position the TLVUpdater's reader on an element before calling this
* method. Just like the TLVReader::Next() method, if the reader is positioned
* on a container element at the time of the call, all the members of the
* container will be copied. If the reader is not positioned on any element,
* nothing changes on calling this method.
*
* @retval #WEAVE_NO_ERROR If the TLVUpdater reader was
* successfully positioned on a new
* element.
* @retval #WEAVE_END_OF_TLV If the TLVUpdater's reader is pointing
* to end of container.
* @retval #WEAVE_ERROR_INVALID_TLV_ELEMENT
* If the TLVIpdater's reader is not
* positioned on a valid TLV element.
* @retval other Returns other error codes returned by
* TLVReader::Skip() method.
*
*/
WEAVE_ERROR TLVUpdater::Move()
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
const uint8_t *elementEnd;
uint32_t copyLen;
VerifyOrExit((mUpdaterReader.mControlByte & kTLVTypeMask) != kTLVElementType_EndOfContainer,
err = WEAVE_END_OF_TLV);
VerifyOrExit(mUpdaterReader.GetType() != kTLVType_NotSpecified,
err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
// Skip to the end of the element
err = mUpdaterReader.Skip();
SuccessOrExit(err);
elementEnd = mUpdaterReader.mReadPoint;
copyLen = elementEnd - mElementStartAddr;
// Move the element to output TLV
memmove(mUpdaterWriter.mWritePoint, mElementStartAddr, copyLen);
// Adjust the updater state
mElementStartAddr += copyLen;
mUpdaterWriter.mWritePoint += copyLen;
mUpdaterWriter.mLenWritten += copyLen;
mUpdaterWriter.mMaxLen += copyLen;
exit:
return err;
}
/**
* Move everything from the TLVUpdater's current read point till end of input
* TLV buffer over to output.
*
* This method supports moving everything from the TLVUpdater's current read
* point till the end of the reader buffer over to the TLVUpdater's writer.
*
* @note This method can be called with the TLVUpdater's reader positioned
* anywhere within the input TLV. The reader can also be positioned under
* multiple levels of nested containers and this method will still work.
*
* @note This method also changes the state of the TLVUpdater object to a state
* it would be in if the application had painstakingly parsed each element from
* the current read point till the end of the input encoding and copied them to
* the output TLV.
*/
void TLVUpdater::MoveUntilEnd()
{
const uint8_t *buffEnd = mUpdaterReader.GetReadPoint() +
mUpdaterReader.GetRemainingLength();
uint32_t copyLen = buffEnd - mElementStartAddr;
// Move all elements till end to output TLV
memmove(mUpdaterWriter.mWritePoint, mElementStartAddr, copyLen);
// Adjust the updater state
mElementStartAddr += copyLen;
mUpdaterWriter.mWritePoint += copyLen;
mUpdaterWriter.mLenWritten += copyLen;
mUpdaterWriter.mMaxLen += copyLen;
mUpdaterWriter.mContainerType = kTLVType_NotSpecified;
mUpdaterWriter.SetContainerOpen(false);
mUpdaterWriter.SetCloseContainerReserved(false);
mUpdaterReader.mReadPoint += copyLen;
mUpdaterReader.mLenRead += copyLen;
mUpdaterReader.mControlByte = kTLVControlByte_NotSpecified;
mUpdaterReader.mElemTag = AnonymousTag;
mUpdaterReader.mElemLenOrVal = 0;
mUpdaterReader.mContainerType = kTLVType_NotSpecified;
mUpdaterReader.SetContainerOpen(false);
}
/**
* Prepares a TLVUpdater object for reading elements of a container. It also
* encodes a start of container object in the output TLV.
*
* The EnterContainer() method prepares the current TLVUpdater object to begin
* reading the member elements of a TLV container (a structure, array or path).
* For every call to EnterContainer() applications must make a corresponding
* call to ExitContainer().
*
* When EnterContainer() is called the TLVUpdater's reader must be positioned on
* the container element. The method takes as an argument a reference to a
* TLVType value which will be used to save the context of the updater while it
* is reading the container.
*
* When the EnterContainer() method returns, the updater is positioned
* immediately @em before the first member of the container. Repeatedly calling
* Next() will advance the updater through the members of the collection until
* the end is reached, at which point the updater will return WEAVE_END_OF_TLV.
*
* Once the application has finished reading a container it can continue reading
* the elements after the container by calling the ExitContainer() method.
*
* @note This method implicitly encodes a start of container element in the
* output TLV buffer.
*
* @param[out] outerContainerType A reference to a TLVType value that will
* receive the context of the updater.
*
* @retval #WEAVE_NO_ERROR If the method succeeded.
* @retval #WEAVE_ERROR_INCORRECT_STATE If the TLVUpdater reader is not
* positioned on a container element.
* @retval other Any other Weave or platform error code
* returned by TLVWriter::StartContainer()
* or TLVReader::EnterContainer().
*
*/
WEAVE_ERROR TLVUpdater::EnterContainer(TLVType& outerContainerType)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
TLVType containerType;
VerifyOrExit(TLVTypeIsContainer(mUpdaterReader.mControlByte & kTLVTypeMask),
err = WEAVE_ERROR_INCORRECT_STATE);
// Change the updater state
AdjustInternalWriterFreeSpace();
err = mUpdaterWriter.StartContainer(mUpdaterReader.GetTag(),
mUpdaterReader.GetType(),
containerType);
SuccessOrExit(err);
err = mUpdaterReader.EnterContainer(containerType);
SuccessOrExit(err);
outerContainerType = containerType;
exit:
return err;
}
/**
* Completes the reading of a TLV container element and encodes an end of TLV
* element in the output TLV.
*
* The ExitContainer() method restores the state of a TLVUpdater object after a
* call to EnterContainer(). For every call to EnterContainer() applications
* must make a corresponding call to ExitContainer(), passing the context value
* returned by the EnterContainer() method.
*
* When ExitContainer() returns, the TLVUpdater reader is positioned immediately
* before the first element that follows the container in the input TLV. From
* this point applications can call Next() to advance through any remaining
* elements.
*
* Once EnterContainer() has been called, applications can call ExitContainer()
* on the updater at any point in time, regardless of whether all elements in
* the underlying container have been read. Also, note that calling
* ExitContainer() before reading all the elements in the container, will result
* in the updated container getting truncated in the output TLV.
*
* @note Any changes made to the configuration of the updater between the calls
* to EnterContainer() and ExitContainer() are NOT undone by the call to
* ExitContainer(). For example, a change to the implicit profile id
* (@p ImplicitProfileId) will not be reversed when a container is exited. Thus
* it is the application's responsibility to adjust the configuration
* accordingly at the appropriate times.
*
* @param[in] outerContainerType The TLVType value that was returned by
* the EnterContainer() method.
*
* @retval #WEAVE_NO_ERROR If the method succeeded.
* @retval #WEAVE_ERROR_TLV_UNDERRUN If the underlying TLV encoding ended
* prematurely.
* @retval #WEAVE_ERROR_INVALID_TLV_ELEMENT
* If the updater encountered an invalid or
* unsupported TLV element type.
* @retval #WEAVE_ERROR_INVALID_TLV_TAG If the updater encountered a TLV tag in
* an invalid context.
* @retval other Any other Weave or platform error code
* returned by TLVWriter::EndContainer() or
* TLVReader::ExitContainer().
*
*/
WEAVE_ERROR TLVUpdater::ExitContainer(TLVType outerContainerType)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
err = mUpdaterReader.ExitContainer(outerContainerType);
SuccessOrExit(err);
// Change the updater's state
AdjustInternalWriterFreeSpace();
err = mUpdaterWriter.EndContainer(outerContainerType);
SuccessOrExit(err);
exit:
return err;
}
/**
* This is a private method that adjusts the TLVUpdater's free space count by
* accounting for the freespace from mElementStartAddr to current read point.
*/
void TLVUpdater::AdjustInternalWriterFreeSpace()
{
const uint8_t *nextElementStart = mUpdaterReader.mReadPoint;
if (nextElementStart != mElementStartAddr)
{
// Increase the internal writer's free space state variables
mUpdaterWriter.mRemainingLen += nextElementStart - mElementStartAddr;
mUpdaterWriter.mMaxLen += nextElementStart - mElementStartAddr;
// Cache the start address of the next element
mElementStartAddr = nextElementStart;
}
}
} // namespace TLV
} // namespace Weave
} // namespace nl