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