diff --git a/include/boost/mysql/detail/protocol/impl/binary_deserialization.ipp b/include/boost/mysql/detail/protocol/impl/binary_deserialization.ipp index 39cb4a8b..b40c95cf 100644 --- a/include/boost/mysql/detail/protocol/impl/binary_deserialization.ipp +++ b/include/boost/mysql/detail/protocol/impl/binary_deserialization.ipp @@ -168,24 +168,14 @@ inline errc deserialize_binary_value_to_variant_datetime( if (err != errc::ok) return err; - // Check for zero datetimes, represented as NULL in C++ - if (length.value < datetime_d_sz) + // Deserialize date. If the DATETIME does not contain these values, + // they are supposed to be zero (invalid date) + ::date::year_month_day ymd (::date::year(0), ::date::month(0), ::date::day(0)); + if (length.value >= datetime_d_sz) { - output = nullptr; - return errc::ok; - } - - // Deserialize date - ::date::year_month_day ymd; - err = deserialize_binary_ymd(ctx, ymd); - if (err != errc::ok) - return err; - - // Check for invalid dates, represented in C++ as NULL - if (!ymd.ok()) - { - output = nullptr; - return errc::ok; + err = deserialize_binary_ymd(ctx, ymd); + if (err != errc::ok) + return err; } // If the DATETIME contains no value for these fields, they are zero @@ -210,6 +200,15 @@ inline errc deserialize_binary_value_to_variant_datetime( return err; } + // Check for invalid dates, represented in C++ as NULL. + // Note: we do the check here to ensure we consume all the bytes + // associated to this datetime + if (!ymd.ok()) + { + output = nullptr; + return errc::ok; + } + // Range check date d (ymd); if (is_out_of_range(d) || diff --git a/test/unit/detail/protocol/binary_deserialization_value.cpp b/test/unit/detail/protocol/binary_deserialization_value.cpp index 87ec9a3e..8e4b1b4d 100644 --- a/test/unit/detail/protocol/binary_deserialization_value.cpp +++ b/test/unit/detail/protocol/binary_deserialization_value.cpp @@ -7,6 +7,7 @@ #include #include "boost/mysql/detail/protocol/binary_deserialization.hpp" +#include "boost/mysql/detail/auxiliar/stringize.hpp" #include "test_common.hpp" // Tests for deserialize_binary_value() @@ -144,25 +145,19 @@ INSTANTIATE_TEST_SUITE_P(DATE, DeserializeBinaryValueTest, ::testing::Values( makedate(9999, 12, 31), protocol_field_type::date), binary_value_testcase("zero", {0x00}, nullptr, protocol_field_type::date), - binary_value_testcase("zero_invalid_month", {0x04, 0x00, 0x00, 13, 0x01}, + binary_value_testcase("invalid_month", {0x04, 0x00, 0x00, 13, 0x01}, nullptr, protocol_field_type::date), - binary_value_testcase("zero_invalid_month_max", {0x04, 0x00, 0x00, 0xff, 0x01}, + binary_value_testcase("invalid_month_max", {0x04, 0x00, 0x00, 0xff, 0x01}, nullptr, protocol_field_type::date), - binary_value_testcase("zero_invalid_day", {0x04, 0x00, 0x00, 0x01, 32}, + binary_value_testcase("invalid_month_min", {0x04, 0x00, 0x00, 0x00, 0x01}, nullptr, protocol_field_type::date), - binary_value_testcase("zero_invalid_day_max", {0x04, 0x00, 0x00, 0x01, 0xff}, + binary_value_testcase("invalid_day", {0x04, 0x00, 0x00, 0x01, 32}, nullptr, protocol_field_type::date), - binary_value_testcase("zero_invalid_date", {0x04, 0x00, 0x00, 31, 4}, + binary_value_testcase("invalid_day_max", {0x04, 0x00, 0x00, 0x01, 0xff}, nullptr, protocol_field_type::date), - binary_value_testcase("nonzero_invalid_month", {0x04, 0xe4, 0x07, 13, 0x01}, + binary_value_testcase("invalid_day_min", {0x04, 0x00, 0x00, 0x01, 0x00}, nullptr, protocol_field_type::date), - binary_value_testcase("nonzero_invalid_month_max", {0x04, 0xe4, 0x07, 0xff, 0x01}, - nullptr, protocol_field_type::date), - binary_value_testcase("nonzero_invalid_day", {0x04, 0xe4, 0x07, 0x01, 32}, - nullptr, protocol_field_type::date), - binary_value_testcase("nonzero_invalid_day_max", {0x04, 0xe4, 0x07, 0x01, 0xff}, - nullptr, protocol_field_type::date), - binary_value_testcase("nonzero_invalid_date", {0x04, 0xe4, 0x07, 31, 4}, + binary_value_testcase("invalid_date", {0x04, 0x00, 0x00, 31, 4}, nullptr, protocol_field_type::date) ), test_name_generator); @@ -170,7 +165,7 @@ std::vector make_datetime_cases( protocol_field_type type ) { - return { + std::vector res { { "only_date", {0x04, 0xda, 0x07, 0x01, 0x01}, makedt(2010, 1, 1), type }, { "date_h", {0x07, 0xda, 0x07, 0x01, 0x01, 0x14, 0x00, 0x00}, @@ -203,7 +198,64 @@ std::vector make_datetime_cases( makedt(2010, 1, 1, 23, 0, 59, 967510), type }, { "date_hmsu", {0x0b, 0xda, 0x07, 0x01, 0x01, 0x17, 0x01, 0x3b, 0x56, 0xc3, 0x0e, 0x00}, makedt(2010, 1, 1, 23, 1, 59, 967510), type }, + { "zeros", {0x00}, nullptr, type }, + { "zeros_d", {0x04, 0x00, 0x00, 0x00, 0x00}, nullptr, type }, + { "zeros_hms", {0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, nullptr, type }, + { "zeros_hmsu", {0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, nullptr, type }, }; + + // Create all the casuistic for invalid datetimes. We consider three factors: + // which member is invalid, why is it invalid, and which length does the datetime have. + constexpr struct + { + const char* name; + std::size_t pos; + std::uint8_t invalid_value; + } what_is_invalid [] = { + { "month", 3, 13 }, + { "day", 4, 32 } + }; + + constexpr struct + { + const char* name; + int value; + } why_is_invalid [] = { + { "zero", 0 }, + { "protocolmax", 0xff }, + { "gtmax", -1 } // means "look inside what_is_invalid" + }; + + constexpr struct + { + const char* name; + std::uint8_t length; + } lengths [] = { + { "d", 4 }, + { "hms", 7 }, + { "hmsu", 11 } + }; + + bytestring regular {0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + for (const auto& what: what_is_invalid) + { + for (const auto& why: why_is_invalid) + { + for (const auto& len: lengths) + { + std::string name = stringize("invalid_", what.name, "_", why.name, "_", len.name); + std::uint8_t invalid_value = why.value == -1 ? what.invalid_value : why.value; + bytestring buffer (regular); + buffer[what.pos] = invalid_value; + buffer[0] = std::uint8_t(len.length); + buffer.resize(len.length + 1); + res.emplace_back(std::move(name), std::move(buffer), nullptr, type); + } + } + }; + + return res; } INSTANTIATE_TEST_SUITE_P(DATETIME, DeserializeBinaryValueTest, ValuesIn(