2
0
mirror of https://github.com/boostorg/mysql.git synced 2026-02-15 13:12:21 +00:00

Improved text deserializ error handling

Added more tests for text deserialization
Corrected some previous tests
Added code to handle some corner cases more gracefully
Refactored
This commit is contained in:
ruben
2020-05-03 15:23:40 +01:00
parent fa8c11d784
commit a35671a7ef
3 changed files with 197 additions and 65 deletions

View File

@@ -1,5 +1,13 @@
Sanitize
Test zero dates
Refactor text deserialization constants
Add text deserialization error tests
Refactor binary deserialization constants
Add binary deserialization error tests
Float: nan, inf, signalling nan
See if we have any trouble with user input in binary serialization
Test with an unknown protocol type
Random input tests
Better docs
Wandbox
Breaking up the tutorial in pieces
@@ -38,7 +46,6 @@ Other possible features
Status flags accessors in resultset (for OK_Packet)
Technical debt
Change channel read to make less syscalls
Random input tests
Test dashboard
Sanitizers
Integ tests for different default auth plugins

View File

@@ -17,25 +17,71 @@ namespace boost {
namespace mysql {
namespace detail {
// Text protocol deserialization constants
namespace textc {
constexpr unsigned max_decimals = 6u;
constexpr std::size_t year_sz = 4;
constexpr std::size_t month_sz = 2;
constexpr std::size_t day_sz = 2;
constexpr std::size_t hours_min_sz = 2; // in TIME, it may be longer
constexpr std::size_t mins_sz = 2;
constexpr std::size_t secs_sz = 2;
constexpr std::size_t date_sz = year_sz + month_sz + day_sz + 2; // delimiters
constexpr std::size_t time_min_sz = hours_min_sz + mins_sz + secs_sz + 2; // delimiters
constexpr std::size_t time_max_sz = time_min_sz + max_decimals + 3; // sign, period, hour extra character
constexpr std::size_t datetime_min_sz = date_sz + time_min_sz + 1; // delimiter
constexpr unsigned max_hour = 838;
constexpr unsigned max_min = 59;
constexpr unsigned max_sec = 59;
constexpr unsigned max_micro = 999999;
} // textc
inline unsigned sanitize_decimals(unsigned decimals)
{
return std::min(decimals, textc::max_decimals);
}
// Computes the meaning of the parsed microsecond number, taking into
// account decimals (85 with 2 decimals means 850000us)
inline unsigned compute_micros(unsigned parsed_micros, unsigned decimals)
{
return parsed_micros * static_cast<unsigned>(std::pow(10, textc::max_decimals - decimals));
}
inline errc deserialize_text_value_impl(
std::string_view from,
date& to
)
{
constexpr std::size_t size = 4 + 2 + 2 + 2; // year, month, day, separators
if (from.size() != size)
// Size check
if (from.size() != textc::date_sz)
return errc::protocol_value_error;
unsigned year, month, day;
char buffer [size + 1] {};
// Copy to a NULL-terminated buffer
char buffer [textc::date_sz + 1] {};
memcpy(buffer, from.data(), from.size());
// Parse individual components
unsigned year, month, day;
int parsed = sscanf(buffer, "%4u-%2u-%2u", &year, &month, &day);
if (parsed != 3)
return errc::protocol_value_error;
// Verify date validity
::date::year_month_day result (::date::year(year)/::date::month(month)/::date::day(day));
if (!result.ok())
return errc::protocol_value_error;
// Range check
if (result > max_date || result < min_date)
return errc::protocol_value_error;
// Done
to = result;
return errc::ok;
}
@@ -46,28 +92,46 @@ inline errc deserialize_text_value_impl(
unsigned decimals
)
{
using namespace textc;
// Adjust decimals
decimals = sanitize_decimals(decimals);
// size check
constexpr std::size_t min_size = 2 + 2 + 2 + 2; // hours, mins, seconds, no micros
constexpr std::size_t max_size = min_size + 1 + 1 + 7; // hour extra character, sign and micros
decimals = std::min(decimals, 6u);
if (from.size() < min_size || from.size() > max_size)
std::size_t actual_min_size = time_min_sz + (decimals ? decimals + 1 : 0);
std::size_t actual_max_size = actual_min_size + 1 + 1; // hour extra character and sign
assert(actual_max_size <= time_max_sz);
if (from.size() < actual_min_size || from.size() > actual_max_size)
return errc::protocol_value_error;
// Copy to NULL-terminated buffer
char buffer [time_max_sz + 1] {};
memcpy(buffer, from.data(), from.size());
// Sign
bool is_negative = from[0] == '-';
const char* first = is_negative ? buffer + 1 : buffer;
// Parse it
int hours;
unsigned minutes, seconds, micros = 0;
char buffer [max_size + 1] {};
memcpy(buffer, from.data(), from.size());
int parsed = decimals ? sscanf(buffer, "%4d:%2u:%2u.%6u", &hours, &minutes, &seconds, &micros) : // sign adds 1 char
sscanf(buffer, "%4d:%2u:%2u", &hours, &minutes, &seconds);
if ((decimals && parsed != 4) || (!decimals && parsed != 3))
return errc::protocol_value_error;
micros *= static_cast<unsigned>(std::pow(10, 6 - decimals));
hours = std::abs(hours);
bool is_negative = from[0] == '-';
unsigned hours, minutes, seconds;
unsigned micros = 0;
char extra_char;
if (decimals)
{
int parsed = sscanf(first, "%3u:%2u:%2u.%6u%c", &hours, &minutes, &seconds, &micros, &extra_char);
if (parsed != 4)
return errc::protocol_value_error;
micros = compute_micros(micros, decimals);
}
else
{
int parsed = sscanf(first, "%3u:%2u:%2u%c", &hours, &minutes, &seconds, &extra_char);
if (parsed != 3)
return errc::protocol_value_error;
}
// Range check for minutes and seconds (hours may be greater than 24)
if (minutes >= 60 || seconds >= 60)
// Range check
if (hours > max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
return errc::protocol_value_error;
// Sum it
@@ -78,10 +142,7 @@ inline errc deserialize_text_value_impl(
res = -res;
}
// Range check
if (res > max_time || res < min_time)
return errc::protocol_value_error;
// Done
to = res;
return errc::ok;
}
@@ -92,30 +153,35 @@ inline errc deserialize_text_value_impl(
unsigned decimals
)
{
using namespace textc;
// Sanitize decimals
decimals = sanitize_decimals(decimals);
// Length check
constexpr std::size_t min_size = 4 + 5*2 + 5; // year, month, day, hour, minute, seconds, separators
decimals = std::min(decimals, 6u);
std::size_t expected_size = min_size + (decimals ? decimals + 1 : 0);
std::size_t expected_size = datetime_min_sz + (decimals ? decimals + 1 : 0);
if (from.size() != expected_size)
return errc::protocol_value_error;
// Date part
date dt;
auto err = deserialize_text_value_impl(from.substr(0, 10), dt);
// Parse date
date d;
auto err = deserialize_text_value_impl(from.substr(0, date_sz), d);
if (err != errc::ok)
return err;
// Time of day part
time time_of_day;
err = deserialize_text_value_impl(from.substr(11), time_of_day, decimals);
err = deserialize_text_value_impl(from.substr(date_sz + 1), time_of_day, decimals);
if (err != errc::ok)
return err;
// Range check
constexpr auto max_time_of_day = std::chrono::hours(24) - std::chrono::microseconds(1);
if (time_of_day < std::chrono::seconds(0) || time_of_day > max_time_of_day)
return errc::protocol_value_error;
// Sum it up
to = dt + time_of_day;
to = d + time_of_day;
return errc::ok;
}
@@ -141,30 +207,16 @@ inline errc deserialize_text_value_impl(std::string_view from, std::string_view&
template <typename T, typename... Args>
errc deserialize_text_value_to_variant(std::string_view from, value& to, Args&&... args)
{
T value;
auto err = deserialize_text_value_impl(from, value, std::forward<Args>(args)...);
if (err == errc::ok)
{
to = value;
}
return err;
return deserialize_text_value_impl(from, to.emplace<T>(), std::forward<Args>(args)...);
}
inline bool is_next_field_null(
deserialization_context& ctx
const deserialization_context& ctx
)
{
int1 type_byte;
errc err = deserialize(type_byte, ctx);
if (err == errc::ok)
{
if (type_byte.value == 0xfb)
{
return true; // it was null, do not rewind
}
ctx.rewind(1); // it was not null, rewind (this byte is part of the actual message)
}
return false;
if (!ctx.enough_size(1))
return false;
return *ctx.first() == 0xfb;
}
} // detail
@@ -232,9 +284,9 @@ boost::mysql::error_code boost::mysql::detail::deserialize_text_row(
output.resize(fields.size());
for (std::vector<value>::size_type i = 0; i < fields.size(); ++i)
{
bool is_null = is_next_field_null(ctx);
if (is_null)
if (is_next_field_null(ctx))
{
ctx.advance(1);
output[i] = nullptr;
}
else

View File

@@ -216,10 +216,13 @@ INSTANTIATE_TEST_SUITE_P(DATETIME, DeserializeTextValueTest, Values(
text_value_testcase("6_decimals_hms", "2010-02-15 02:05:30.000000", makedt(2010, 2, 15, 2, 5, 30), protocol_field_type::datetime, 0, 6),
text_value_testcase("6_decimals_hmsu", "2010-02-15 02:05:30.002395", makedt(2010, 2, 15, 2, 5, 30, 2395), protocol_field_type::datetime, 0, 6),
text_value_testcase("6_decimals_min", "1000-01-01 00:00:00.000000", makedt(1000, 1, 1), protocol_field_type::datetime, 0, 6),
text_value_testcase("6_decimals_max", "9999-12-31 23:59:59.999999", makedt(9999, 12, 31, 23, 59, 59, 999999), protocol_field_type::datetime, 0, 6)
text_value_testcase("6_decimals_max", "9999-12-31 23:59:59.999999", makedt(9999, 12, 31, 23, 59, 59, 999999), protocol_field_type::datetime, 0, 6),
// not a real case, we cap decimals to 6
text_value_testcase("7_decimals", "2010-02-15 02:05:30.002395", makedt(2010, 2, 15, 2, 5, 30, 2395), protocol_field_type::datetime, 0, 7)
), test_name_generator);
// Right now, timestamps are deserialized as DATETIMEs. TODO: update this when we consider time zones
// Right now, timestamps are deserialized as DATETIMEs
INSTANTIATE_TEST_SUITE_P(TIMESTAMP, DeserializeTextValueTest, Values(
text_value_testcase("0_decimals", "2010-02-15 02:05:30", makedt(2010, 2, 15, 2, 5, 30), protocol_field_type::timestamp),
text_value_testcase("6_decimals", "2010-02-15 02:05:30.085670", makedt(2010, 2, 15, 2, 5, 30, 85670), protocol_field_type::timestamp, 0, 6),
@@ -237,6 +240,7 @@ INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueTest, Values(
text_value_testcase("0_decimals_negative_hms", "-14:51:23", -maket(14, 51, 23), protocol_field_type::time),
text_value_testcase("0_decimals_min", "-838:59:59", -maket(838, 59, 59), protocol_field_type::time),
text_value_testcase("0_decimals_zero", "00:00:00", maket(0, 0, 0), protocol_field_type::time),
text_value_testcase("0_decimals_negative_h0", "-00:51:23", -maket(0, 51, 23), protocol_field_type::time),
text_value_testcase("1_decimals_positive_hms", "14:51:23.0", maket(14, 51, 23), protocol_field_type::time, 0, 1),
text_value_testcase("1_decimals_positive_hmsu", "14:51:23.5", maket(14, 51, 23, 500000), protocol_field_type::time, 0, 1),
@@ -245,6 +249,7 @@ INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueTest, Values(
text_value_testcase("1_decimals_negative_hmsu", "-14:51:23.5", -maket(14, 51, 23, 500000), protocol_field_type::time, 0, 1),
text_value_testcase("1_decimals_min", "-838:59:58.9", -maket(838, 59, 58, 900000), protocol_field_type::time, 0, 1),
text_value_testcase("1_decimals_zero", "00:00:00.0", maket(0, 0, 0), protocol_field_type::time, 0, 1),
text_value_testcase("1_decimals_negative_h0", "-00:51:23.1", -maket(0, 51, 23, 100000), protocol_field_type::time, 0, 1),
text_value_testcase("2_decimals_positive_hms", "14:51:23.00", maket(14, 51, 23), protocol_field_type::time, 0, 2),
text_value_testcase("2_decimals_positive_hmsu", "14:51:23.52", maket(14, 51, 23, 520000), protocol_field_type::time, 0, 2),
@@ -253,6 +258,7 @@ INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueTest, Values(
text_value_testcase("2_decimals_negative_hmsu", "-14:51:23.50", -maket(14, 51, 23, 500000), protocol_field_type::time, 0, 2),
text_value_testcase("2_decimals_min", "-838:59:58.99", -maket(838, 59, 58, 990000), protocol_field_type::time, 0, 2),
text_value_testcase("2_decimals_zero", "00:00:00.00", maket(0, 0, 0), protocol_field_type::time, 0, 2),
text_value_testcase("2_decimals_negative_h0", "-00:51:23.12", -maket(0, 51, 23, 120000), protocol_field_type::time, 0, 2),
text_value_testcase("3_decimals_positive_hms", "14:51:23.000", maket(14, 51, 23), protocol_field_type::time, 0, 3),
text_value_testcase("3_decimals_positive_hmsu", "14:51:23.501", maket(14, 51, 23, 501000), protocol_field_type::time, 0, 3),
@@ -261,6 +267,7 @@ INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueTest, Values(
text_value_testcase("3_decimals_negative_hmsu", "-14:51:23.003", -maket(14, 51, 23, 3000), protocol_field_type::time, 0, 3),
text_value_testcase("3_decimals_min", "-838:59:58.999", -maket(838, 59, 58, 999000), protocol_field_type::time, 0, 3),
text_value_testcase("3_decimals_zero", "00:00:00.000", maket(0, 0, 0), protocol_field_type::time, 0, 3),
text_value_testcase("3_decimals_negative_h0", "-00:51:23.123", -maket(0, 51, 23, 123000), protocol_field_type::time, 0, 3),
text_value_testcase("4_decimals_positive_hms", "14:51:23.0000", maket(14, 51, 23), protocol_field_type::time, 0, 4),
text_value_testcase("4_decimals_positive_hmsu", "14:51:23.5017", maket(14, 51, 23, 501700), protocol_field_type::time, 0, 4),
@@ -269,6 +276,7 @@ INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueTest, Values(
text_value_testcase("4_decimals_negative_hmsu", "-14:51:23.0038", -maket(14, 51, 23, 3800), protocol_field_type::time, 0, 4),
text_value_testcase("4_decimals_min", "-838:59:58.9999", -maket(838, 59, 58, 999900), protocol_field_type::time, 0, 4),
text_value_testcase("4_decimals_zero", "00:00:00.0000", maket(0, 0, 0), protocol_field_type::time, 0, 4),
text_value_testcase("4_decimals_negative_h0", "-00:51:23.1234", -maket(0, 51, 23, 123400), protocol_field_type::time, 0, 4),
text_value_testcase("5_decimals_positive_hms", "14:51:23.00000", maket(14, 51, 23), protocol_field_type::time, 0, 5),
text_value_testcase("5_decimals_positive_hmsu", "14:51:23.50171", maket(14, 51, 23, 501710), protocol_field_type::time, 0, 5),
@@ -277,6 +285,7 @@ INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueTest, Values(
text_value_testcase("5_decimals_negative_hmsu", "-14:51:23.00009", -maket(14, 51, 23, 90), protocol_field_type::time, 0, 5),
text_value_testcase("5_decimals_min", "-838:59:58.99999", -maket(838, 59, 58, 999990), protocol_field_type::time, 0, 5),
text_value_testcase("5_decimals_zero", "00:00:00.00000", maket(0, 0, 0), protocol_field_type::time, 0, 5),
text_value_testcase("5_decimals_negative_h0", "-00:51:23.12345", -maket(0, 51, 23, 123450), protocol_field_type::time, 0, 5),
text_value_testcase("6_decimals_positive_hms", "14:51:23.000000", maket(14, 51, 23), protocol_field_type::time, 0, 6),
text_value_testcase("6_decimals_positive_hmsu", "14:51:23.501717", maket(14, 51, 23, 501717), protocol_field_type::time, 0, 6),
@@ -285,6 +294,7 @@ INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueTest, Values(
text_value_testcase("6_decimals_negative_hmsu", "-14:51:23.900000", -maket(14, 51, 23, 900000), protocol_field_type::time, 0, 6),
text_value_testcase("6_decimals_min", "-838:59:58.999999", -maket(838, 59, 58, 999999), protocol_field_type::time, 0, 6),
text_value_testcase("6_decimals_zero", "00:00:00.000000", maket(0, 0, 0), protocol_field_type::time, 0, 6),
text_value_testcase("6_decimals_negative_h0", "-00:51:23.123456", -maket(0, 51, 23, 123456), protocol_field_type::time, 0, 6),
// This is not a real case - we cap anything above 6 decimals to 6
text_value_testcase("7_decimals", "14:51:23.501717", maket(14, 51, 23, 501717), protocol_field_type::time, 0, 7)
@@ -303,17 +313,17 @@ struct err_text_value_testcase : named_param
std::string name;
std::string_view from;
protocol_field_type type;
unsigned decimals;
std::uint16_t flags;
unsigned decimals;
errc expected_err;
err_text_value_testcase(std::string&& name, std::string_view from, protocol_field_type type,
unsigned decimals=0, std::uint16_t flags=0, errc expected_err=errc::protocol_value_error) :
std::uint16_t flags=0, unsigned decimals=0, errc expected_err=errc::protocol_value_error) :
name(std::move(name)),
from(from),
type(type),
decimals(decimals),
flags(flags),
decimals(decimals),
expected_err(expected_err)
{
}
@@ -369,7 +379,7 @@ std::vector<err_text_value_testcase> make_int32_err_cases(
{
// Unsigned integers behaviour for negative inputs are determined by what iostreams
// do (accepting it and overflowing)
return make_int_err_cases(t, "-2147483649", "2147483648", "-2147483649", "4294967296");
return make_int_err_cases(t, "-2147483649", "2147483648", "-4294967296", "4294967296");
}
INSTANTIATE_TEST_SUITE_P(TINYINT, DeserializeTextValueErrorTest, ValuesIn(
@@ -398,7 +408,7 @@ std::vector<err_text_value_testcase> make_int64_err_cases(
t,
"-9223372036854775809",
"9223372036854775808",
"-9223372036854775809",
"-18446744073709551616",
"18446744073709551616"
);
}
@@ -479,6 +489,13 @@ std::vector<err_text_value_testcase> make_datetime_err_cases(
err_text_value_testcase("no_decimals_4", "2020-05-02 23:01:00 ", t, 0, 4),
err_text_value_testcase("no_decimals_5", "2020-05-02 23:01:00 ", t, 0, 5),
err_text_value_testcase("no_decimals_6", "2020-05-02 23:01:00 ", t, 0, 6),
err_text_value_testcase("trailing_0", "2020-05-02 23:01:0p", t, 0, 0),
err_text_value_testcase("trailing_1", "2020-05-02 23:01:00.p", t, 0, 1),
err_text_value_testcase("trailing_2", "2020-05-02 23:01:00.1p", t, 0, 2),
err_text_value_testcase("trailing_3", "2020-05-02 23:01:00.12p", t, 0, 3),
err_text_value_testcase("trailing_4", "2020-05-02 23:01:00.123p", t, 0, 4),
err_text_value_testcase("trailing_5", "2020-05-02 23:01:00.1234p", t, 0, 5),
err_text_value_testcase("trailing_6", "2020-05-02 23:01:00.12345p", t, 0, 6),
err_text_value_testcase("bad_delimiter", "2020-05-02 23-01-00", t),
err_text_value_testcase("missing_1gp_0", "2020-05-02 23:01: ", t),
err_text_value_testcase("missing_2gp_0", "2020-05-02 23: ", t),
@@ -490,11 +507,11 @@ std::vector<err_text_value_testcase> make_datetime_err_cases(
err_text_value_testcase("invalid_hour2", "2020-05-02 240:2:20", t),
err_text_value_testcase("negative_hour", "2020-05-02 -2:20:20", t),
err_text_value_testcase("invalid_min", "2020-05-02 22:60:20", t),
err_text_value_testcase("nagetive_min", "2020-05-02 22:-1:20", t),
err_text_value_testcase("negative_min", "2020-05-02 22:-1:20", t),
err_text_value_testcase("invalid_sec", "2020-05-02 22:06:60", t),
err_text_value_testcase("nagetive_sec", "2020-05-02 22:06:-1", t),
err_text_value_testcase("negative_sec", "2020-05-02 22:06:-1", t),
err_text_value_testcase("negative_micro", "2020-05-02 22:06:01.-1", t, 0, 2),
err_text_value_testcase("lt_min", "0900-01-01 00:00:00.00", t, 0, 2),
err_text_value_testcase("lt_min", "0090-01-01 00:00:00.00", t, 0, 2),
err_text_value_testcase("gt_max", "10000-01-01 00:00:00.00", t, 0, 2)
};
}
@@ -509,6 +526,62 @@ INSTANTIATE_TEST_SUITE_P(TIMESTAMP, DeserializeTextValueErrorTest, ValuesIn(
make_datetime_err_cases(protocol_field_type::timestamp)
), test_name_generator);
INSTANTIATE_TEST_SUITE_P(TIME, DeserializeTextValueErrorTest, Values(
err_text_value_testcase("empty", "", protocol_field_type::time),
err_text_value_testcase("not_numbers", "abjkjdb67", protocol_field_type::time),
err_text_value_testcase("too_short_0", "1:20:20", protocol_field_type::time),
err_text_value_testcase("too_short_1", "1:20:20.1", protocol_field_type::time, 0, 1),
err_text_value_testcase("too_short_2", "01:20:20.1", protocol_field_type::time, 0, 2),
err_text_value_testcase("too_short_3", "01:20:20.12", protocol_field_type::time, 0, 3),
err_text_value_testcase("too_short_4", "01:20:20.123", protocol_field_type::time, 0, 4),
err_text_value_testcase("too_short_5", "01:20:20.1234", protocol_field_type::time, 0, 5),
err_text_value_testcase("too_short_6", "01:20:20.12345", protocol_field_type::time, 0, 6),
err_text_value_testcase("too_long_0", "-9999:40:40", protocol_field_type::time, 0, 0),
err_text_value_testcase("too_long_1", "-9999:40:40.1", protocol_field_type::time, 0, 1),
err_text_value_testcase("too_long_2", "-9999:40:40.12", protocol_field_type::time, 0, 2),
err_text_value_testcase("too_long_3", "-9999:40:40.123", protocol_field_type::time, 0, 3),
err_text_value_testcase("too_long_4", "-9999:40:40.1234", protocol_field_type::time, 0, 4),
err_text_value_testcase("too_long_5", "-9999:40:40.12345", protocol_field_type::time, 0, 5),
err_text_value_testcase("too_long_6", "-9999:40:40.123456", protocol_field_type::time, 0, 6),
err_text_value_testcase("extra_long", "-99999999:40:40.12345678", protocol_field_type::time, 0, 6),
err_text_value_testcase("extra_long2", "99999999999:40:40", protocol_field_type::time, 0, 6),
err_text_value_testcase("decimals_0", "01:20:20.1", protocol_field_type::time, 0, 0),
err_text_value_testcase("no_decimals_1", "01:20:20 ", protocol_field_type::time, 0, 1),
err_text_value_testcase("no_decimals_2", "01:20:20 ", protocol_field_type::time, 0, 2),
err_text_value_testcase("no_decimals_3", "01:20:20 ", protocol_field_type::time, 0, 3),
err_text_value_testcase("no_decimals_4", "01:20:20 ", protocol_field_type::time, 0, 4),
err_text_value_testcase("no_decimals_5", "01:20:20 ", protocol_field_type::time, 0, 5),
err_text_value_testcase("no_decimals_6", "01:20:20 ", protocol_field_type::time, 0, 6),
err_text_value_testcase("bad_delimiter", "01-20-20", protocol_field_type::time),
err_text_value_testcase("missing_1gp_0", "23:01: ", protocol_field_type::time),
err_text_value_testcase("missing_2gp_0", "23: ", protocol_field_type::time),
err_text_value_testcase("missing_1gp_1", "23:01:.9 ", protocol_field_type::time, 0, 1),
err_text_value_testcase("missing_2gp_1", "23:.9 ", protocol_field_type::time, 0, 1),
err_text_value_testcase("invalid_min", "22:60:20", protocol_field_type::time),
err_text_value_testcase("negative_min", "22:-1:20", protocol_field_type::time),
err_text_value_testcase("invalid_sec", "22:06:60", protocol_field_type::time),
err_text_value_testcase("negative_sec", "22:06:-1", protocol_field_type::time),
err_text_value_testcase("invalid_micro_1", "22:06:01.99", protocol_field_type::time, 0, 1),
err_text_value_testcase("invalid_micro_2", "22:06:01.999", protocol_field_type::time, 0, 2),
err_text_value_testcase("invalid_micro_3", "22:06:01.9999", protocol_field_type::time, 0, 3),
err_text_value_testcase("invalid_micro_4", "22:06:01.99999", protocol_field_type::time, 0, 4),
err_text_value_testcase("invalid_micro_5", "22:06:01.999999", protocol_field_type::time, 0, 5),
err_text_value_testcase("invalid_micro_6", "22:06:01.9999999", protocol_field_type::time, 0, 6),
err_text_value_testcase("negative_micro", "22:06:01.-1", protocol_field_type::time, 0, 2),
err_text_value_testcase("lt_min", "-900:00:00.00", protocol_field_type::time, 0, 2),
err_text_value_testcase("gt_max", "900:00:00.00", protocol_field_type::time, 0, 2),
err_text_value_testcase("invalid_sign", "x670:00:00.00", protocol_field_type::time, 0, 2),
err_text_value_testcase("null_char", makesv("20:00:\00.00"), protocol_field_type::time, 0, 2),
err_text_value_testcase("trailing_0", "22:06:01k", protocol_field_type::time, 0, 0),
err_text_value_testcase("trailing_1", "22:06:01.1k", protocol_field_type::time, 0, 1),
err_text_value_testcase("trailing_2", "22:06:01.12k", protocol_field_type::time, 0, 2),
err_text_value_testcase("trailing_3", "22:06:01.123k", protocol_field_type::time, 0, 3),
err_text_value_testcase("trailing_4", "22:06:01.1234k", protocol_field_type::time, 0, 4),
err_text_value_testcase("trailing_5", "22:06:01.12345k", protocol_field_type::time, 0, 5),
err_text_value_testcase("trailing_6", "22:06:01.123456k", protocol_field_type::time, 0, 6),
err_text_value_testcase("double_sign", "--22:06:01.123456", protocol_field_type::time, 0, 6)
), test_name_generator);
// All cases, row
struct DeserializeTextRowTest : public Test
{