From 1706fba305ce3f689e2408d32e7c213d80ab01fa Mon Sep 17 00:00:00 2001 From: Freddie Lamble Date: Thu, 15 Oct 2026 09:15:12 +0000 Subject: [PATCH] Return a decode result from Buffer::copy `Buffer::copy` relied on `BSLS_ASSERT` to confirm enough bytes remained before its `memcpy`. Where that assert is compiled out a short buffer read past the end of the backing data; where it is enabled a truncated frame aborted the process. Neither is a reasonable response to bytes that arrive off the wire. Give `copy` an out parameter and a `bool` result, so a caller cannot go on to use a value that was never read. The destination must be exactly as wide as the wire type, checked at compile time, so naming the wrong type can neither truncate the value nor leave the read position part way through a field. `Buffer::consume` truncates to the bytes available. Every decoder in `rmqamqpt` propagates that result, so a truncated payload becomes a decode failure, which `Framer::appendFrame` already reports to `Connection` as a channel exception. `ConfirmSelect::decode` had no length check at all and is covered by the same change. Three value level fixes in the decoders this rewrites: - AMQP packs consecutive `bit` fields into one octet. Those fields are read into a `uint8_t` and masked to the bit they occupy, rather than copied into a `bool`, which is undefined for a byte other than 0 or 1 and let a reserved bit decide the value. - The unsupported `DECIMAL` value reported success without consuming its five octets, leaving the rest of the field table to be read from the wrong offset. It is now consumed and left unset, which re-encodes as `V`, so republishing such headers drops the decimal. - `Types::decodeTimestamp` returns `bool` and uses the checked `bdlt::EpochUtil::convertFromTimeT64` overload, whose unchecked counterpart has an undefined behaviour precondition that a broker supplied value can violate. The rewritten `BasicQoS::decode` guard also drops a stale log message that named `Connection.Tune`. --- src/rmq/rmqamqpt/rmqamqpt_basicack.cpp | 17 +- src/rmq/rmqamqpt/rmqamqpt_basiccancel.cpp | 8 +- src/rmq/rmqamqpt/rmqamqpt_basicconsume.cpp | 11 +- src/rmq/rmqamqpt/rmqamqpt_basicdeliver.cpp | 12 +- src/rmq/rmqamqpt/rmqamqpt_basicnack.cpp | 11 +- src/rmq/rmqamqpt/rmqamqpt_basicproperties.cpp | 28 +++- src/rmq/rmqamqpt/rmqamqpt_basicpublish.cpp | 11 +- src/rmq/rmqamqpt/rmqamqpt_basicqos.cpp | 19 ++- src/rmq/rmqamqpt/rmqamqpt_basicreturn.cpp | 5 +- src/rmq/rmqamqpt/rmqamqpt_buffer.cpp | 7 +- src/rmq/rmqamqpt/rmqamqpt_buffer.h | 33 +++- src/rmq/rmqamqpt/rmqamqpt_channelclose.cpp | 14 +- src/rmq/rmqamqpt/rmqamqpt_channelflow.cpp | 11 +- src/rmq/rmqamqpt/rmqamqpt_channelflowok.cpp | 11 +- src/rmq/rmqamqpt/rmqamqpt_confirmselect.cpp | 12 +- src/rmq/rmqamqpt/rmqamqpt_connectionclose.cpp | 13 +- src/rmq/rmqamqpt/rmqamqpt_connectionstart.cpp | 6 +- src/rmq/rmqamqpt/rmqamqpt_connectiontune.cpp | 9 +- .../rmqamqpt/rmqamqpt_connectiontuneok.cpp | 13 +- src/rmq/rmqamqpt/rmqamqpt_contentheader.cpp | 12 +- src/rmq/rmqamqpt/rmqamqpt_exchangebind.cpp | 9 +- src/rmq/rmqamqpt/rmqamqpt_exchangedeclare.cpp | 7 +- src/rmq/rmqamqpt/rmqamqpt_queuebind.cpp | 9 +- src/rmq/rmqamqpt/rmqamqpt_queuedeclare.cpp | 7 +- src/rmq/rmqamqpt/rmqamqpt_queuedeclareok.cpp | 13 +- src/rmq/rmqamqpt/rmqamqpt_queuedelete.cpp | 4 +- src/rmq/rmqamqpt/rmqamqpt_queuedeleteok.cpp | 8 +- src/rmq/rmqamqpt/rmqamqpt_types.cpp | 138 +++++++++++------ src/rmq/rmqamqpt/rmqamqpt_types.h | 2 +- src/tests/rmqamqpt/rmqamqpt_basicack.t.cpp | 38 +++++ src/tests/rmqamqpt/rmqamqpt_basiccancel.t.cpp | 16 ++ .../rmqamqpt/rmqamqpt_basicdeliver.t.cpp | 30 ++++ .../rmqamqpt/rmqamqpt_basicproperties.t.cpp | 46 ++++++ src/tests/rmqamqpt/rmqamqpt_basicqos.t.cpp | 29 ++++ src/tests/rmqamqpt/rmqamqpt_buffer.t.cpp | 66 +++++++- src/tests/rmqamqpt/rmqamqpt_channelflow.t.cpp | 16 ++ .../rmqamqpt/rmqamqpt_channelflowok.t.cpp | 17 ++ .../rmqamqpt/rmqamqpt_confirmselect.t.cpp | 8 + .../rmqamqpt/rmqamqpt_exchangebind.t.cpp | 18 +++ src/tests/rmqamqpt/rmqamqpt_queuebind.t.cpp | 18 +++ src/tests/rmqamqpt/rmqamqpt_types.t.cpp | 146 +++++++++++++++++- 41 files changed, 707 insertions(+), 201 deletions(-) diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicack.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicack.cpp index 7da0a128..2da00fa1 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicack.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicack.cpp @@ -25,6 +25,9 @@ namespace BloombergLP { namespace rmqamqpt { +namespace { +const uint8_t BITMASK_BIT_MULTIPLE = (1 << 0); +} // namespace BasicAck::BasicAck(bsl::uint64_t dt, bool multiple) : d_deliveryTag(dt) @@ -44,19 +47,13 @@ bool BasicAck::decode(BasicAck* ack, { rmqamqpt::Buffer buffer(data, dataLength); - if (buffer.available() < sizeof(bdlb::BigEndianUint64)) { - - return false; - } - - ack->d_deliveryTag = buffer.copy(); - - if (buffer.available() < sizeof(bool)) { + uint8_t bitmask = 0; + if (!buffer.copy(&ack->d_deliveryTag) || + !buffer.copy(&bitmask)) { return false; } - ack->d_multiple = buffer.copy(); - + ack->d_multiple = bitmask & BITMASK_BIT_MULTIPLE; return true; } diff --git a/src/rmq/rmqamqpt/rmqamqpt_basiccancel.cpp b/src/rmq/rmqamqpt/rmqamqpt_basiccancel.cpp index 80753af0..3e0e01a9 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basiccancel.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basiccancel.cpp @@ -22,6 +22,9 @@ namespace BloombergLP { namespace rmqamqpt { +namespace { +const uint8_t BITMASK_BIT_NOWAIT = (1 << 0); +} // namespace BasicCancel::BasicCancel(const bsl::string& consumerTag, bool noWait) : d_consumerTag(consumerTag) @@ -42,11 +45,12 @@ bool BasicCancel::decode(BasicCancel* cancel, if (!Types::decodeShortString(&cancel->d_consumerTag, &buffer)) { return false; } - if (buffer.available() < sizeof(bool)) { + uint8_t bitmask = 0; + if (!buffer.copy(&bitmask)) { return false; } - cancel->d_noWait = buffer.copy(); + cancel->d_noWait = bitmask & BITMASK_BIT_NOWAIT; return true; } diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicconsume.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicconsume.cpp index 087133dc..724e761c 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicconsume.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicconsume.cpp @@ -67,23 +67,22 @@ bool BasicConsume::decode(BasicConsume* consume, { rmqamqpt::Buffer buffer(data, dataLength); - // uint16_t reserved1; - if (sizeof(uint16_t /* reserved1 */) > buffer.available()) { + uint16_t reserved1 = 0; + if (!buffer.copy(&reserved1)) { return false; } - /*reserved1 = */ buffer.copy(); if (!Types::decodeShortString(&consume->d_queue, &buffer)) { return false; } if (!Types::decodeShortString(&consume->d_consumerTag, &buffer)) { return false; } - if (sizeof(uint8_t) > buffer.available()) { + + uint8_t theByte = 0; + if (!buffer.copy(&theByte)) { return false; } - const uint8_t theByte = buffer.copy(); - consume->d_noLocal = theByte & MASK_BIT_0; consume->d_noAck = theByte & MASK_BIT_1; consume->d_exclusive = theByte & MASK_BIT_2; diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicdeliver.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicdeliver.cpp index 93cb820d..71fea7f1 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicdeliver.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicdeliver.cpp @@ -26,7 +26,8 @@ namespace rmqamqpt { namespace { BALL_LOG_SET_NAMESPACE_CATEGORY("RMQAMQPT.BASICDELIVER") -} +const uint8_t BITMASK_BIT_REDELIVERED = (1 << 0); +} // namespace BasicDeliver::BasicDeliver(const bsl::string& consumerTag, uint64_t deliveryTag, @@ -60,15 +61,16 @@ bool BasicDeliver::decode(BasicDeliver* deliver, return false; } - if (sizeof(deliver->d_deliveryTag) + sizeof(deliver->d_redelivered) > - buffer.available()) { + uint8_t bitmask = 0; + if (!buffer.copy(&deliver->d_deliveryTag) || + !buffer.copy(&bitmask)) { BALL_LOG_ERROR << "Not enough data to decode BasicDeliver frame: available " << buffer.available(); return false; } - deliver->d_deliveryTag = buffer.copy(); - deliver->d_redelivered = buffer.copy(); + + deliver->d_redelivered = bitmask & BITMASK_BIT_REDELIVERED; if (!Types::decodeShortString(&deliver->d_exchange, &buffer)) { return false; diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicnack.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicnack.cpp index 43c49b6b..60d8eaf0 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicnack.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicnack.cpp @@ -48,17 +48,12 @@ bool BasicNack::decode(BasicNack* nack, { rmqamqpt::Buffer buffer(data, dataLength); - if (buffer.available() < sizeof(bdlb::BigEndianUint64)) { - return false; - } - - nack->d_deliveryTag = buffer.copy(); - - if (buffer.available() < sizeof(uint8_t)) { + uint8_t bitmask = 0; + if (!buffer.copy(&nack->d_deliveryTag) || + !buffer.copy(&bitmask)) { return false; } - uint8_t bitmask = buffer.copy(); nack->d_multiple = bitmask & BITMASK_BIT_0; nack->d_requeue = bitmask & BITMASK_BIT_1; diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicproperties.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicproperties.cpp index e9f51d31..f4e06cf9 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicproperties.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicproperties.cpp @@ -252,11 +252,12 @@ bool BasicProperties::decode(BasicProperties* props, { rmqamqpt::Buffer buffer(data, dataLength); - if (sizeof(uint16_t) > buffer.available()) { + uint16_t flags = 0; + if (!buffer.copy(&flags)) { return false; } - bool success = true; - const uint16_t flags = buffer.copy(); + + bool success = true; rmqt::Properties properties; @@ -288,10 +289,20 @@ bool BasicProperties::decode(BasicProperties* props, } } if (flags & maskForProperty(DELIVERY_MODE)) { - properties.deliveryMode = buffer.copy(); + properties.deliveryMode = uint8_t(); + if (!buffer.copy(&*properties.deliveryMode)) { + BALL_LOG_ERROR << "Decoding fail for basic property: " + << PROPERTY_NAMES[DELIVERY_MODE]; + success = false; + } } if (flags & maskForProperty(PRIORITY)) { - properties.priority = buffer.copy(); + properties.priority = uint8_t(); + if (!buffer.copy(&*properties.priority)) { + BALL_LOG_ERROR << "Decoding fail for basic property: " + << PROPERTY_NAMES[PRIORITY]; + success = false; + } } if (flags & maskForProperty(CORRELATION_ID)) { properties.correlationId = ""; @@ -331,7 +342,12 @@ bool BasicProperties::decode(BasicProperties* props, } if (flags & maskForProperty(TIMESTAMP)) { properties.timestamp = bdlt::Datetime(); - rmqamqpt::Types::decodeTimestamp(&*properties.timestamp, &buffer); + if (!rmqamqpt::Types::decodeTimestamp(&*properties.timestamp, + &buffer)) { + BALL_LOG_ERROR << "Decoding fail for basic property: " + << PROPERTY_NAMES[TIMESTAMP]; + success = false; + } } if (flags & maskForProperty(TYPE)) { properties.type = ""; diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicpublish.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicpublish.cpp index b4b1d7b2..8de7a516 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicpublish.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicpublish.cpp @@ -53,22 +53,23 @@ bool BasicPublish::decode(BasicPublish* publish, bsl::size_t dataLength) { rmqamqpt::Buffer buffer(data, dataLength); - if (sizeof(int16_t) > buffer.available()) { + + int16_t reserved1 = 0; + if (!buffer.copy(&reserved1)) { return false; } - buffer.copy(); if (!Types::decodeShortString(&publish->d_exchangeName, &buffer)) { return false; } if (!Types::decodeShortString(&publish->d_routingKey, &buffer)) { return false; } - if (sizeof(uint8_t) > buffer.available()) { + + uint8_t theByte = 0; + if (!buffer.copy(&theByte)) { return false; } - const uint8_t theByte = buffer.copy(); - publish->d_mandatory = theByte & MASK_BIT_0; publish->d_immediate = theByte & MASK_BIT_1; diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicqos.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicqos.cpp index 9ba7e2c5..57f35aac 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicqos.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicqos.cpp @@ -26,7 +26,8 @@ namespace rmqamqpt { namespace { BALL_LOG_SET_NAMESPACE_CATEGORY("RMQAMQPT.BASICQOS") -} +const uint8_t BITMASK_BIT_GLOBAL = (1 << 0); +} // namespace BasicQoS::BasicQoS(bsl::uint16_t prefetchCount, bsl::uint32_t prefetchSize, @@ -50,18 +51,16 @@ bool BasicQoS::decode(BasicQoS* qos, { rmqamqpt::Buffer buffer(data, dataLength); - if (sizeof(qos->d_prefetchSize) + sizeof(qos->d_prefetchCount) + - sizeof(qos->d_global) > - buffer.available()) { - BALL_LOG_ERROR - << "Not enough data to decode ConnectionTune frame: available " - << buffer.available(); + uint8_t bitmask = 0; + if (!buffer.copy(&qos->d_prefetchSize) || + !buffer.copy(&qos->d_prefetchCount) || + !buffer.copy(&bitmask)) { + BALL_LOG_ERROR << "Not enough data to decode BasicQoS frame: available " + << buffer.available(); return false; } - qos->d_prefetchSize = buffer.copy(); - qos->d_prefetchCount = buffer.copy(); - qos->d_global = buffer.copy(); + qos->d_global = bitmask & BITMASK_BIT_GLOBAL; return true; } diff --git a/src/rmq/rmqamqpt/rmqamqpt_basicreturn.cpp b/src/rmq/rmqamqpt/rmqamqpt_basicreturn.cpp index 97521135..a14f08ff 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_basicreturn.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_basicreturn.cpp @@ -52,11 +52,10 @@ bool BasicReturn::decode(BasicReturn* basicReturn, { rmqamqpt::Buffer buffer(data, dataLength); - if (buffer.available() < sizeof(basicReturn->d_replyCode)) { + uint16_t replyCode = 0; + if (!buffer.copy(&replyCode)) { return false; } - - const uint16_t replyCode = buffer.copy(); basicReturn->d_replyCode = static_cast(replyCode); diff --git a/src/rmq/rmqamqpt/rmqamqpt_buffer.cpp b/src/rmq/rmqamqpt/rmqamqpt_buffer.cpp index bd249533..09c3a0ba 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_buffer.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_buffer.cpp @@ -17,6 +17,8 @@ #include +#include + namespace BloombergLP { namespace rmqamqpt { @@ -30,8 +32,9 @@ Buffer::Buffer(const_pointer start, size_type length) Buffer Buffer::consume(size_type size) { - Buffer b(ptr(), size); - skip(size); + const size_type consumed = bsl::min(size, available()); + Buffer b(ptr(), consumed); + skip(consumed); return b; } diff --git a/src/rmq/rmqamqpt/rmqamqpt_buffer.h b/src/rmq/rmqamqpt/rmqamqpt_buffer.h index 4280232a..b8970627 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_buffer.h +++ b/src/rmq/rmqamqpt/rmqamqpt_buffer.h @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include @@ -48,9 +48,19 @@ class Buffer { ~Buffer() = default; #endif - template - TYPE copy(); + /// Read a `TYPE` out of the buffer, assign it to `value` and advance + /// past it. + /// + /// Return false, leaving `value` and the read position untouched, when + /// fewer than `sizeof(TYPE)` bytes remain. `TYPE` is the type as it + /// appears on the wire and must be given explicitly; `VALUE` is + /// deduced, so a big-endian field can be read straight into the + /// host-order member it belongs to. + template + bool copy(VALUE* value); + /// Return a `Buffer` over the next `size` bytes and advance past them, + /// truncating `size` to `available()`. Buffer consume(size_type size); void skip(size_type size); const_pointer ptr() const { return d_data + d_offset; } @@ -64,16 +74,25 @@ class Buffer { offset_type d_offset; }; -template -TYPE Buffer::copy() +template +bool Buffer::copy(VALUE* value) { BSLMF_ASSERT(bsl::is_trivially_copyable::value); BSLMF_ASSERT(bslmf::IsBitwiseMoveable::value); - BSLS_ASSERT(available() >= sizeof(TYPE)); + BSLMF_ASSERT(sizeof(VALUE) == sizeof(TYPE)); + // AMQP `bit` fields share an octet: read a uint8_t and mask it instead + BSLMF_ASSERT((!bsl::is_same::value)); + + if (available() < sizeof(TYPE)) { + return false; + } + TYPE val; bsl::memcpy(&val, ptr(), sizeof(TYPE)); skip(sizeof(TYPE)); - return val; + + *value = val; + return true; } } // namespace rmqamqpt diff --git a/src/rmq/rmqamqpt/rmqamqpt_channelclose.cpp b/src/rmq/rmqamqpt/rmqamqpt_channelclose.cpp index 4dd5747d..cc3481a2 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_channelclose.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_channelclose.cpp @@ -52,11 +52,11 @@ bool ChannelClose::decode(ChannelClose* close, bsl::size_t dataLength) { rmqamqpt::Buffer buffer(data, dataLength); - if (buffer.available() < sizeof(bdlb::BigEndianUint16)) { + + uint16_t replyCode = 0; + if (!buffer.copy(&replyCode)) { return false; } - - const uint16_t replyCode = buffer.copy(); close->d_replyCode = static_cast(replyCode); @@ -64,14 +64,14 @@ bool ChannelClose::decode(ChannelClose* close, return false; } - if (buffer.available() < 2 * sizeof(bdlb::BigEndianUint16)) { + uint16_t classId = 0; + uint16_t methodId = 0; + if (!buffer.copy(&classId) || + !buffer.copy(&methodId)) { return false; } - const uint16_t classId = buffer.copy(); close->d_classId = static_cast(classId); - - const uint16_t methodId = buffer.copy(); close->d_methodId = static_cast(methodId); diff --git a/src/rmq/rmqamqpt/rmqamqpt_channelflow.cpp b/src/rmq/rmqamqpt/rmqamqpt_channelflow.cpp index 67cb1c91..000efebf 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_channelflow.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_channelflow.cpp @@ -25,7 +25,8 @@ namespace rmqamqpt { namespace { BALL_LOG_SET_NAMESPACE_CATEGORY("RMQAMQPT.CHANNELFLOW") -} +const uint8_t BITMASK_BIT_ACTIVE = (1 << 0); +} // namespace ChannelFlow::ChannelFlow() : d_activeFlow(0) @@ -41,13 +42,15 @@ bool ChannelFlow::decode(ChannelFlow* flow, const uint8_t* data, bsl::size_t dataLength) { - if (dataLength < sizeof(uint8_t)) { + rmqamqpt::Buffer buffer(data, dataLength); + + uint8_t bitmask = 0; + if (!buffer.copy(&bitmask)) { BALL_LOG_ERROR << "Not enough data to decode ChannelFlow method"; return false; } - rmqamqpt::Buffer buffer(data, dataLength); - flow->d_activeFlow = buffer.copy(); + flow->d_activeFlow = bitmask & BITMASK_BIT_ACTIVE; return true; } diff --git a/src/rmq/rmqamqpt/rmqamqpt_channelflowok.cpp b/src/rmq/rmqamqpt/rmqamqpt_channelflowok.cpp index 3e6fe87a..7ec9fa86 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_channelflowok.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_channelflowok.cpp @@ -25,7 +25,8 @@ namespace rmqamqpt { namespace { BALL_LOG_SET_NAMESPACE_CATEGORY("RMQAMQPT.CHANNELFLOWOK") -} +const uint8_t BITMASK_BIT_ACTIVE = (1 << 0); +} // namespace ChannelFlowOk::ChannelFlowOk() : d_activeFlow(0) @@ -41,13 +42,15 @@ bool ChannelFlowOk::decode(ChannelFlowOk* flowOk, const uint8_t* data, bsl::size_t dataLength) { - if (dataLength < sizeof(uint8_t)) { + rmqamqpt::Buffer buffer(data, dataLength); + + uint8_t bitmask = 0; + if (!buffer.copy(&bitmask)) { BALL_LOG_ERROR << "Not enough data to decode ChannelFlowOk method"; return false; } - rmqamqpt::Buffer buffer(data, dataLength); - flowOk->d_activeFlow = buffer.copy(); + flowOk->d_activeFlow = bitmask & BITMASK_BIT_ACTIVE; return true; } diff --git a/src/rmq/rmqamqpt/rmqamqpt_confirmselect.cpp b/src/rmq/rmqamqpt/rmqamqpt_confirmselect.cpp index b7d953a0..d4f759b7 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_confirmselect.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_confirmselect.cpp @@ -21,6 +21,9 @@ namespace BloombergLP { namespace rmqamqpt { +namespace { +const uint8_t BITMASK_BIT_NOWAIT = (1 << 0); +} // namespace ConfirmSelect::ConfirmSelect() : d_noWait() @@ -37,8 +40,13 @@ bool ConfirmSelect::decode(ConfirmSelect* select, bsl::size_t dataLength) { rmqamqpt::Buffer buffer(data, dataLength); - uint8_t bitmask = buffer.copy(); - select->d_noWait = bitmask & 0x1; + + uint8_t bitmask = 0; + if (!buffer.copy(&bitmask)) { + return false; + } + + select->d_noWait = bitmask & BITMASK_BIT_NOWAIT; return true; } diff --git a/src/rmq/rmqamqpt/rmqamqpt_connectionclose.cpp b/src/rmq/rmqamqpt/rmqamqpt_connectionclose.cpp index 396a146e..5b787cc8 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_connectionclose.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_connectionclose.cpp @@ -53,11 +53,10 @@ bool ConnectionClose::decode(ConnectionClose* close, { rmqamqpt::Buffer buffer(data, dataLength); - if (buffer.available() < sizeof(bdlb::BigEndianUint16)) { + uint16_t replyCode = 0; + if (!buffer.copy(&replyCode)) { return false; } - - const uint16_t replyCode = buffer.copy(); close->d_replyCode = static_cast(replyCode); @@ -65,14 +64,14 @@ bool ConnectionClose::decode(ConnectionClose* close, return false; } - if (buffer.available() < 2 * sizeof(bdlb::BigEndianUint16)) { + uint16_t classId = 0; + uint16_t methodId = 0; + if (!buffer.copy(&classId) || + !buffer.copy(&methodId)) { return false; } - const uint16_t classId = buffer.copy(); close->d_classId = static_cast(classId); - - const uint16_t methodId = buffer.copy(); close->d_methodId = static_cast(methodId); diff --git a/src/rmq/rmqamqpt/rmqamqpt_connectionstart.cpp b/src/rmq/rmqamqpt/rmqamqpt_connectionstart.cpp index f391aae7..a0d7fb34 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_connectionstart.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_connectionstart.cpp @@ -77,14 +77,12 @@ bool ConnectionStart::decode(ConnectionStart* start, { rmqamqpt::Buffer buffer(data, dataLength); - if (buffer.available() < 2) { + if (!buffer.copy(&start->d_versionMajor) || + !buffer.copy(&start->d_versionMinor)) { BALL_LOG_ERROR << "Cannot decode ConnectionStart version"; return false; } - start->d_versionMajor = buffer.copy(); - start->d_versionMinor = buffer.copy(); - if (!Types::decodeFieldTable(&start->d_properties, &buffer)) { BALL_LOG_ERROR << "Cannot decode ConnectionStart properties"; return false; diff --git a/src/rmq/rmqamqpt/rmqamqpt_connectiontune.cpp b/src/rmq/rmqamqpt/rmqamqpt_connectiontune.cpp index ef09e04b..a2a318a5 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_connectiontune.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_connectiontune.cpp @@ -49,18 +49,15 @@ bool ConnectionTune::decode(ConnectionTune* tune, { rmqamqpt::Buffer buffer(data, dataLength); - if (sizeof(tune->d_channelMax) + sizeof(tune->d_frameMax) + - sizeof(tune->d_heartbeatInterval) > - buffer.available()) { + if (!buffer.copy(&tune->d_channelMax) || + !buffer.copy(&tune->d_frameMax) || + !buffer.copy(&tune->d_heartbeatInterval)) { BALL_LOG_ERROR << "Not enough data to decode ConnectionTune frame: available " << buffer.available(); return false; } - tune->d_channelMax = buffer.copy(); - tune->d_frameMax = buffer.copy(); - tune->d_heartbeatInterval = buffer.copy(); return true; } void ConnectionTune::encode(Writer& output, const ConnectionTune& tune) diff --git a/src/rmq/rmqamqpt/rmqamqpt_connectiontuneok.cpp b/src/rmq/rmqamqpt/rmqamqpt_connectiontuneok.cpp index ccbf939e..9112ab6e 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_connectiontuneok.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_connectiontuneok.cpp @@ -44,16 +44,9 @@ bool ConnectionTuneOk::decode(ConnectionTuneOk* tuneOk, { rmqamqpt::Buffer buffer(data, dataLength); - if (sizeof(tuneOk->d_channelMax) + sizeof(tuneOk->d_frameMax) + - sizeof(tuneOk->d_heartbeatInterval) > - buffer.available()) { - return false; - } - - tuneOk->d_channelMax = buffer.copy(); - tuneOk->d_frameMax = buffer.copy(); - tuneOk->d_heartbeatInterval = buffer.copy(); - return true; + return buffer.copy(&tuneOk->d_channelMax) && + buffer.copy(&tuneOk->d_frameMax) && + buffer.copy(&tuneOk->d_heartbeatInterval); } void ConnectionTuneOk::encode(Writer& output, const ConnectionTuneOk& tuneOk) { diff --git a/src/rmq/rmqamqpt/rmqamqpt_contentheader.cpp b/src/rmq/rmqamqpt/rmqamqpt_contentheader.cpp index 1d971dcd..8a190f06 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_contentheader.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_contentheader.cpp @@ -59,26 +59,26 @@ bool ContentHeader::decode(ContentHeader* contentHeader, bsl::size_t dataLength) { rmqamqpt::Buffer buffer(data, dataLength); - if (sizeof(uint16_t) + sizeof(uint16_t) + - sizeof(contentHeader->d_bodySize) > - buffer.available()) { + + uint16_t classId = 0; + uint16_t weight = 0; + if (!buffer.copy(&classId) || + !buffer.copy(&weight) || + !buffer.copy(&contentHeader->d_bodySize)) { BALL_LOG_ERROR << "Not enough data to decode ContentHeader frame. " << "Available buffer bytes: " << buffer.available(); return false; } - const uint16_t classId = buffer.copy(); contentHeader->d_classId = static_cast(classId); - const uint16_t weight = buffer.copy(); // Reserved and should always be zero if (weight != 0) { BALL_LOG_ERROR << "Weight field inside content header frame is non-zero: " << weight; } - contentHeader->d_bodySize = buffer.copy(); return BasicProperties::decode(&contentHeader->d_properties, static_cast(buffer.ptr()), diff --git a/src/rmq/rmqamqpt/rmqamqpt_exchangebind.cpp b/src/rmq/rmqamqpt/rmqamqpt_exchangebind.cpp index 769c1310..d1cc7731 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_exchangebind.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_exchangebind.cpp @@ -24,6 +24,9 @@ namespace BloombergLP { namespace rmqamqpt { +namespace { +const uint8_t BITMASK_BIT_NOWAIT = (1 << 0); +} // namespace ExchangeBind::ExchangeBind() : d_sourceX() @@ -72,11 +75,11 @@ bool ExchangeBind::decode(ExchangeBind* bind, return false; } - if (buffer.available() < sizeof(bind->d_noWait)) { + uint8_t bitmask = 0; + if (!buffer.copy(&bitmask)) { return false; } - - bind->d_noWait = buffer.copy(); + bind->d_noWait = bitmask & BITMASK_BIT_NOWAIT; return Types::decodeFieldTable(&bind->d_arguments, &buffer); } diff --git a/src/rmq/rmqamqpt/rmqamqpt_exchangedeclare.cpp b/src/rmq/rmqamqpt/rmqamqpt_exchangedeclare.cpp index a2bedc0f..ae387d29 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_exchangedeclare.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_exchangedeclare.cpp @@ -85,14 +85,11 @@ bool ExchangeDeclare::decode(ExchangeDeclare* declare, return false; } - if (buffer.available() < - sizeof(declare->d_passive) + sizeof(declare->d_durable) + - sizeof(declare->d_autoDelete) + sizeof(declare->d_internal) + - sizeof(declare->d_noWait)) { + uint8_t bitmask = 0; + if (!buffer.copy(&bitmask)) { return false; } - uint8_t bitmask = buffer.copy(); declare->d_passive = bitmask & BITMASK_BIT_PASSIVE; declare->d_durable = bitmask & BITMASK_BIT_DURABLE; declare->d_autoDelete = bitmask & BITMASK_BIT_AUTODELETE; diff --git a/src/rmq/rmqamqpt/rmqamqpt_queuebind.cpp b/src/rmq/rmqamqpt/rmqamqpt_queuebind.cpp index e00c782c..d662429f 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_queuebind.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_queuebind.cpp @@ -24,6 +24,9 @@ namespace BloombergLP { namespace rmqamqpt { +namespace { +const uint8_t BITMASK_BIT_NOWAIT = (1 << 0); +} // namespace QueueBind::QueueBind() : d_queue() @@ -72,11 +75,11 @@ bool QueueBind::decode(QueueBind* bind, return false; } - if (buffer.available() < sizeof(bind->d_noWait)) { + uint8_t bitmask = 0; + if (!buffer.copy(&bitmask)) { return false; } - - bind->d_noWait = buffer.copy(); + bind->d_noWait = bitmask & BITMASK_BIT_NOWAIT; return Types::decodeFieldTable(&bind->d_arguments, &buffer); } diff --git a/src/rmq/rmqamqpt/rmqamqpt_queuedeclare.cpp b/src/rmq/rmqamqpt/rmqamqpt_queuedeclare.cpp index 03053418..3bf50ac4 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_queuedeclare.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_queuedeclare.cpp @@ -73,15 +73,12 @@ bool QueueDeclare::decode(QueueDeclare* declare, } buffer.skip(sizeof(uint16_t)); + uint8_t bitmask = 0; if (!Types::decodeShortString(&declare->d_name, &buffer) || - buffer.available() < - sizeof(declare->d_passive) + sizeof(declare->d_durable) + - sizeof(declare->d_exclusive) + sizeof(declare->d_autoDelete) + - sizeof(declare->d_noWait)) { + !buffer.copy(&bitmask)) { return false; } - uint8_t bitmask = buffer.copy(); declare->d_passive = bitmask & BITMASK_BIT_0; declare->d_durable = bitmask & BITMASK_BIT_1; declare->d_exclusive = bitmask & BITMASK_BIT_2; diff --git a/src/rmq/rmqamqpt/rmqamqpt_queuedeclareok.cpp b/src/rmq/rmqamqpt/rmqamqpt_queuedeclareok.cpp index a8f68245..74e1ca59 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_queuedeclareok.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_queuedeclareok.cpp @@ -46,16 +46,9 @@ bool QueueDeclareOk::decode(QueueDeclareOk* declareOk, rmqamqpt::Buffer buffer(data, dataLength); - if (!Types::decodeShortString(&declareOk->d_name, &buffer) || - buffer.available() < sizeof(declareOk->d_messageCount) + - sizeof(declareOk->d_consumerCount)) { - return false; - } - - declareOk->d_messageCount = buffer.copy(); - declareOk->d_consumerCount = buffer.copy(); - - return true; + return Types::decodeShortString(&declareOk->d_name, &buffer) && + buffer.copy(&declareOk->d_messageCount) && + buffer.copy(&declareOk->d_consumerCount); } void QueueDeclareOk::encode(Writer& output, const QueueDeclareOk& declareOk) diff --git a/src/rmq/rmqamqpt/rmqamqpt_queuedelete.cpp b/src/rmq/rmqamqpt/rmqamqpt_queuedelete.cpp index eb8228a5..e4b7b92b 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_queuedelete.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_queuedelete.cpp @@ -62,12 +62,12 @@ bool QueueDelete::decode(QueueDelete* queueDelete, } buffer.skip(sizeof(uint16_t)); + uint8_t bitmask = 0; if (!Types::decodeShortString(&queueDelete->d_name, &buffer) || - buffer.available() < sizeof(uint8_t)) { + !buffer.copy(&bitmask)) { return false; } - uint8_t bitmask = buffer.copy(); queueDelete->d_ifUnused = bitmask & BITMASK_BIT_0; queueDelete->d_ifEmpty = bitmask & BITMASK_BIT_1; queueDelete->d_noWait = bitmask & BITMASK_BIT_2; diff --git a/src/rmq/rmqamqpt/rmqamqpt_queuedeleteok.cpp b/src/rmq/rmqamqpt/rmqamqpt_queuedeleteok.cpp index 932522be..b4d3f6df 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_queuedeleteok.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_queuedeleteok.cpp @@ -40,13 +40,7 @@ bool QueueDeleteOk::decode(QueueDeleteOk* deleteOk, rmqamqpt::Buffer buffer(data, dataLength); - if (buffer.available() < sizeof(deleteOk->d_messageCount)) { - return false; - } - - deleteOk->d_messageCount = buffer.copy(); - - return true; + return buffer.copy(&deleteOk->d_messageCount); } void QueueDeleteOk::encode(Writer& output, const QueueDeleteOk& deleteOk) diff --git a/src/rmq/rmqamqpt/rmqamqpt_types.cpp b/src/rmq/rmqamqpt/rmqamqpt_types.cpp index 4fa4a532..f90ca5b2 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_types.cpp +++ b/src/rmq/rmqamqpt/rmqamqpt_types.cpp @@ -15,6 +15,7 @@ #include +#include #include #include @@ -44,14 +45,13 @@ bool decodeString(bsl::string* string, rmqamqpt::Buffer* buffer) BSLMF_ASSERT(sizeof(T) == sizeof(bsl::uint8_t) || sizeof(T) == sizeof(bsl::uint32_t)); - if (sizeof(T) > buffer->available()) { + T stringLength = T(); + if (!buffer->copy(&stringLength)) { BALL_LOG_ERROR << "Not enough data to read string length"; return false; } - const T stringLength = buffer->copy(); - if (stringLength > buffer->available()) { BALL_LOG_ERROR << "Not enough data to read string of length: " @@ -129,7 +129,6 @@ bool Types::decodeFieldValue(rmqt::FieldValue* outValue, { BSLS_ASSERT(outValue); - BSLMF_ASSERT(sizeof(bool) == 1); BSLMF_ASSERT(sizeof(bsl::int8_t) == 1); BSLMF_ASSERT(sizeof(bsl::uint8_t) == 1); @@ -154,66 +153,70 @@ bool Types::decodeFieldValue(rmqt::FieldValue* outValue, BSLMF_ASSERT(sizeof(float) == sizeof(bdlb::BigEndianUint32)); BSLMF_ASSERT(sizeof(double) == sizeof(bdlb::BigEndianUint64)); - if (buffer->available() < sizeof(bsl::uint8_t)) { + bsl::uint8_t typeByte = 0; + if (!buffer->copy(&typeByte)) { return false; } const rmqamqpt::FieldValue::Type type = - static_cast(buffer->copy()); + static_cast(typeByte); switch (type) { case rmqamqpt::FieldValue::BOOLEAN: { - if (buffer->available() < sizeof(bool)) { + bsl::uint8_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = rmqt::FieldValue(buffer->copy() != 0); + *outValue = rmqt::FieldValue(val != 0); } break; case rmqamqpt::FieldValue::SHORT_SHORT_INT: { - if (buffer->available() < sizeof(bsl::int8_t)) { + bsl::int8_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = static_cast(buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::SHORT_SHORT_UINT: { - if (buffer->available() < sizeof(bsl::uint8_t)) { + bsl::uint8_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = static_cast(buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::SHORT_INT: { - if (buffer->available() < sizeof(bdlb::BigEndianInt16)) { + bsl::int16_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = - static_cast(buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::SHORT_UINT: { - if (buffer->available() < sizeof(bdlb::BigEndianUint16)) { + bsl::uint16_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = static_cast( - buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::LONG_INT: { - if (buffer->available() < sizeof(bdlb::BigEndianInt32)) { + bsl::int32_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = - static_cast(buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::LONG_UINT: { - if (buffer->available() < sizeof(bdlb::BigEndianUint32)) { + bsl::uint32_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = static_cast( - buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::AMQP_SPEC_LONG_LONG_INT: { - if (buffer->available() < sizeof(bdlb::BigEndianInt64)) { + bsl::int64_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = - static_cast(buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::RABBIT_SPEC_LONG_LONG_INT: { // https://www.rabbitmq.com/amqp-0-9-1-errata.html @@ -223,35 +226,43 @@ bool Types::decodeFieldValue(rmqt::FieldValue* outValue, // support unsigned 64bit integers, like RabbitMQ. But we decode // both 'L' and 'l' to signed 64bit integers for internal backwards // compatibility reasons. - if (buffer->available() < sizeof(bdlb::BigEndianInt64)) { + bsl::int64_t val = 0; + if (!buffer->copy(&val)) { return false; } - *outValue = - static_cast(buffer->copy()); + *outValue = val; } break; case rmqamqpt::FieldValue::FLOAT: { - if (buffer->available() < sizeof(bdlb::BigEndianUint32)) { + bsl::uint32_t temp = 0; + if (!buffer->copy(&temp)) { return false; } - const bsl::uint32_t temp = static_cast( - buffer->copy()); float floatValue; bsl::memcpy(&floatValue, &temp, sizeof(floatValue)); *outValue = floatValue; } break; case rmqamqpt::FieldValue::DOUBLE: { - if (buffer->available() < sizeof(bdlb::BigEndianUint64)) { + bsl::uint64_t temp = 0; + if (!buffer->copy(&temp)) { return false; } - const bsl::uint64_t temp = static_cast( - buffer->copy()); double doubleValue; bsl::memcpy(&doubleValue, &temp, sizeof(doubleValue)); *outValue = doubleValue; } break; case rmqamqpt::FieldValue::DECIMAL: { + // TODO Handle Decimal. Consume the field so that whatever + // follows it in the table still decodes. The value is left + // unset, which is indistinguishable from a NO_FIELD ('V') + // value, so re-encoding these headers writes the field back + // out as 'V' and the decimal is lost. + if (buffer->available() < Constants::DECIMAL_OCTETS) { + return false; + } + buffer->skip(Constants::DECIMAL_OCTETS); + BALL_LOG_ERROR << "Received unsupported decimal type"; - // TODO Handle Decimal + outValue->reset(); } break; case rmqamqpt::FieldValue::LONG_STRING: { bsl::string val; @@ -261,11 +272,10 @@ bool Types::decodeFieldValue(rmqt::FieldValue* outValue, *outValue = val; } break; case rmqamqpt::FieldValue::BYTE_ARRAY: { - if (buffer->available() < sizeof(bdlb::BigEndianUint32)) { + bsl::uint32_t arraySize = 0; + if (!buffer->copy(&arraySize)) { return false; } - const bsl::uint32_t arraySize = static_cast( - buffer->copy()); bsl::vector val; if (!decodeByteVector(&val, buffer, arraySize)) { return false; @@ -282,7 +292,9 @@ bool Types::decodeFieldValue(rmqt::FieldValue* outValue, } break; case rmqamqpt::FieldValue::TIMESTAMP: { bdlt::Datetime timestamp; - decodeTimestamp(×tamp, buffer); + if (!decodeTimestamp(×tamp, buffer)) { + return false; + } *outValue = timestamp; } break; case rmqamqpt::FieldValue::FIELD_TABLE: { @@ -432,9 +444,17 @@ bool Types::decodeFieldArray(rmqt::FieldArray* fieldArray, { BSLS_ASSERT(fieldArray); - const bdlb::BigEndianUint32 arrayLength = - buffer->copy(); + bsl::uint32_t arrayLength = 0; + if (!buffer->copy(&arrayLength)) { + BALL_LOG_ERROR << "Not enough data to read field array size field, " + "available: " + << buffer->available(); + return false; + } + if (arrayLength > buffer->available()) { + BALL_LOG_ERROR << "Not enough data to read field array of size: " + << arrayLength << " available: " << buffer->available(); return false; } @@ -466,8 +486,16 @@ void Types::encodeFieldArray(Writer& output, const rmqt::FieldArray& v) bool Types::decodeFieldTable(rmqt::FieldTable* table, rmqamqpt::Buffer* buffer) { - const bdlb::BigEndianUint32 fieldTableLength = - buffer->copy(); + BSLS_ASSERT(table); + + bsl::uint32_t fieldTableLength = 0; + if (!buffer->copy(&fieldTableLength)) { + BALL_LOG_ERROR << "Not enough data to read table size field, " + "available: " + << buffer->available(); + return false; + } + if (fieldTableLength > buffer->available()) { BALL_LOG_ERROR << "Not enough data to read table size: " << fieldTableLength @@ -522,10 +550,24 @@ void Types::encodeTimestamp(Writer& output, const bdlt::Datetime& timestamp) bdlt::EpochUtil::convertToTimeT64(timestamp))); } -void Types::decodeTimestamp(bdlt::Datetime* timestamp, rmqamqpt::Buffer* buffer) +bool Types::decodeTimestamp(bdlt::Datetime* timestamp, rmqamqpt::Buffer* buffer) { - *timestamp = bdlt::EpochUtil::convertFromTimeT64( - buffer->copy()); + BSLS_ASSERT(timestamp); + + bdlt::EpochUtil::TimeT64 seconds = 0; + if (!buffer->copy(&seconds)) { + BALL_LOG_ERROR << "Not enough data to read timestamp, available: " + << buffer->available(); + return false; + } + + if (0 != bdlt::EpochUtil::convertFromTimeT64(timestamp, seconds)) { + BALL_LOG_ERROR << "Timestamp not representable as a datetime: " + << seconds; + return false; + } + + return true; } } // namespace rmqamqpt diff --git a/src/rmq/rmqamqpt/rmqamqpt_types.h b/src/rmq/rmqamqpt/rmqamqpt_types.h index a7a7cc4a..55c092af 100644 --- a/src/rmq/rmqamqpt/rmqamqpt_types.h +++ b/src/rmq/rmqamqpt/rmqamqpt_types.h @@ -75,7 +75,7 @@ class Types { static void encodeTimestamp(Writer& output, const bdlt::Datetime& timestamp); - static void decodeTimestamp(bdlt::Datetime* timestamp, + static bool decodeTimestamp(bdlt::Datetime* timestamp, rmqamqpt::Buffer* buffer); template diff --git a/src/tests/rmqamqpt/rmqamqpt_basicack.t.cpp b/src/tests/rmqamqpt/rmqamqpt_basicack.t.cpp index 66fb7cf5..e7ca7b75 100644 --- a/src/tests/rmqamqpt/rmqamqpt_basicack.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_basicack.t.cpp @@ -21,6 +21,7 @@ #include #include +#include #include using namespace BloombergLP; @@ -51,3 +52,40 @@ TEST(Methods_BasicAck, AckEncodeDecode) EXPECT_EQ(bm.the().deliveryTag(), 1337); EXPECT_EQ(bm.the().multiple(), true); } + +TEST(Methods_BasicAck, TruncatedPayloadFailsToDecode) +{ + // A full Basic.Ack payload is an 8 byte delivery tag plus a flag byte + const uint8_t full[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x01}; + + for (bsl::size_t length = 0; length < sizeof(full); ++length) { + rmqamqpt::BasicAck ack; + + EXPECT_FALSE(rmqamqpt::BasicAck::decode(&ack, full, length)) + << "length: " << length; + } + + rmqamqpt::BasicAck ack; + EXPECT_TRUE(rmqamqpt::BasicAck::decode(&ack, full, sizeof(full))); + EXPECT_EQ(ack.deliveryTag(), 1337); + EXPECT_TRUE(ack.multiple()); +} + +TEST(Methods_BasicAck, MultipleIsReadFromBitZeroOnly) +{ + // multiple is bit 0 of a packed bit octet; the rest are reserved + const uint8_t cases[][2] = { + {0x00, 0}, {0x01, 1}, {0x02, 0}, {0x03, 1}, {0xFE, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + const uint8_t data[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x39, cases[i][0]}; + + rmqamqpt::BasicAck ack; + EXPECT_TRUE(rmqamqpt::BasicAck::decode(&ack, data, sizeof(data))); + EXPECT_EQ(ack.deliveryTag(), 1337); + EXPECT_EQ(static_cast(ack.multiple()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_basiccancel.t.cpp b/src/tests/rmqamqpt/rmqamqpt_basiccancel.t.cpp index 5fbeec89..59dcb451 100644 --- a/src/tests/rmqamqpt/rmqamqpt_basiccancel.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_basiccancel.t.cpp @@ -21,6 +21,7 @@ #include #include +#include #include using namespace BloombergLP; @@ -52,3 +53,18 @@ TEST(Methods_BasicCancel, CancelEncodeDecode) "test-consumer-tag"); EXPECT_EQ(bm.the().noWait(), false); } + +TEST(Methods_BasicCancel, NoWaitIsReadFromBitZeroOnly) +{ + const uint8_t cases[][2] = {{0x00, 0}, {0x01, 1}, {0x02, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + // empty consumer tag, then the packed bit octet + const uint8_t data[] = {0x00, cases[i][0]}; + + rmqamqpt::BasicCancel cancel; + EXPECT_TRUE(rmqamqpt::BasicCancel::decode(&cancel, data, sizeof(data))); + EXPECT_EQ(static_cast(cancel.noWait()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_basicdeliver.t.cpp b/src/tests/rmqamqpt/rmqamqpt_basicdeliver.t.cpp index c7a5f991..99a93ed4 100644 --- a/src/tests/rmqamqpt/rmqamqpt_basicdeliver.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_basicdeliver.t.cpp @@ -21,6 +21,7 @@ #include #include +#include #include using namespace BloombergLP; @@ -61,3 +62,32 @@ TEST(Methods_BasicDeliver, DeliverEncodeDecode) EXPECT_EQ(bm.the().redelivered(), true); } + +TEST(Methods_BasicDeliver, RedeliveredIsReadFromBitZeroOnly) +{ + const uint8_t cases[][2] = {{0x00, 0}, {0x01, 1}, {0x02, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + // empty consumer tag, delivery tag 1337, the packed bit octet, + // then empty exchange and routing key + const uint8_t data[] = {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x39, + cases[i][0], + 0x00, + 0x00}; + + rmqamqpt::BasicDeliver deliver; + EXPECT_TRUE( + rmqamqpt::BasicDeliver::decode(&deliver, data, sizeof(data))); + EXPECT_EQ(deliver.deliveryTag(), 1337); + EXPECT_EQ(static_cast(deliver.redelivered()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_basicproperties.t.cpp b/src/tests/rmqamqpt/rmqamqpt_basicproperties.t.cpp index b2081453..d1570132 100644 --- a/src/tests/rmqamqpt/rmqamqpt_basicproperties.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_basicproperties.t.cpp @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -541,3 +542,48 @@ TEST(Methods_BasicProperties, FromMessage) EXPECT_FALSE(basicProps.userId()); EXPECT_FALSE(basicProps.appId()); } + +TEST(Methods_BasicProperties, TruncatedScalarPropertiesFailToDecode) +{ + // Property flags announcing a property, with none of its bytes present + const uint16_t flags[] = { + 0x2000, // headers + 0x1000, // delivery-mode + 0x0800, // priority + 0x0040, // timestamp + }; + + for (bsl::size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) { + const uint8_t data[] = {static_cast(flags[i] >> 8), + static_cast(flags[i] & 0xFF)}; + + rmqamqpt::BasicProperties basicProps; + EXPECT_THAT( + rmqamqpt::BasicProperties::decode(&basicProps, data, sizeof(data)), + Eq(false)) + << "flags: " << flags[i]; + } +} + +TEST(Methods_BasicProperties, TruncatedHeaderTableLengthFailsToDecode) +{ + // headers flag, with a partial field table length prefix + const uint8_t data[] = {0x20, 0x00, 0x00, 0x00, 0x00}; + + rmqamqpt::BasicProperties basicProps; + EXPECT_THAT( + rmqamqpt::BasicProperties::decode(&basicProps, data, sizeof(data)), + Eq(false)); +} + +TEST(Methods_BasicProperties, TruncatedFlagsFailToDecode) +{ + const uint8_t data[] = {0x20}; + + rmqamqpt::BasicProperties basicProps; + EXPECT_THAT(rmqamqpt::BasicProperties::decode(&basicProps, data, 0), + Eq(false)); + EXPECT_THAT( + rmqamqpt::BasicProperties::decode(&basicProps, data, sizeof(data)), + Eq(false)); +} diff --git a/src/tests/rmqamqpt/rmqamqpt_basicqos.t.cpp b/src/tests/rmqamqpt/rmqamqpt_basicqos.t.cpp index b8799624..06958117 100644 --- a/src/tests/rmqamqpt/rmqamqpt_basicqos.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_basicqos.t.cpp @@ -21,6 +21,7 @@ #include #include +#include #include using namespace BloombergLP; @@ -52,3 +53,31 @@ TEST(Methods_BasicQoS, QoSEncodeDecode) EXPECT_EQ(bm.the().prefetchSize(), 0xb19); EXPECT_EQ(bm.the().global(), true); } + +TEST(Methods_BasicQoS, GlobalIsReadFromBitZeroOnly) +{ + const uint8_t cases[][2] = {{0x00, 0}, {0x01, 1}, {0x02, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + const uint8_t data[] = { + 0x00, 0x00, 0x00, 0x0A, 0x00, 0x14, cases[i][0]}; + + rmqamqpt::BasicQoS qos; + EXPECT_TRUE(rmqamqpt::BasicQoS::decode(&qos, data, sizeof(data))); + EXPECT_EQ(qos.prefetchSize(), 10); + EXPECT_EQ(qos.prefetchCount(), 20); + EXPECT_EQ(static_cast(qos.global()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} + +TEST(Methods_BasicQoS, TruncatedPayloadFailsToDecode) +{ + const uint8_t full[] = {0x00, 0x00, 0x00, 0x0A, 0x00, 0x14, 0x01}; + + for (bsl::size_t length = 0; length < sizeof(full); ++length) { + rmqamqpt::BasicQoS qos; + EXPECT_FALSE(rmqamqpt::BasicQoS::decode(&qos, full, length)) + << "length: " << length; + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_buffer.t.cpp b/src/tests/rmqamqpt/rmqamqpt_buffer.t.cpp index bbeaf57b..19bbb9b6 100644 --- a/src/tests/rmqamqpt/rmqamqpt_buffer.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_buffer.t.cpp @@ -15,6 +15,10 @@ #include +#include + +#include + #include using namespace BloombergLP; @@ -32,9 +36,67 @@ TEST(Buffer, CopyByte) const uint8_t buf[] = {'H', 'E', 'L', 'L', 'O'}; Buffer b(buf, sizeof(buf)); EXPECT_EQ(b.available(), sizeof(buf)); - char first = b.copy(); + char first = 0; + EXPECT_TRUE(b.copy(&first)); EXPECT_EQ(first, 'H'); b.skip(3); - EXPECT_EQ('O', b.copy()); + char last = 0; + EXPECT_TRUE(b.copy(&last)); + EXPECT_EQ('O', last); + EXPECT_EQ(b.available(), 0); +} + +TEST(Buffer, CopyConvertsFromWireByteOrder) +{ + const uint8_t buf[] = {0x01, 0x02, 0x03, 0x04}; + Buffer b(buf, sizeof(buf)); + + bsl::uint32_t value = 0; + EXPECT_TRUE(b.copy(&value)); + EXPECT_EQ(value, 0x01020304u); + EXPECT_EQ(b.available(), 0); +} + +TEST(Buffer, CopyPastEndFailsAndDoesNotAdvance) +{ + const uint8_t buf[] = {0xDE, 0xAD, 0xBE}; + Buffer b(buf, sizeof(buf)); + + bsl::uint32_t tooBig = 0x5A5A5A5A; + EXPECT_FALSE(b.copy(&tooBig)); + + // value and read position are both left untouched + EXPECT_EQ(tooBig, 0x5A5A5A5Au); + EXPECT_EQ(b.available(), sizeof(buf)); + + bsl::uint8_t fits = 0; + EXPECT_TRUE(b.copy(&fits)); + EXPECT_EQ(fits, 0xDE); + EXPECT_EQ(b.available(), 2); +} + +TEST(Buffer, CopyFromEmptyBufferFails) +{ + Buffer b(NULL, 0); + + char value = 'x'; + EXPECT_FALSE(b.copy(&value)); + EXPECT_EQ(value, 'x'); EXPECT_EQ(b.available(), 0); } + +TEST(Buffer, ConsumeIsTruncatedToAvailable) +{ + const uint8_t buf[] = {'H', 'E', 'L', 'L', 'O'}; + Buffer b(buf, sizeof(buf)); + b.skip(2); + + Buffer consumed = b.consume(100); + + EXPECT_EQ(consumed.available(), 3); + EXPECT_EQ(b.available(), 0); + + char first = 0; + EXPECT_TRUE(consumed.copy(&first)); + EXPECT_EQ('L', first); +} diff --git a/src/tests/rmqamqpt/rmqamqpt_channelflow.t.cpp b/src/tests/rmqamqpt/rmqamqpt_channelflow.t.cpp index 28317e9f..4f2e5db3 100644 --- a/src/tests/rmqamqpt/rmqamqpt_channelflow.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_channelflow.t.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include #include @@ -55,3 +57,17 @@ TEST(ChannelFlow, FlowEncodeDecode) EXPECT_TRUE(cm.is()); EXPECT_EQ(cm.the().activeFlow(), 1); } + +TEST(Methods_ChannelFlow, ActiveIsReadFromBitZeroOnly) +{ + const uint8_t cases[][2] = {{0x00, 0}, {0x01, 1}, {0x02, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + const uint8_t data[] = {cases[i][0]}; + + rmqamqpt::ChannelFlow flow; + EXPECT_TRUE(rmqamqpt::ChannelFlow::decode(&flow, data, sizeof(data))); + EXPECT_EQ(static_cast(flow.activeFlow()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_channelflowok.t.cpp b/src/tests/rmqamqpt/rmqamqpt_channelflowok.t.cpp index f50b66fd..66e58f9f 100644 --- a/src/tests/rmqamqpt/rmqamqpt_channelflowok.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_channelflowok.t.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include #include @@ -55,3 +57,18 @@ TEST(ChannelFlowOk, FlowOkEncodeDecode) EXPECT_TRUE(cm.is()); EXPECT_EQ(cm.the().activeFlow(), 1); } + +TEST(Methods_ChannelFlowOk, ActiveIsReadFromBitZeroOnly) +{ + const uint8_t cases[][2] = {{0x00, 0}, {0x01, 1}, {0x02, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + const uint8_t data[] = {cases[i][0]}; + + rmqamqpt::ChannelFlowOk flowOk; + EXPECT_TRUE( + rmqamqpt::ChannelFlowOk::decode(&flowOk, data, sizeof(data))); + EXPECT_EQ(static_cast(flowOk.activeFlow()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_confirmselect.t.cpp b/src/tests/rmqamqpt/rmqamqpt_confirmselect.t.cpp index a2c099a8..8eff431c 100644 --- a/src/tests/rmqamqpt/rmqamqpt_confirmselect.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_confirmselect.t.cpp @@ -51,3 +51,11 @@ TEST(Methods_ConfirmSelect, QoSEncodeDecode) EXPECT_TRUE(bm.is()); EXPECT_TRUE(bm.the().noWait()); } + +TEST(Methods_ConfirmSelect, EmptyPayloadFailsToDecode) +{ + rmqamqpt::ConfirmSelect confirmSelect; + const uint8_t data[] = {0x00}; + + EXPECT_FALSE(rmqamqpt::ConfirmSelect::decode(&confirmSelect, data, 0)); +} diff --git a/src/tests/rmqamqpt/rmqamqpt_exchangebind.t.cpp b/src/tests/rmqamqpt/rmqamqpt_exchangebind.t.cpp index 6fa4459e..2fe781eb 100644 --- a/src/tests/rmqamqpt/rmqamqpt_exchangebind.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_exchangebind.t.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -64,3 +65,20 @@ TEST(Methods_Exchange_Bind, ExchangeBindEncodeDecode) ASSERT_TRUE(qm.is()); EXPECT_EQ(exchangeBindMethod, qm.the()); } + +TEST(Methods_ExchangeBind, NoWaitIsReadFromBitZeroOnly) +{ + const uint8_t cases[][2] = {{0x00, 0}, {0x01, 1}, {0x02, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + // reserved short, three empty short strings, the packed bit octet, + // then an empty field table + const uint8_t data[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, cases[i][0], 0x00, 0x00, 0x00, 0x00}; + + rmqamqpt::ExchangeBind bind; + EXPECT_TRUE(rmqamqpt::ExchangeBind::decode(&bind, data, sizeof(data))); + EXPECT_EQ(static_cast(bind.noWait()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_queuebind.t.cpp b/src/tests/rmqamqpt/rmqamqpt_queuebind.t.cpp index 6d3f6a71..2adec40a 100644 --- a/src/tests/rmqamqpt/rmqamqpt_queuebind.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_queuebind.t.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -63,3 +64,20 @@ TEST(Methods_Queue_Bind, QueueBindEncodeDecode) ASSERT_TRUE(qm.is()); EXPECT_EQ(queueBindMethod, qm.the()); } + +TEST(Methods_QueueBind, NoWaitIsReadFromBitZeroOnly) +{ + const uint8_t cases[][2] = {{0x00, 0}, {0x01, 1}, {0x02, 0}, {0xFF, 1}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + // reserved short, three empty short strings, the packed bit octet, + // then an empty field table + const uint8_t data[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, cases[i][0], 0x00, 0x00, 0x00, 0x00}; + + rmqamqpt::QueueBind bind; + EXPECT_TRUE(rmqamqpt::QueueBind::decode(&bind, data, sizeof(data))); + EXPECT_EQ(static_cast(bind.noWait()), cases[i][1]) + << "octet: " << static_cast(cases[i][0]); + } +} diff --git a/src/tests/rmqamqpt/rmqamqpt_types.t.cpp b/src/tests/rmqamqpt/rmqamqpt_types.t.cpp index c1e1c0ec..11453cae 100644 --- a/src/tests/rmqamqpt/rmqamqpt_types.t.cpp +++ b/src/tests/rmqamqpt/rmqamqpt_types.t.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -610,11 +611,154 @@ TEST(TypesEncoding, Timestamp) bdlt::Datetime decoded; rmqamqpt::Buffer buf(storage.data(), storage.size()); - rmqamqpt::Types::decodeTimestamp(&decoded, &buf), Eq(true); + EXPECT_THAT(rmqamqpt::Types::decodeTimestamp(&decoded, &buf), Eq(true)); EXPECT_THAT(decoded, Eq(millennium)); } +TEST(TypesTruncatedDecode, TruncatedTimestampFails) +{ + const uint8_t truncated[] = {0x00, 0x00, 0x00, 0x00, 0x38, 0x6D, 0x43}; + + for (bsl::size_t length = 0; length < sizeof(truncated); ++length) { + bdlt::Datetime decoded; + rmqamqpt::Buffer buffer(truncated, length); + + EXPECT_THAT(rmqamqpt::Types::decodeTimestamp(&decoded, &buffer), + Eq(false)) + << "length: " << length; + EXPECT_THAT(buffer.available(), Eq(length)); + } +} + +TEST(TypesTruncatedDecode, OutOfRangeTimestampFails) +{ + // Seconds since the epoch that no bdlt::Datetime can represent + const uint8_t cases[][8] = { + {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + + for (bsl::size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + bdlt::Datetime decoded; + rmqamqpt::Buffer buffer(cases[i], sizeof(cases[i])); + + EXPECT_THAT(rmqamqpt::Types::decodeTimestamp(&decoded, &buffer), + Eq(false)) + << "case: " << i; + } +} + +TEST(TypesTruncatedDecode, OutOfRangeTimestampFieldValueFails) +{ + const uint8_t data[] = { + 'T', 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + rmqt::FieldValue value; + rmqamqpt::Buffer buffer(data, sizeof(data)); + + EXPECT_THAT(rmqamqpt::Types::decodeFieldValue(&value, &buffer), Eq(false)); +} + +TEST(TypesFieldTableDecode, DecimalIsSkippedAndLaterFieldsStillDecode) +{ + // A decimal is not supported, but its 5 octets must still be consumed + // so that whatever follows it in the table is read from the right place. + const uint8_t table[] = { + 0x00, 0x00, 0x00, 0x10, // table length + 0x05, 'p', 'r', 'i', 'c', 'e', // "price" + 'D', 0x02, 0x00, 0x00, 0x00, 0x63, // decimal, scale 2, 99 + 0x01, 'x', // "x" + 'b', 0x07}; // short short int, 7 + + rmqamqpt::Buffer buffer(table, sizeof(table)); + rmqt::FieldTable ft; + + EXPECT_THAT(rmqamqpt::Types::decodeFieldTable(&ft, &buffer), Eq(true)); + EXPECT_THAT(ft.size(), Eq(2u)); + + EXPECT_TRUE(ft["price"].isUnset()); + + EXPECT_TRUE(ft["x"].is()); + EXPECT_THAT(ft["x"].the(), Eq(7)); +} + +TEST(TypesTruncatedDecode, TruncatedDecimalFails) +{ + const uint8_t truncated[] = {'D', 0x02, 0x00, 0x00, 0x00}; + + for (bsl::size_t length = 1; length < sizeof(truncated); ++length) { + rmqt::FieldValue value; + rmqamqpt::Buffer buffer(truncated, length); + + EXPECT_THAT(rmqamqpt::Types::decodeFieldValue(&value, &buffer), + Eq(false)) + << "length: " << length; + } +} + +TEST(TypesTruncatedDecode, TruncatedFieldTableLengthPrefixFails) +{ + const uint8_t truncated[] = {0x00, 0x00, 0x00}; + + for (bsl::size_t length = 0; length < sizeof(truncated); ++length) { + rmqt::FieldTable table; + rmqamqpt::Buffer buffer(truncated, length); + + EXPECT_THAT(rmqamqpt::Types::decodeFieldTable(&table, &buffer), + Eq(false)) + << "length: " << length; + } +} + +TEST(TypesTruncatedDecode, TruncatedFieldArrayLengthPrefixFails) +{ + const uint8_t truncated[] = {0x00, 0x00, 0x00}; + + for (bsl::size_t length = 0; length < sizeof(truncated); ++length) { + rmqt::FieldArray array; + rmqamqpt::Buffer buffer(truncated, length); + + EXPECT_THAT(rmqamqpt::Types::decodeFieldArray(&array, &buffer), + Eq(false)) + << "length: " << length; + } +} + +TEST(TypesTruncatedDecode, TruncatedFieldValuePayloadFails) +{ + // Every field value type header, with no payload bytes following it + const bsl::string types("tbBsuIiLlfdDSxATF"); + + for (bsl::size_t i = 0; i < types.size(); ++i) { + const uint8_t type = static_cast(types[i]); + + rmqt::FieldValue value; + rmqamqpt::Buffer buffer(&type, sizeof(type)); + + EXPECT_THAT(rmqamqpt::Types::decodeFieldValue(&value, &buffer), + Eq(false)) + << "type: " << types[i]; + } +} + +TEST(TypesTruncatedDecode, TruncatedStringLengthPrefixFails) +{ + const uint8_t truncated[] = {0x00, 0x00, 0x00}; + + bsl::string decoded; + rmqamqpt::Buffer empty(truncated, 0); + EXPECT_THAT(rmqamqpt::Types::decodeShortString(&decoded, &empty), + Eq(false)); + + for (bsl::size_t length = 0; length < sizeof(truncated); ++length) { + rmqamqpt::Buffer buffer(truncated, length); + + EXPECT_THAT(rmqamqpt::Types::decodeLongString(&decoded, &buffer), + Eq(false)) + << "length: " << length; + } +} + rmqt::FieldValue roundTripFieldValue(bool* decodeResult, const rmqt::FieldValue& fv) {