diff --git a/sbe-tool/src/main/cpp/otf/OtfMessageDecoder.h b/sbe-tool/src/main/cpp/otf/OtfMessageDecoder.h index e84e51db3f..f3b94a34d3 100644 --- a/sbe-tool/src/main/cpp/otf/OtfMessageDecoder.h +++ b/sbe-tool/src/main/cpp/otf/OtfMessageDecoder.h @@ -1,5 +1,5 @@ /* - * Copyright 2013-2025 Real Logic Limited. + * Copyright 2013-2026 Real Logic Limited. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -303,7 +303,8 @@ std::size_t decodeData( bufferIndex += dataToken.offset(); } - if ((bufferIndex + dataLength) > length) + // bufferIndex <= length is established above, the subtraction cannot wrap whereas the addition can + if (dataLength > static_cast(length - bufferIndex)) { throw std::runtime_error("length too short for data field"); } diff --git a/sbe-tool/src/test/cpp/CMakeLists.txt b/sbe-tool/src/test/cpp/CMakeLists.txt index ef756b03c8..bd2c6adcd9 100644 --- a/sbe-tool/src/test/cpp/CMakeLists.txt +++ b/sbe-tool/src/test/cpp/CMakeLists.txt @@ -46,6 +46,7 @@ set(ACCESS_ORDER_SCHEMA ${CODEC_SCHEMA_DIR}/field-order-check-schema.xml) set(VERSIONED_MESSAGE_V1_SCHEMA ${CODEC_SCHEMA_DIR}/versioned-message-v1.xml) set(VERSIONED_MESSAGE_V2_SCHEMA ${CODEC_SCHEMA_DIR}/versioned-message-v2.xml) set(UINT64_LENGTH_SCHEMA ${CODEC_SCHEMA_DIR}/uint64-length-schema.xml) +set(UINT64_VAR_DATA_LENGTH_SCHEMA ${CODEC_SCHEMA_DIR}/uint64-var-data-length-schema.xml) set(GENERATED_CODECS ${CXX_CODEC_TARGET_DIR} @@ -67,6 +68,7 @@ add_custom_command( ${VERSIONED_MESSAGE_V1_SCHEMA} ${VERSIONED_MESSAGE_V2_SCHEMA} ${UINT64_LENGTH_SCHEMA} + ${UINT64_VAR_DATA_LENGTH_SCHEMA} sbe-jar ${SBE_JAR} COMMAND ${Java_JAVA_EXECUTABLE} --add-opens java.base/jdk.internal.misc=ALL-UNNAMED @@ -93,6 +95,7 @@ add_custom_command( ${VERSIONED_MESSAGE_V1_SCHEMA} ${VERSIONED_MESSAGE_V2_SCHEMA} ${UINT64_LENGTH_SCHEMA} + ${UINT64_VAR_DATA_LENGTH_SCHEMA} ) add_custom_target(codecs DEPENDS ${GENERATED_CODECS}) @@ -114,6 +117,7 @@ sbe_test(FieldAccessOrderCheckTest codecs) target_compile_definitions(FieldAccessOrderCheckTest PRIVATE SBE_ENABLE_PRECEDENCE_CHECKS_IN_TESTS) sbe_test(VersionedMessageTest codecs) sbe_test(Uint64LengthTest codecs) +sbe_test(OtfVarDataLengthTest codecs) # OtfMessageEncoderTest lives in the otf/ subdirectory and provides its own main() that takes the # IR file path as argv[1]; it is handed the generated code-generation-schema IR. diff --git a/sbe-tool/src/test/cpp/OtfVarDataLengthTest.cpp b/sbe-tool/src/test/cpp/OtfVarDataLengthTest.cpp new file mode 100644 index 0000000000..203c530611 --- /dev/null +++ b/sbe-tool/src/test/cpp/OtfVarDataLengthTest.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2026 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "otf/IrDecoder.h" +#include "otf/OtfMessageDecoder.h" +#include "WireBuffer.h" + +using namespace sbe::otf; + +static const char *IR_FILENAME = "uint64-var-data-length-schema.sbeir"; +static const int WIDE_LENGTH_TEMPLATE_ID = 1; + +class VarDataListener : public OtfMessageDecoder::BasicTokenListener +{ +public: + void onVarData(Token &, const char *, const std::uint64_t length, Token &) override + { + ++varDataCount; + lastLength = length; + } + + std::uint64_t varDataCount = 0; + std::uint64_t lastLength = 0; +}; + +class OtfVarDataLengthTest : public testing::Test +{ +protected: + void SetUp() override + { + ASSERT_EQ(m_ir.decode(IR_FILENAME), 0); + m_tokens = m_ir.message(WIDE_LENGTH_TEMPLATE_ID, 0); + ASSERT_TRUE(m_tokens); + } + + IrDecoder m_ir; + std::shared_ptr> m_tokens; +}; + +TEST_F(OtfVarDataLengthTest, shouldRejectUint64LengthThatWrapsBufferIndex) +{ + WireBuffer buffer; + buffer.u32(1).u64(std::numeric_limits::max()).bytes("abcd"); + VarDataListener listener; + + EXPECT_THROW( + OtfMessageDecoder::decode(buffer.data(), buffer.size(), 0, 4, m_tokens, listener), std::runtime_error); + EXPECT_EQ(listener.varDataCount, 0u); +} + +TEST_F(OtfVarDataLengthTest, shouldDecodeVarDataEndingAtBufferEnd) +{ + WireBuffer buffer; + buffer.u32(1).u64(4).bytes("abcd"); + VarDataListener listener; + + EXPECT_EQ(OtfMessageDecoder::decode(buffer.data(), buffer.size(), 0, 4, m_tokens, listener), buffer.size()); + EXPECT_EQ(listener.varDataCount, 1u); + EXPECT_EQ(listener.lastLength, 4u); +} diff --git a/sbe-tool/src/test/cpp/WireBuffer.h b/sbe-tool/src/test/cpp/WireBuffer.h new file mode 100644 index 0000000000..a26f88f05a --- /dev/null +++ b/sbe-tool/src/test/cpp/WireBuffer.h @@ -0,0 +1,81 @@ +/* + * Copyright 2026 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _SBE_TEST_WIRE_BUFFER_H +#define _SBE_TEST_WIRE_BUFFER_H + +#include +#include +#include + +/* + * Buffers are built byte by byte in little-endian order and sized exactly to the bytes written, so a read past + * the end of the buffer is a read past the end of the allocation. + */ +class WireBuffer +{ +public: + WireBuffer &u8(const std::uint8_t value) + { + m_bytes.push_back(static_cast(value)); + return *this; + } + + WireBuffer &u16(const std::uint16_t value) + { + return putLittleEndian(value, 2); + } + + WireBuffer &u32(const std::uint32_t value) + { + return putLittleEndian(value, 4); + } + + WireBuffer &u64(const std::uint64_t value) + { + return putLittleEndian(value, 8); + } + + WireBuffer &bytes(const std::string &value) + { + m_bytes.insert(m_bytes.end(), value.begin(), value.end()); + return *this; + } + + char *data() + { + return m_bytes.data(); + } + + std::uint64_t size() const + { + return m_bytes.size(); + } + +private: + WireBuffer &putLittleEndian(std::uint64_t value, const int length) + { + for (int i = 0; i < length; i++) + { + m_bytes.push_back(static_cast(value & 0xFF)); + value >>= 8; + } + return *this; + } + + std::vector m_bytes; +}; + +#endif diff --git a/sbe-tool/src/test/resources/uint64-var-data-length-schema.xml b/sbe-tool/src/test/resources/uint64-var-data-length-schema.xml new file mode 100644 index 0000000000..dffdb62807 --- /dev/null +++ b/sbe-tool/src/test/resources/uint64-var-data-length-schema.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + +