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) {