blob: 725ca8e23ea915eaad79fe3638becc1d7a8339d2 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* 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
* Utility classes for safely reading from size-limited buffers.
*/
#pragma once
#include <climits>
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/Span.h>
#include <stdint.h>
namespace chip {
namespace Encoding {
class BufferReader
{
public:
BufferReader(const uint8_t * buffer, size_t buf_len) : mBufStart(buffer), mReadPtr(buffer), mAvailable(buf_len)
{
if (mBufStart == nullptr)
{
mAvailable = 0;
}
}
/**
* Number of octets we have read so far.
*/
size_t OctetsRead() const { return static_cast<size_t>(mReadPtr - mBufStart); }
/**
* Number of octets we have remaining to read.
*/
size_t Remaining() const { return mAvailable; }
/**
* Test whether we have at least the given number of octets left to read.
*/
bool HasAtLeast(size_t octets) const { return octets <= Remaining(); }
/**
* The reader status. Once the status becomes a failure status, all later
* read operations become no-ops and the status continues to be a failure
* status.
*/
CHECK_RETURN_VALUE
CHIP_ERROR StatusCode() const { return mStatus; }
/**
* @return false if the reader is in error, true if the reader is OK.
*/
bool IsSuccess() const { return StatusCode() == CHIP_NO_ERROR; }
/**
* Read a byte string from the BufferReader
*
* @param [out] dest Where the bytes read
* @param [in] size How many bytes to read
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
BufferReader & ReadBytes(uint8_t * dest, size_t size);
/**
* Access bytes of size length, useful for in-place processing of strings
*
* data_ptr MUST NOT be null and will contain the data pointer with `len` bytes available
* if this call is successful
*
* If len is greater than the number of available bytes, the object enters in a failed status.
*/
CHECK_RETURN_VALUE
BufferReader & ZeroCopyProcessBytes(size_t len, const uint8_t ** data_ptr)
{
if (len > mAvailable)
{
*data_ptr = nullptr;
mStatus = CHIP_ERROR_BUFFER_TOO_SMALL;
// Ensure that future reads all fail.
mAvailable = 0;
}
else
{
*data_ptr = mReadPtr;
mReadPtr += len;
mAvailable -= len;
}
return *this;
}
/**
* Advance the Reader forward by the specified number of octets.
*
* @param len The number of octets to skip.
*
* @note If the len argument is greater than the number of available octets
* remaining, the Reader will advance to the end of the buffer
* without entering a failed-status state.
*/
BufferReader & Skip(size_t len)
{
len = std::min(len, mAvailable);
mReadPtr += len;
mAvailable = static_cast<size_t>(mAvailable - len);
return *this;
}
protected:
/// Our buffer start.
const uint8_t * const mBufStart;
/// Our current read point.
const uint8_t * mReadPtr;
/// The number of octets we can still read starting at mReadPtr.
size_t mAvailable;
/// Our current status.
CHIP_ERROR mStatus = CHIP_NO_ERROR;
/// Make sure we have at least the given number of bytes available (does not consume them)
bool EnsureAvailable(size_t size)
{
if (mAvailable < size)
{
mStatus = CHIP_ERROR_BUFFER_TOO_SMALL;
// Ensure that future reads all fail.
mAvailable = 0;
return false;
}
return true;
}
};
namespace LittleEndian {
/**
* @class Reader
*
* Simple reader for reading little-endian things out of buffers.
*/
class Reader : public BufferReader
{
public:
/**
* Create a buffer reader from a given buffer and length.
*
* @param buffer The octet buffer from which to read. The caller must ensure
* (most simply by allocating the reader on the stack) that
* the buffer outlives the reader. If `buffer` is nullptr,
* length is automatically overridden to zero, to avoid accesses.
* @param buf_len The number of octets in the buffer.
*/
Reader(const uint8_t * buffer, size_t buf_len) : BufferReader(buffer, buf_len) {}
/**
* Create a buffer reader from a given byte span.
*
* @param buffer The octet buffer byte span from which to read. The caller must ensure
* that the buffer outlives the reader. The buffer's ByteSpan .data() pointer
* is is nullptr, length is automatically overridden to zero, to avoid accesses.
*/
Reader(const ByteSpan & buffer) : Reader(buffer.data(), buffer.size()) {}
/**
* Read a bool, assuming single byte storage.
*
* @param [out] dest Where the 8-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & ReadBool(bool * dest)
{
static_assert(sizeof(bool) == 1, "Expect single-byte bools");
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a char, assuming single byte storage.
*
* @param [out] dest Where the char just read should be placed.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & ReadChar(char * dest)
{
static_assert(sizeof(char) == 1, "Expect single-byte chars");
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 8-bit unsigned integer.
*
* @param [out] dest Where the 8-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & Read8(uint8_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 16-bit unsigned integer.
*
* @param [out] dest Where the 16-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & Read16(uint16_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 32-bit unsigned integer.
*
* @param [out] dest Where the 32-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & Read32(uint32_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 64-bit unsigned integer.
*
* @param [out] dest Where the 64-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & Read64(uint64_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 8-bit signed integer.
*
* @param [out] dest Where the 8-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & ReadSigned8(int8_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 16-bit signed integer.
*
* @param [out] dest Where the 16-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & ReadSigned16(int16_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 32-bit signed integer.
*
* @param [out] dest Where the 32-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & ReadSigned32(int32_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Read a single 64-bit signed integer.
*
* @param [out] dest Where the 64-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & ReadSigned64(int64_t * dest)
{
RawReadLowLevelBeCareful(dest);
return *this;
}
/**
* Helper for our various APIs so we don't have to write out various logic
* multiple times. This is public so that consumers that want to read into
* whatever size a logical thing they are reading into has don't have to
* hardcode the right API. This is meant for other reader classes that
* delegate to this one.
*/
template <typename T>
void RawReadLowLevelBeCareful(T * retval);
};
} // namespace LittleEndian
namespace BigEndian {
/**
* @class Reader
*
* Simple reader for reading big-endian things out of buffers.
*/
class Reader : public BufferReader
{
public:
/**
* Create a buffer reader from a given buffer and length.
*
* @param buffer The octet buffer from which to read. The caller must ensure
* (most simply by allocating the reader on the stack) that
* the buffer outlives the reader. If `buffer` is nullptr,
* length is automatically overridden to zero, to avoid accesses.
* @param buf_len The number of octets in the buffer.
*/
Reader(const uint8_t * buffer, size_t buf_len) : BufferReader(buffer, buf_len) {}
/**
* Create a buffer reader from a given byte span.
*
* @param buffer The octet buffer byte span from which to read. The caller must ensure
* that the buffer outlives the reader. If the buffer's ByteSpan .data() pointer
* is nullptr, length is automatically overridden to zero, to avoid accesses.
*/
Reader(const ByteSpan & buffer) : Reader(buffer.data(), buffer.size()) {}
/**
* Read a single 8-bit unsigned integer.
*
* @param [out] dest Where the 8-bit integer goes.
*
* @note The read can put the reader in a failed-status state if there are
* not enough octets available. Callers must either continue to do
* more reads on the return value or check its status to see whether
* the sequence of reads that has been performed succeeded.
*/
CHECK_RETURN_VALUE
Reader & Read8(uint8_t * dest)
{
(void) ReadBytes(dest, 1);
return *this;
}
CHECK_RETURN_VALUE
Reader & ReadChar(char * dest)
{
(void) ReadBytes(reinterpret_cast<uint8_t *>(dest), 1);
return *this;
}
CHECK_RETURN_VALUE
Reader & ReadBool(char * dest)
{
(void) ReadBytes(reinterpret_cast<uint8_t *>(dest), 1);
return *this;
}
/// NOTE: only a subset of reads are supported here, more can be added if used/needed
CHECK_RETURN_VALUE
Reader & Read16(uint16_t * dest);
CHECK_RETURN_VALUE
Reader & Read32(uint32_t * dest);
};
} // namespace BigEndian
} // namespace Encoding
} // namespace chip