From aadd8213ad5b53bc6bdcd9ce6f78d9ce2941dbf0 Mon Sep 17 00:00:00 2001 From: ruben Date: Mon, 4 May 2020 20:21:28 +0100 Subject: [PATCH] Added binary deserialization error tests --- TODO.txt | 8 +- .../detail/protocol/impl/serialization.hpp | 4 + .../detail/protocol/impl/serialization.ipp | 6 + test/CMakeLists.txt | 1 + .../protocol/binary_deserialization_error.cpp | 146 ++++++++++++++++++ .../protocol/binary_deserialization_value.cpp | 1 + 6 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 test/unit/detail/protocol/binary_deserialization_error.cpp diff --git a/TODO.txt b/TODO.txt index 8adfe138..7b6d38c4 100644 --- a/TODO.txt +++ b/TODO.txt @@ -7,9 +7,14 @@ Sanitize Refactor binary deserialization constants Change text row to use parameterized tests Refactor deserialize_binary_value to have similar implementation to the text one + Better resilience of deserialization + Make float inf and nan output NULL? + Lift range check on dates, datetimes, times + Make invalid dates and datetimes output NULL + Integ tests for zero and invalid dates + Complete DATETIME and TIME error tests See if we have any trouble with user input in binary serialization: make assertions Test with an unknown protocol type - Test zero dates Random input tests Better docs Wandbox @@ -27,6 +32,7 @@ Handshake SSL certificate & common name validation sha256_password Usability + Operator<< and == for value and ADL Should make_error_code be public? Incomplete query reads: how does this affect further queries? Metadata in rows: being able to index by name diff --git a/include/boost/mysql/detail/protocol/impl/serialization.hpp b/include/boost/mysql/detail/protocol/impl/serialization.hpp index c4f08c66..371a80a5 100644 --- a/include/boost/mysql/detail/protocol/impl/serialization.hpp +++ b/include/boost/mysql/detail/protocol/impl/serialization.hpp @@ -8,6 +8,8 @@ #ifndef BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_SERIALIZATION_HPP #define BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_SERIALIZATION_HPP +#include + namespace boost { namespace mysql { namespace detail { @@ -268,6 +270,8 @@ boost::mysql::detail::serialization_traits< std::memcpy(&output, ctx.first(), sizeof(T)); #endif ctx.advance(sizeof(T)); + if (std::isnan(output) || std::isinf(output)) + return errc::protocol_value_error; return errc::ok; } diff --git a/include/boost/mysql/detail/protocol/impl/serialization.ipp b/include/boost/mysql/detail/protocol/impl/serialization.ipp index c451e8c0..ce9e9974 100644 --- a/include/boost/mysql/detail/protocol/impl/serialization.ipp +++ b/include/boost/mysql/detail/protocol/impl/serialization.ipp @@ -41,7 +41,13 @@ inline errc deserialize_binary_date( // TODO: how does this handle zero dates? ::date::year_month_day ymd (::date::year(year.value), ::date::month(month.value), ::date::day(day.value)); + if (!ymd.ok()) + return errc::protocol_value_error; + output = date(ymd); + if (output < min_date || output > max_date) + return errc::protocol_value_error; + return errc::ok; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d0a80956..8832bd6a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable( unit/detail/protocol/text_deserialization_error.cpp unit/detail/protocol/text_deserialization_row.cpp unit/detail/protocol/binary_deserialization_value.cpp + unit/detail/protocol/binary_deserialization_error.cpp unit/detail/protocol/binary_deserialization_row.cpp unit/detail/protocol/null_bitmap_traits.cpp unit/metadata.cpp diff --git a/test/unit/detail/protocol/binary_deserialization_error.cpp b/test/unit/detail/protocol/binary_deserialization_error.cpp new file mode 100644 index 00000000..33f24056 --- /dev/null +++ b/test/unit/detail/protocol/binary_deserialization_error.cpp @@ -0,0 +1,146 @@ +// +// Copyright (c) 2019-2020 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test deserialize_binary_value(), only error cases + +#include +#include "boost/mysql/detail/protocol/binary_deserialization.hpp" +#include "test_common.hpp" + +using namespace boost::mysql::detail; +using namespace boost::mysql::test; +using namespace testing; +using boost::mysql::value; +using boost::mysql::error_code; +using boost::mysql::errc; + +namespace +{ + +struct err_binary_value_testcase : named_param +{ + std::string name; + bytestring from; + protocol_field_type type; + std::uint16_t flags; + errc expected_err; + + err_binary_value_testcase(std::string&& name, bytestring&& from, protocol_field_type type, + std::uint16_t flags=0, errc expected_err=errc::protocol_value_error) : + name(std::move(name)), + from(std::move(from)), + type(type), + flags(flags), + expected_err(expected_err) + { + } + + err_binary_value_testcase(std::string&& name, bytestring&& from, protocol_field_type type, + errc expected_err) : + name(std::move(name)), + from(std::move(from)), + type(type), + flags(0), + expected_err(expected_err) + { + } +}; + +struct DeserializeBinaryValueErrorTest : TestWithParam +{ +}; + +TEST_P(DeserializeBinaryValueErrorTest, Error_ReturnsExpectedErrc) +{ + column_definition_packet coldef; + coldef.type = GetParam().type; + coldef.flags.value = GetParam().flags; + boost::mysql::field_metadata meta (coldef); + value actual_value; + const auto& buff = GetParam().from; + deserialization_context ctx (buff.data(), buff.data() + buff.size(), capabilities()); + auto err = deserialize_binary_value(ctx, meta, actual_value); + auto expected = GetParam().expected_err; + EXPECT_EQ(expected, err) + << "expected: " << error_to_string(expected) << ", actual: " << error_to_string(err); +} + +std::vector make_int_cases( + protocol_field_type type, + unsigned num_bytes +) +{ + return { + { "signed_not_enough_space", bytestring(num_bytes, 0x0a), + type, errc::incomplete_message }, + { "unsigned_not_enough_space", bytestring(num_bytes, 0x0a), + type, column_flags::unsigned_, errc::incomplete_message } + }; +} + + +INSTANTIATE_TEST_SUITE_P(TINY, DeserializeBinaryValueErrorTest, ValuesIn( + make_int_cases(protocol_field_type::tiny, 0) +), test_name_generator); + +INSTANTIATE_TEST_SUITE_P(SMALLINT, DeserializeBinaryValueErrorTest, ValuesIn( + make_int_cases(protocol_field_type::short_, 1) +), test_name_generator); + +INSTANTIATE_TEST_SUITE_P(MEDIUMINT, DeserializeBinaryValueErrorTest, ValuesIn( + make_int_cases(protocol_field_type::int24, 3) +), test_name_generator); + +INSTANTIATE_TEST_SUITE_P(INT, DeserializeBinaryValueErrorTest, ValuesIn( + make_int_cases(protocol_field_type::long_, 3) +), test_name_generator); + +INSTANTIATE_TEST_SUITE_P(BIGINT, DeserializeBinaryValueErrorTest, ValuesIn( + make_int_cases(protocol_field_type::longlong, 7) +), test_name_generator); + +INSTANTIATE_TEST_SUITE_P(FLOAT, DeserializeBinaryValueErrorTest, Values( + err_binary_value_testcase("not_enough_space", {0x01, 0x02, 0x03}, + protocol_field_type::float_, errc::incomplete_message), + err_binary_value_testcase("inf", {0x00, 0x00, 0x80, 0x7f}, + protocol_field_type::float_), + err_binary_value_testcase("minus_inf", {0x00, 0x00, 0x80, 0xff}, + protocol_field_type::float_), + err_binary_value_testcase("nan", {0xff, 0xff, 0xff, 0x7f}, + protocol_field_type::float_), + err_binary_value_testcase("minus_nan", {0xff, 0xff, 0xff, 0xff}, + protocol_field_type::float_) +), test_name_generator); + +INSTANTIATE_TEST_SUITE_P(DOUBLE, DeserializeBinaryValueErrorTest, Values( + err_binary_value_testcase("not_enough_space", {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, + protocol_field_type::double_, errc::incomplete_message), + err_binary_value_testcase("inf", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f}, + protocol_field_type::double_), + err_binary_value_testcase("minus_inf", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff}, + protocol_field_type::double_), + err_binary_value_testcase("nan", {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + protocol_field_type::double_), + err_binary_value_testcase("minus_nan", {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + protocol_field_type::double_) +), test_name_generator); + +INSTANTIATE_TEST_SUITE_P(DATE, DeserializeBinaryValueErrorTest, Values( + err_binary_value_testcase("empty", {}, protocol_field_type::date, errc::incomplete_message), + err_binary_value_testcase("incomplete_year", {0x04, 0xff}, + protocol_field_type::date, errc::incomplete_message), + err_binary_value_testcase("year_gt_max", {0x04, 0x10, 0x27, 0x03, 0x1c}, // year 10000 + protocol_field_type::date), + err_binary_value_testcase("year_lt_min", {0x04, 0x63, 0x00, 0x03, 0x1c}, // year 99 + protocol_field_type::date) +)); + +INSTANTIATE_TEST_SUITE_P(YEAR, DeserializeBinaryValueErrorTest, ValuesIn( + make_int_cases(protocol_field_type::year, 1) +), test_name_generator); + +} // anon namespace diff --git a/test/unit/detail/protocol/binary_deserialization_value.cpp b/test/unit/detail/protocol/binary_deserialization_value.cpp index 12e7f566..93ae3944 100644 --- a/test/unit/detail/protocol/binary_deserialization_value.cpp +++ b/test/unit/detail/protocol/binary_deserialization_value.cpp @@ -64,6 +64,7 @@ TEST_P(DeserializeBinaryValueTest, CorrectFormat_SetsOutputValueReturnsTrue) auto err = deserialize_binary_value(ctx, meta, actual_value); EXPECT_EQ(err, errc::ok); EXPECT_EQ(actual_value, GetParam().expected); + EXPECT_EQ(ctx.first(), buffer.data() + buffer.size()); // all bytes consumed } INSTANTIATE_TEST_SUITE_P(StringTypes, DeserializeBinaryValueTest, Values(