diff --git a/configure.ac b/configure.ac index 5bcfc6d79f28df7163bd6451672001887ed6afbd..89727e648a3ca3234d0a135d86546bd224375b57 100644 --- a/configure.ac +++ b/configure.ac @@ -96,7 +96,7 @@ AC_PROG_MKDIR_P PKG_PROG_PKG_CONFIG([0.20]) -AX_CXX_COMPILE_STDCXX([14], [noext], [optional]) +AX_CXX_COMPILE_STDCXX([17], [noext], [optional]) # Checks for libraries. diff --git a/examples/examplestest.cc b/examples/examplestest.cc index 9e61689d087177099ee6136005ca69ec3e5313f5..2c35f7a776f252fa06497e8ec30fa9f9e05e994f 100644 --- a/examples/examplestest.cc +++ b/examples/examplestest.cc @@ -53,8 +53,18 @@ int main(int argc, char *argv[]) { } // add the tests to the suite - if (!CU_add_test(pSuite, "util_format_duration", - ngtcp2::test_util_format_duration)) { + if (!CU_add_test(pSuite, "util_format_durationf", + ngtcp2::test_util_format_durationf) || + !CU_add_test(pSuite, "util_format_uint", ngtcp2::test_util_format_uint) || + !CU_add_test(pSuite, "util_format_uint_iec", + ngtcp2::test_util_format_uint_iec) || + !CU_add_test(pSuite, "util_format_duration", + ngtcp2::test_util_format_duration) || + !CU_add_test(pSuite, "util_parse_uint", ngtcp2::test_util_parse_uint) || + !CU_add_test(pSuite, "util_parse_uint_iec", + ngtcp2::test_util_parse_uint_iec) || + !CU_add_test(pSuite, "util_parse_duration", + ngtcp2::test_util_parse_duration)) { CU_cleanup_registry(); return CU_get_error(); } diff --git a/examples/util.cc b/examples/util.cc index 3b4635864deb4074dd78b72d958186bcdaa7f587..4cd37b69b9690f54c7cd4dd77b7aed0aaae2b191 100644 --- a/examples/util.cc +++ b/examples/util.cc @@ -39,6 +39,7 @@ #include <iostream> #include <fstream> #include <algorithm> +#include <limits> namespace ngtcp2 { @@ -131,7 +132,7 @@ uint64_t round2even(uint64_t n) { } } // namespace -std::string format_duration(uint64_t ns) { +std::string format_durationf(uint64_t ns) { static constexpr const char *units[] = {"us", "ms", "s"}; if (ns < 1000) { return std::to_string(ns) + "ns"; @@ -334,6 +335,132 @@ OSSL_ENCRYPTION_LEVEL from_ngtcp2_level(ngtcp2_crypto_level crypto_level) { } } +namespace { +std::tuple<uint64_t, size_t, int> parse_uint_internal(const std::string &s) { + uint64_t res = 0; + + if (s.empty()) { + return {0, 0, -1}; + } + + for (size_t i = 0; i < s.size(); ++i) { + auto c = s[i]; + if (c < '0' || '9' < c) { + return {res, i, 0}; + } + + auto d = c - '0'; + if (res > (std::numeric_limits<uint64_t>::max() - d) / 10) { + return {0, i, -1}; + } + + res *= 10; + res += d; + } + + return {res, s.size(), 0}; +} +} // namespace + +std::pair<uint64_t, int> parse_uint(const std::string &s) { + auto [res, idx, rv] = parse_uint_internal(s); + if (rv != 0 || idx != s.size()) { + return {0, -1}; + } + return {res, 0}; +} + +std::pair<uint64_t, int> parse_uint_iec(const std::string &s) { + auto [res, idx, rv] = parse_uint_internal(s); + if (rv != 0) { + return {0, rv}; + } + if (idx == s.size()) { + return {res, 0}; + } + if (idx + 1 != s.size()) { + return {0, -1}; + } + + uint64_t m; + switch (s[idx]) { + case 'G': + case 'g': + m = 1 << 30; + break; + case 'M': + case 'm': + m = 1 << 20; + break; + case 'K': + case 'k': + m = 1 << 10; + break; + default: + return {0, -1}; + } + + if (res > std::numeric_limits<uint64_t>::max() / m) { + return {0, -1}; + } + + return {res * m, 0}; +} + +std::pair<uint64_t, int> parse_duration(const std::string &s) { + auto [res, idx, rv] = parse_uint_internal(s); + if (rv != 0) { + return {0, rv}; + } + if (idx == s.size()) { + return {res, 0}; + } + + uint64_t m; + if (idx + 1 == s.size()) { + switch (s[idx]) { + case 'H': + case 'h': + m = 3600 * NGTCP2_SECONDS; + break; + case 'M': + case 'm': + m = 60 * NGTCP2_SECONDS; + break; + case 'S': + case 's': + m = NGTCP2_SECONDS; + break; + default: + return {0, -1}; + } + } else if (idx + 2 == s.size() && (s[idx + 1] == 's' || s[idx + 1] == 'S')) { + switch (s[idx]) { + case 'M': + case 'm': + m = NGTCP2_MILLISECONDS; + break; + case 'U': + case 'u': + m = NGTCP2_MICROSECONDS; + break; + case 'N': + case 'n': + return {res, 0}; + default: + return {0, -1}; + } + } else { + return {0, -1}; + } + + if (res > std::numeric_limits<uint64_t>::max() / m) { + return {0, -1}; + } + + return {res * m, 0}; +} + } // namespace util } // namespace ngtcp2 diff --git a/examples/util.h b/examples/util.h index 10ef898b5920de2561b75284ffe6d50761bc10ab..74ddfbada4fe573c03f5be02afcd8612c5040597 100644 --- a/examples/util.h +++ b/examples/util.h @@ -77,12 +77,12 @@ template <size_t N> std::string format_hex(const uint8_t (&s)[N]) { std::string decode_hex(const std::string &s); -// format_duration formats |ns| in human readable manner. |ns| must +// format_durationf formats |ns| in human readable manner. |ns| must // be nanoseconds resolution. This function uses the largest unit so // that the integral part is strictly more than zero, and the // precision is at most 2 digits. For example, 1234 is formatted as // "1.23us". The largest unit is seconds. -std::string format_duration(uint64_t ns); +std::string format_durationf(uint64_t ns); std::mt19937 make_mt19937(); @@ -215,6 +215,77 @@ ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level); // OSSL_ENCRYPTION_LEVEL. OSSL_ENCRYPTION_LEVEL from_ngtcp2_level(ngtcp2_crypto_level crypto_level); +// format_uint converts |n| into string. +template <typename T> std::string format_uint(T n) { + std::string res; + if (n == 0) { + res = "0"; + return res; + } + size_t nlen = 0; + for (auto t = n; t; t /= 10, ++nlen) + ; + res.resize(nlen); + for (; n; n /= 10) { + res[--nlen] = (n % 10) + '0'; + } + return res; +} + +// format_uint_iec converts |n| into string with the IEC unit (either +// "G", "M", or "K"). It chooses the largest unit which does not drop +// precision. +template <typename T> std::string format_uint_iec(T n) { + if (n >= (1 << 30) && (n & ((1 << 30) - 1)) == 0) { + return format_uint(n / (1 << 30)) + 'G'; + } + if (n >= (1 << 20) && (n & ((1 << 20) - 1)) == 0) { + return format_uint(n / (1 << 20)) + 'M'; + } + if (n >= (1 << 10) && (n & ((1 << 10) - 1)) == 0) { + return format_uint(n / (1 << 10)) + 'K'; + } + return format_uint(n); +} + +// format_duration converts |n| into string with the unit in either +// "h" (hours), "m" (minutes), "s" (seconds), "ms" (milliseconds), +// "us" (microseconds) or "ns" (nanoseconds). It chooses the largest +// unit which does not drop precision. |n| is in nanosecond +// resolution. +template <typename T> std::string format_duration(T n) { + if (n >= 3600 * NGTCP2_SECONDS && (n % (3600 * NGTCP2_SECONDS)) == 0) { + return format_uint(n / (3600 * NGTCP2_SECONDS)) + 'h'; + } + if (n >= 60 * NGTCP2_SECONDS && (n % (60 * NGTCP2_SECONDS)) == 0) { + return format_uint(n / (60 * NGTCP2_SECONDS)) + 'm'; + } + if (n >= NGTCP2_SECONDS && (n % NGTCP2_SECONDS) == 0) { + return format_uint(n / NGTCP2_SECONDS) + 's'; + } + if (n >= NGTCP2_MILLISECONDS && (n % NGTCP2_MILLISECONDS) == 0) { + return format_uint(n / NGTCP2_MILLISECONDS) + "ms"; + } + if (n >= NGTCP2_MICROSECONDS && (n % NGTCP2_MICROSECONDS) == 0) { + return format_uint(n / NGTCP2_MICROSECONDS) + "us"; + } + return format_uint(n) + "ns"; +} + +// parse_uint parses |s| as 64-bit unsigned integer. If it cannot +// parse |s|, it returns -1 as the second return value. +std::pair<uint64_t, int> parse_uint(const std::string &s); + +// parse_uint_iec parses |s| as 64-bit unsigned integer. It accepts +// IEC unit letter (either "G", "M", or "K") in |s|. If it cannot +// parse |s|, it returns -1 as the second return value. +std::pair<uint64_t, int> parse_uint_iec(const std::string &s); + +// parse_duration parses |s| as 64-bit unsigned integer. It accepts a +// unit (either "h", "m", "s", "ms", "us", or "ns") in |s|. If it +// cannot parse |s|, it returns -1 as the second return value. +std::pair<uint64_t, int> parse_duration(const std::string &s); + } // namespace util } // namespace ngtcp2 diff --git a/examples/util_test.cc b/examples/util_test.cc index 9206df7bd6d62ce50568a5f61a1bac7414118f97..d9bc00c8e53fa45c5b6f633ef0ead335aad194e4 100644 --- a/examples/util_test.cc +++ b/examples/util_test.cc @@ -24,23 +24,194 @@ */ #include "util_test.h" +#include <limits> + #include <CUnit/CUnit.h> #include "util.h" namespace ngtcp2 { +void test_util_format_durationf() { + CU_ASSERT("0ns" == util::format_durationf(0)); + CU_ASSERT("999ns" == util::format_durationf(999)); + CU_ASSERT("1.00us" == util::format_durationf(1000)); + CU_ASSERT("1.00us" == util::format_durationf(1004)); + CU_ASSERT("1.00us" == util::format_durationf(1005)); + CU_ASSERT("1.02us" == util::format_durationf(1015)); + CU_ASSERT("2.00us" == util::format_durationf(1999)); + CU_ASSERT("1.00ms" == util::format_durationf(999999)); + CU_ASSERT("3.50ms" == util::format_durationf(3500111)); + CU_ASSERT("9999.99s" == util::format_durationf(9999990000000llu)); +} + +void test_util_format_uint() { + CU_ASSERT("0" == util::format_uint(0)); + CU_ASSERT("18446744073709551615" == + util::format_uint(18446744073709551615ull)); +} + +void test_util_format_uint_iec() { + CU_ASSERT("0" == util::format_uint_iec(0)); + CU_ASSERT("1023" == util::format_uint_iec((1 << 10) - 1)); + CU_ASSERT("1K" == util::format_uint_iec(1 << 10)); + CU_ASSERT("1M" == util::format_uint_iec(1 << 20)); + CU_ASSERT("1G" == util::format_uint_iec(1 << 30)); + CU_ASSERT("18446744073709551615" == + util::format_uint_iec(std::numeric_limits<uint64_t>::max())); + CU_ASSERT("1025K" == util::format_uint_iec((1 << 20) + (1 << 10))); +} + void test_util_format_duration() { CU_ASSERT("0ns" == util::format_duration(0)); CU_ASSERT("999ns" == util::format_duration(999)); - CU_ASSERT("1.00us" == util::format_duration(1000)); - CU_ASSERT("1.00us" == util::format_duration(1004)); - CU_ASSERT("1.00us" == util::format_duration(1005)); - CU_ASSERT("1.02us" == util::format_duration(1015)); - CU_ASSERT("2.00us" == util::format_duration(1999)); - CU_ASSERT("1.00ms" == util::format_duration(999999)); - CU_ASSERT("3.50ms" == util::format_duration(3500111)); - CU_ASSERT("9999.99s" == util::format_duration(9999990000000llu)); + CU_ASSERT("1us" == util::format_duration(1000)); + CU_ASSERT("1ms" == util::format_duration(1000000)); + CU_ASSERT("1s" == util::format_duration(1000000000)); + CU_ASSERT("1m" == util::format_duration(60000000000ull)); + CU_ASSERT("1h" == util::format_duration(3600000000000ull)); + CU_ASSERT("18446744073709551615ns" == + util::format_duration(std::numeric_limits<uint64_t>::max())); + CU_ASSERT("61s" == util::format_duration(61000000000ull)); +} + +void test_util_parse_uint() { + { + auto [res, rv] = util::parse_uint("0"); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == res); + } + { + auto [res, rv] = util::parse_uint("1"); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == res); + } + { + auto [res, rv] = util::parse_uint("18446744073709551615"); + CU_ASSERT(0 == rv); + CU_ASSERT(18446744073709551615ull == res); + } + { + auto [_, rv] = util::parse_uint("18446744073709551616"); + CU_ASSERT(-1 == rv); + } + { + auto [_, rv] = util::parse_uint("a"); + CU_ASSERT(-1 == rv); + } + { + auto [_, rv] = util::parse_uint("1a"); + CU_ASSERT(-1 == rv); + } +} + +void test_util_parse_uint_iec() { + { + auto [res, rv] = util::parse_uint_iec("0"); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == res); + } + { + auto [res, rv] = util::parse_uint_iec("1023"); + CU_ASSERT(0 == rv); + CU_ASSERT(1023 == res); + } + { + auto [res, rv] = util::parse_uint_iec("1K"); + CU_ASSERT(0 == rv); + CU_ASSERT(1 << 10 == res); + } + { + auto [res, rv] = util::parse_uint_iec("1M"); + CU_ASSERT(0 == rv); + CU_ASSERT(1 << 20 == res); + } + { + auto [res, rv] = util::parse_uint_iec("1G"); + CU_ASSERT(0 == rv); + CU_ASSERT(1 << 30 == res); + } + { + auto [res, rv] = util::parse_uint_iec("11G"); + CU_ASSERT(0 == rv); + CU_ASSERT((1ull << 30) * 11); + } + { + auto [_, rv] = util::parse_uint_iec("18446744073709551616"); + CU_ASSERT(-1 == rv); + } + { + auto [_, rv] = util::parse_uint_iec("1x"); + CU_ASSERT(-1 == rv); + } + { + auto [_, rv] = util::parse_uint_iec("1Gx"); + CU_ASSERT(-1 == rv); + } +} + +void test_util_parse_duration() { + { + auto [res, rv] = util::parse_duration("0"); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == res); + } + { + auto [res, rv] = util::parse_duration("0ns"); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == res); + } + { + auto [res, rv] = util::parse_duration("1ns"); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == res); + } + { + auto [res, rv] = util::parse_duration("1us"); + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_MICROSECONDS == res); + } + { + auto [res, rv] = util::parse_duration("1ms"); + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_MILLISECONDS == res); + } + { + auto [res, rv] = util::parse_duration("1s"); + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_SECONDS == res); + } + { + auto [res, rv] = util::parse_duration("1m"); + CU_ASSERT(0 == rv); + CU_ASSERT(60 * NGTCP2_SECONDS == res); + } + { + auto [res, rv] = util::parse_duration("1h"); + CU_ASSERT(0 == rv); + CU_ASSERT(3600 * NGTCP2_SECONDS == res); + } + { + auto [res, rv] = util::parse_duration("2h"); + CU_ASSERT(0 == rv); + CU_ASSERT(2 * 3600 * NGTCP2_SECONDS == res); + } + { + auto [_, rv] = util::parse_duration("18446744073709551616"); + CU_ASSERT(-1 == rv); + } + { + auto [_, rv] = util::parse_duration("1x"); + CU_ASSERT(-1 == rv); + } + { + auto [_, rv] = util::parse_duration("1mx"); + CU_ASSERT(-1 == rv); + } + { + auto [_, rv] = util::parse_duration("1mxy"); + CU_ASSERT(-1 == rv); + } } } // namespace ngtcp2 diff --git a/examples/util_test.h b/examples/util_test.h index 20eb1200d748ca561045fb9e952969b0592f46e2..06a0a84a7ebc6b907c87255fc787ccf74a4927f9 100644 --- a/examples/util_test.h +++ b/examples/util_test.h @@ -31,7 +31,13 @@ namespace ngtcp2 { +void test_util_format_durationf(); +void test_util_format_uint(); +void test_util_format_uint_iec(); void test_util_format_duration(); +void test_util_parse_uint(); +void test_util_parse_uint_iec(); +void test_util_parse_duration(); } // namespace ngtcp2