-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for signed/bool to BufferReader/Writer #27637
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,40 +17,46 @@ | |
|
||
#include "BufferReader.h" | ||
|
||
#include <lib/core/CHIPEncoding.h> | ||
|
||
#include <string.h> | ||
#include <type_traits> | ||
|
||
namespace chip { | ||
namespace Encoding { | ||
namespace LittleEndian { | ||
|
||
namespace { | ||
// These helper methods return void and put the value being read into an | ||
|
||
// This helper methods return void and put the value being read into an | ||
// outparam because that allows us to easily overload on the type of the | ||
// thing being read. | ||
void ReadHelper(const uint8_t *& p, uint8_t * dest) | ||
{ | ||
*dest = Read8(p); | ||
} | ||
void ReadHelper(const uint8_t *& p, uint16_t * dest) | ||
{ | ||
*dest = Read16(p); | ||
} | ||
void ReadHelper(const uint8_t *& p, uint32_t * dest) | ||
void ReadHelper(const uint8_t * p, bool * dest) | ||
{ | ||
*dest = Read32(p); | ||
*dest = (*p != 0); | ||
} | ||
void ReadHelper(const uint8_t *& p, uint64_t * dest) | ||
|
||
template <typename T> | ||
void ReadHelper(const uint8_t * p, T * dest) | ||
{ | ||
*dest = Read64(p); | ||
std::make_unsigned_t<T> result; | ||
memcpy(&result, p, sizeof(result)); | ||
result = chip::Encoding::LittleEndian::HostSwap(result); | ||
|
||
*dest = static_cast<T>(result); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Casting to signed of things that are "negative" (i.e. out of range) has implementation-defined behavior until c++20... This should probably use |
||
} | ||
|
||
} // anonymous namespace | ||
|
||
template <typename T> | ||
void Reader::RawRead(T * retval) | ||
void Reader::RawReadLowLevelBeCareful(T * retval) | ||
{ | ||
static_assert(CHAR_BIT == 8, "Our various sizeof checks rely on bytes and octets being the same thing"); | ||
static_assert((-1 & 3) == 3, "LittleEndian::BufferReader only works with 2's complement architectures."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the part that's meant to make the static_cast above safe? Or is this something we depend on for the HostSwap bits to work right for negative numbers? Would be good to make it clearer what the actual 2's complement dependency is here. |
||
|
||
VerifyOrReturn(IsSuccess()); | ||
|
||
static constexpr size_t data_size = sizeof(T); | ||
constexpr size_t data_size = sizeof(T); | ||
|
||
if (mAvailable < data_size) | ||
{ | ||
|
@@ -61,6 +67,8 @@ void Reader::RawRead(T * retval) | |
} | ||
|
||
ReadHelper(mReadPtr, retval); | ||
mReadPtr += data_size; | ||
|
||
mAvailable = static_cast<uint16_t>(mAvailable - data_size); | ||
} | ||
|
||
|
@@ -84,10 +92,15 @@ Reader & Reader::ReadBytes(uint8_t * dest, size_t size) | |
} | ||
|
||
// Explicit Read instantiations for the data types we want to support. | ||
template void Reader::RawRead(uint8_t *); | ||
template void Reader::RawRead(uint16_t *); | ||
template void Reader::RawRead(uint32_t *); | ||
template void Reader::RawRead(uint64_t *); | ||
template void Reader::RawReadLowLevelBeCareful(bool *); | ||
template void Reader::RawReadLowLevelBeCareful(int8_t *); | ||
template void Reader::RawReadLowLevelBeCareful(int16_t *); | ||
template void Reader::RawReadLowLevelBeCareful(int32_t *); | ||
template void Reader::RawReadLowLevelBeCareful(int64_t *); | ||
template void Reader::RawReadLowLevelBeCareful(uint8_t *); | ||
template void Reader::RawReadLowLevelBeCareful(uint16_t *); | ||
template void Reader::RawReadLowLevelBeCareful(uint32_t *); | ||
template void Reader::RawReadLowLevelBeCareful(uint64_t *); | ||
|
||
} // namespace LittleEndian | ||
} // namespace Encoding | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,17 @@ LittleEndian::BufferWriter & LittleEndian::BufferWriter::EndianPut(uint64_t x, s | |
return *this; | ||
} | ||
|
||
LittleEndian::BufferWriter & LittleEndian::BufferWriter::EndianPutSigned(int64_t x, size_t size) | ||
{ | ||
while (size > 0) | ||
{ | ||
Put(static_cast<uint8_t>(x & 0xff)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it not be safer to just cast to uint64_t and then EndianPut that? That's obviously the reverse of what we do when reading in the BufferReader, whereas it's not entirely obvious without digging through C++ specs what the bitwise ops here will end up doing. |
||
x >>= 8; | ||
size--; | ||
} | ||
return *this; | ||
} | ||
|
||
BigEndian::BufferWriter & BigEndian::BufferWriter::EndianPut(uint64_t x, size_t size) | ||
{ | ||
while (size-- > 0) | ||
|
@@ -69,5 +80,14 @@ BigEndian::BufferWriter & BigEndian::BufferWriter::EndianPut(uint64_t x, size_t | |
return *this; | ||
} | ||
|
||
BigEndian::BufferWriter & BigEndian::BufferWriter::EndianPutSigned(int64_t x, size_t size) | ||
{ | ||
while (size-- > 0) | ||
{ | ||
Put(static_cast<uint8_t>((x >> (size * 8)) & 0xff)); | ||
} | ||
return *this; | ||
} | ||
|
||
} // namespace Encoding | ||
} // namespace chip |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,13 +104,18 @@ class EndianBufferWriterBase : public BufferWriter | |
Derived & Put(uint8_t c) { return static_cast<Derived &>(BufferWriter::Put(c)); } | ||
Derived & Skip(size_t len) { return static_cast<Derived &>(BufferWriter::Skip(len)); } | ||
|
||
// write an integer into a buffer, in an endian specific way | ||
// write an integer into a buffer, in an endian-specific way | ||
|
||
Derived & Put8(uint8_t c) { return static_cast<Derived *>(this)->Put(c); } | ||
Derived & Put16(uint16_t x) { return static_cast<Derived *>(this)->EndianPut(x, sizeof(x)); } | ||
Derived & Put32(uint32_t x) { return static_cast<Derived *>(this)->EndianPut(x, sizeof(x)); } | ||
Derived & Put64(uint64_t x) { return static_cast<Derived *>(this)->EndianPut(x, sizeof(x)); } | ||
|
||
Derived & PutSigned8(int8_t x) { return static_cast<Derived *>(this)->EndianPutSigned(x, sizeof(x)); } | ||
Derived & PutSigned16(int16_t x) { return static_cast<Derived *>(this)->EndianPutSigned(x, sizeof(x)); } | ||
Derived & PutSigned32(int32_t x) { return static_cast<Derived *>(this)->EndianPutSigned(x, sizeof(x)); } | ||
Derived & PutSigned64(int64_t x) { return static_cast<Derived *>(this)->EndianPutSigned(x, sizeof(x)); } | ||
|
||
protected: | ||
EndianBufferWriterBase(uint8_t * buf, size_t len) : BufferWriter(buf, len) {} | ||
EndianBufferWriterBase(MutableByteSpan buf) : BufferWriter(buf.data(), buf.size()) {} | ||
|
@@ -123,11 +128,15 @@ namespace LittleEndian { | |
class BufferWriter : public EndianBufferWriterBase<BufferWriter> | ||
{ | ||
public: | ||
BufferWriter(uint8_t * buf, size_t len) : EndianBufferWriterBase<BufferWriter>(buf, len) {} | ||
BufferWriter(uint8_t * buf, size_t len) : EndianBufferWriterBase<BufferWriter>(buf, len) | ||
{ | ||
static_assert((-1 & 3) == 3, "LittleEndian::BufferWriter only works with 2's complement architectures."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, would be good to document exactly where this dependency comes in. I think if you do cast to unsigned as suggested above there is no such dependency here (because cast to unsigned is well-defined in the C++ standard). |
||
} | ||
BufferWriter(MutableByteSpan buf) : EndianBufferWriterBase<BufferWriter>(buf) {} | ||
BufferWriter(const BufferWriter & other) = default; | ||
BufferWriter & operator=(const BufferWriter & other) = default; | ||
BufferWriter & EndianPut(uint64_t x, size_t size); | ||
BufferWriter & EndianPutSigned(int64_t x, size_t size); | ||
}; | ||
|
||
} // namespace LittleEndian | ||
|
@@ -137,11 +146,15 @@ namespace BigEndian { | |
class BufferWriter : public EndianBufferWriterBase<BufferWriter> | ||
{ | ||
public: | ||
BufferWriter(uint8_t * buf, size_t len) : EndianBufferWriterBase<BufferWriter>(buf, len) {} | ||
BufferWriter(uint8_t * buf, size_t len) : EndianBufferWriterBase<BufferWriter>(buf, len) | ||
{ | ||
static_assert((-1 & 3) == 3, "BigEndian::BufferWriter only works with 2's complement architectures."); | ||
} | ||
BufferWriter(MutableByteSpan buf) : EndianBufferWriterBase<BufferWriter>(buf) {} | ||
BufferWriter(const BufferWriter & other) = default; | ||
BufferWriter & operator=(const BufferWriter & other) = default; | ||
BufferWriter & EndianPut(uint64_t x, size_t size); | ||
BufferWriter & EndianPutSigned(int64_t x, size_t size); | ||
}; | ||
|
||
} // namespace BigEndian | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"method returns" and "puts"