Add a BigEndian::Reader to be the equivalent for LittleEndian::Reader (#36195)
* Add a big endian reader since we seem to potentially want this
* Minor re-arrange
* Fix includes
* Restyled by clang-format
* make some compilers happy
* Update src/lib/support/BufferReader.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
---------
Co-authored-by: Andrei Litvin <andreilitvin@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/lib/support/BufferReader.cpp b/src/lib/support/BufferReader.cpp
index 02bce8f..cbd13f1 100644
--- a/src/lib/support/BufferReader.cpp
+++ b/src/lib/support/BufferReader.cpp
@@ -18,12 +18,29 @@
#include "BufferReader.h"
#include <lib/core/CHIPEncoding.h>
+#include <lib/core/CHIPError.h>
+#include <cstdint>
#include <string.h>
#include <type_traits>
namespace chip {
namespace Encoding {
+
+BufferReader & BufferReader::ReadBytes(uint8_t * dest, size_t size)
+{
+ static_assert(CHAR_BIT == 8, "Our various sizeof checks rely on bytes and octets being the same thing");
+
+ if (EnsureAvailable(size))
+ {
+ memcpy(dest, mReadPtr, size);
+ mReadPtr += size;
+ mAvailable -= size;
+ }
+
+ return *this;
+}
+
namespace LittleEndian {
namespace {
@@ -58,37 +75,12 @@
constexpr size_t data_size = sizeof(T);
- if (mAvailable < data_size)
+ if (EnsureAvailable(data_size))
{
- mStatus = CHIP_ERROR_BUFFER_TOO_SMALL;
- // Ensure that future reads all fail.
- mAvailable = 0;
- return;
+ ReadHelper(mReadPtr, retval);
+ mReadPtr += data_size;
+ mAvailable -= data_size;
}
-
- ReadHelper(mReadPtr, retval);
- mReadPtr += data_size;
-
- mAvailable = static_cast<uint16_t>(mAvailable - data_size);
-}
-
-Reader & Reader::ReadBytes(uint8_t * dest, size_t size)
-{
- static_assert(CHAR_BIT == 8, "Our various sizeof checks rely on bytes and octets being the same thing");
-
- if ((size > UINT16_MAX) || (mAvailable < size))
- {
- mStatus = CHIP_ERROR_BUFFER_TOO_SMALL;
- // Ensure that future reads all fail.
- mAvailable = 0;
- return *this;
- }
-
- memcpy(dest, mReadPtr, size);
-
- mReadPtr += size;
- mAvailable = static_cast<uint16_t>(mAvailable - size);
- return *this;
}
// Explicit Read instantiations for the data types we want to support.
@@ -104,5 +96,46 @@
template void Reader::RawReadLowLevelBeCareful(uint64_t *);
} // namespace LittleEndian
+
+namespace BigEndian {
+
+Reader & Reader::Read16(uint16_t * dest)
+{
+ if (!EnsureAvailable(sizeof(uint16_t)))
+ {
+ return *this;
+ }
+
+ static_assert(sizeof(*dest) == 2);
+
+ *dest = static_cast<uint16_t>((mReadPtr[0] << 8) + mReadPtr[1]);
+ mReadPtr += 2;
+ mAvailable -= 2;
+ return *this;
+}
+
+Reader & Reader::Read32(uint32_t * dest)
+{
+ if (!EnsureAvailable(sizeof(uint32_t)))
+ {
+ return *this;
+ }
+
+ static_assert(sizeof(*dest) == 4);
+
+ *dest = 0;
+ for (unsigned i = 0; i < sizeof(uint32_t); i++)
+ {
+ *dest <<= 8;
+ *dest += mReadPtr[i];
+ }
+
+ mReadPtr += sizeof(uint32_t);
+ mAvailable -= sizeof(uint32_t);
+ return *this;
+}
+
+} // namespace BigEndian
+
} // namespace Encoding
} // namespace chip
diff --git a/src/lib/support/BufferReader.h b/src/lib/support/BufferReader.h
index ac9c714..725ca8e 100644
--- a/src/lib/support/BufferReader.h
+++ b/src/lib/support/BufferReader.h
@@ -31,26 +31,11 @@
namespace chip {
namespace Encoding {
-namespace LittleEndian {
-/**
- * @class Reader
- *
- * Simple reader for reading little-endian things out of buffers.
- */
-class Reader
+class 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) : mBufStart(buffer), mReadPtr(buffer), mAvailable(buf_len)
+ BufferReader(const uint8_t * buffer, size_t buf_len) : mBufStart(buffer), mReadPtr(buffer), mAvailable(buf_len)
{
if (mBufStart == nullptr)
{
@@ -59,15 +44,6 @@
}
/**
- * 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()) {}
-
- /**
* Number of octets we have read so far.
*/
size_t OctetsRead() const { return static_cast<size_t>(mReadPtr - mBufStart); }
@@ -96,6 +72,121 @@
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.
@@ -268,20 +359,6 @@
}
/**
- * 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
- Reader & ReadBytes(uint8_t * dest, size_t size);
-
- /**
* 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
@@ -290,46 +367,80 @@
*/
template <typename T>
void RawReadLowLevelBeCareful(T * retval);
-
- /**
- * 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.
- */
- Reader & Skip(size_t len)
- {
- len = std::min(len, mAvailable);
- mReadPtr += len;
- mAvailable = static_cast<size_t>(mAvailable - len);
- return *this;
- }
-
-private:
- /**
- * 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;
};
} // 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
diff --git a/src/lib/support/tests/TestBufferReader.cpp b/src/lib/support/tests/TestBufferReader.cpp
index 97db9cf..ee100e1 100644
--- a/src/lib/support/tests/TestBufferReader.cpp
+++ b/src/lib/support/tests/TestBufferReader.cpp
@@ -30,21 +30,21 @@
#include <lib/support/BufferReader.h>
using namespace chip;
-using namespace chip::Encoding::LittleEndian;
+using namespace chip::Encoding;
static const uint8_t test_buffer[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
-struct TestReader : public Reader
+struct LittleEndianTestReader : public LittleEndian::Reader
{
- TestReader() : Reader(test_buffer, std::extent<decltype(test_buffer)>::value) {}
+ LittleEndianTestReader() : LittleEndian::Reader(test_buffer, std::extent<decltype(test_buffer)>::value) {}
};
-struct TestSpanReader : public Reader
+struct LittleEndianTestSpanReader : public LittleEndian::Reader
{
- TestSpanReader() : Reader(ByteSpan{ test_buffer, std::extent<decltype(test_buffer)>::value }) {}
+ LittleEndianTestSpanReader() : LittleEndian::Reader(ByteSpan{ test_buffer, std::extent<decltype(test_buffer)>::value }) {}
};
-static void TestBufferReader_BasicImpl(Reader & reader)
+static void TestBufferReader_BasicImpl(LittleEndian::Reader & reader)
{
uint8_t first;
uint16_t second;
@@ -75,21 +75,21 @@
TEST(TestBufferReader, TestBufferReader_Basic)
{
- TestReader reader;
+ LittleEndianTestReader reader;
TestBufferReader_BasicImpl(reader);
}
TEST(TestBufferReader, TestBufferReader_BasicSpan)
{
- TestSpanReader reader;
+ LittleEndianTestSpanReader reader;
TestBufferReader_BasicImpl(reader);
}
TEST(TestBufferReader, TestBufferReader_Saturation)
{
- TestReader reader;
+ LittleEndianTestReader reader;
uint64_t temp;
// Read some bytes out so we can get to the end of the buffer.
CHIP_ERROR err = reader.Read64(&temp).StatusCode();
@@ -113,12 +113,13 @@
TEST(TestBufferReader, TestBufferReader_Skip)
{
- TestReader reader;
+ LittleEndianTestReader reader;
uint8_t temp = 0;
uint16_t firstSkipLen = 2;
// Verify Skip() advances the start pointer the correct amount.
- CHIP_ERROR err = reader.Skip(firstSkipLen).Read8(&temp).StatusCode();
+ reader.Skip(firstSkipLen);
+ CHIP_ERROR err = reader.Read8(&temp).StatusCode();
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(temp, test_buffer[firstSkipLen]);
EXPECT_EQ(reader.OctetsRead(), (firstSkipLen + 1u));
@@ -175,7 +176,8 @@
uint32_t val1 = 0;
uint32_t val2 = 0;
- EXPECT_TRUE(reader.Skip(1).Read32(&val1).Read32(&val2).IsSuccess());
+ reader.Skip(1);
+ EXPECT_TRUE(reader.Read32(&val1).Read32(&val2).IsSuccess());
EXPECT_EQ(reader.Remaining(), 1u);
EXPECT_EQ(val1, static_cast<uint32_t>(0xfffffffeUL));
EXPECT_EQ(val2, static_cast<uint32_t>(0xffffffffUL));
@@ -227,7 +229,8 @@
int32_t val1 = 0;
int32_t val2 = 0;
- EXPECT_TRUE(reader.Skip(1).ReadSigned32(&val1).ReadSigned32(&val2).IsSuccess());
+ reader.Skip(1);
+ EXPECT_TRUE(reader.ReadSigned32(&val1).ReadSigned32(&val2).IsSuccess());
EXPECT_EQ(reader.Remaining(), 1u);
EXPECT_EQ(val1, static_cast<int32_t>(-2L));
EXPECT_EQ(val2, static_cast<int32_t>(-1L));
@@ -272,3 +275,23 @@
EXPECT_EQ(val3, '\xff');
}
}
+
+TEST(TestBigEndianBufferReader, GenericTests)
+{
+ uint8_t test_buf[] = { 0x12, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xBC, 1, 2, 3 };
+
+ chip::Encoding::BigEndian::Reader reader{ ByteSpan{ test_buf } };
+
+ uint16_t v1;
+ uint32_t v2;
+ uint8_t v3;
+
+ EXPECT_TRUE(reader.Read16(&v1).Read32(&v2).Read8(&v3).IsSuccess());
+ EXPECT_EQ(reader.Remaining(), 3u);
+ EXPECT_EQ(v1, 0x1223u);
+ EXPECT_EQ(v2, 0x456789ABu);
+ EXPECT_EQ(v3, 0xBCu);
+
+ // Insufficient buffer after that
+ EXPECT_FALSE(reader.Read32(&v2).IsSuccess());
+}