/*
 * nghttp2 - HTTP/2 C Library
 *
 * Copyright (c) 2013 Tatsuhiro Tsujikawa
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "util_test.h"

#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#  include <sys/socket.h>
#endif // defined(HAVE_SYS_SOCKET_H)
#ifdef HAVE_NETDB_H
#  include <netdb.h>
#endif // defined(HAVE_NETDB_H)

#include <cstring>
#include <random>

#include "munitxx.h"

#include <nghttp2/nghttp2.h>

#include "util.h"
#include "template.h"

using namespace nghttp2;
using namespace std::literals;

namespace shrpx {

namespace {
const MunitTest tests[]{
  munit_void_test(test_util_streq),
  munit_void_test(test_util_strieq),
  munit_void_test(test_util_tolower),
  munit_void_test(test_util_to_base64),
  munit_void_test(test_util_to_token68),
  munit_void_test(test_util_percent_encode_token),
  munit_void_test(test_util_percent_decode),
  munit_void_test(test_util_quote_string),
  munit_void_test(test_util_utox),
  munit_void_test(test_util_http_date),
  munit_void_test(test_util_select_h2),
  munit_void_test(test_util_ipv6_numeric_addr),
  munit_void_test(test_util_contains),
  munit_void_test(test_util_utos),
  munit_void_test(test_util_make_string_ref_uint),
  munit_void_test(test_util_utos_unit),
  munit_void_test(test_util_utos_funit),
  munit_void_test(test_util_parse_uint_with_unit),
  munit_void_test(test_util_parse_uint),
  munit_void_test(test_util_parse_duration_with_unit),
  munit_void_test(test_util_duration_str),
  munit_void_test(test_util_format_duration),
  munit_void_test(test_util_starts_with),
  munit_void_test(test_util_ends_with),
  munit_void_test(test_util_parse_http_date),
  munit_void_test(test_util_localtime_date),
  munit_void_test(test_util_get_uint64),
  munit_void_test(test_util_parse_config_str_list),
  munit_void_test(test_util_make_http_hostport),
  munit_void_test(test_util_make_hostport),
  munit_void_test(test_util_random_alpha_digit),
  munit_void_test(test_util_format_hex),
  munit_void_test(test_util_format_upper_hex_uint8),
  munit_void_test(test_util_is_hex_string),
  munit_void_test(test_util_decode_hex),
  munit_void_test(test_util_extract_host),
  munit_void_test(test_util_split_hostport),
  munit_void_test(test_util_split_str),
  munit_void_test(test_util_rstrip),
  munit_void_test(test_util_contains),
  munit_void_test(test_util_hex_to_uint),
  munit_void_test(test_util_is_alpha),
  munit_void_test(test_util_is_digit),
  munit_void_test(test_util_is_hex_digit),
  munit_void_test(test_util_in_rfc3986_unreserved_chars),
  munit_void_test(test_util_in_rfc3986_sub_delims),
  munit_void_test(test_util_in_token),
  munit_void_test(test_util_in_attr_char),
  munit_void_test(test_util_upcase),
  munit_void_test(test_util_lowcase),
  munit_void_test(test_util_to_numeric_addr),
  munit_void_test(test_util_fieldeq),
  munit_void_test(test_util_sha256),
  munit_void_test(test_util_sha1),
  munit_test_end(),
};
} // namespace

const MunitSuite util_suite{
  .prefix = "/util",
  .tests = tests,
};

void test_util_streq(void) {
  assert_true(util::streq("alpha"sv, "alpha"sv));
  assert_false(util::streq("alphabravo"sv, "alpha"sv));
  assert_false(util::streq("alpha"sv, "alphA"sv));
  assert_false(util::streq(""sv, "a"sv));
  assert_true(util::streq(""sv, ""sv));
  assert_false(util::streq("alpha"sv, ""sv));
}

void test_util_strieq(void) {
  assert_true(util::strieq("alpha"sv, "alpha"sv));
  assert_true(util::strieq("alpha"sv, "AlPhA"sv));
  assert_true(util::strieq(""sv, ""sv));
  assert_false(util::strieq("alpha"sv, "AlPhA "sv));
  assert_false(util::strieq(""sv, "AlPhA "sv));

  assert_true(util::strieq("alpha"sv, "alpha"sv));
  assert_true(util::strieq("alpha"sv, "AlPhA"sv));
  assert_true(util::strieq(""sv, ""sv));
  assert_false(util::strieq("alpha"sv, "AlPhA "sv));
  assert_false(util::strieq(""sv, "AlPhA "sv));
}

void test_util_tolower(void) {
  std::array<char, 16> buf;

  {
    assert_stdsv_equal(
      "alpha"sv,
      (std::string_view{std::ranges::begin(buf),
                        util::tolower("alPha"sv, std::ranges::begin(buf))}));
  }

  {
    auto s = "alPha"sv;

    assert_stdsv_equal(
      "alpha"sv, (std::string_view{std::ranges::begin(buf),
                                   util::tolower(std::ranges::begin(s),
                                                 std::ranges::end(s),
                                                 std::ranges::begin(buf))}));
  }

  {
    assert_stdsv_equal(
      ""sv, (std::string_view{std::ranges::begin(buf),
                              util::tolower(""sv, std::ranges::begin(buf))}));
  }

  {
    std::string s = "AlpHA\x00BraVO"s;

    util::tolower(s, std::ranges::begin(s));

    assert_stdstring_equal("alpha\x00bravo"s, s);
  }

  {
    std::string s = "\xbe\xef"s;

    util::tolower(s, std::ranges::begin(s));

    assert_stdstring_equal("\xbe\xef"s, s);
  }
}

void test_util_to_base64(void) {
  BlockAllocator balloc(4096, 4096);

  assert_stdsv_equal("AAA++B/="sv, util::to_base64(balloc, "AAA--B_"sv));
  assert_stdsv_equal("AAA++B/B"sv, util::to_base64(balloc, "AAA--B_B"sv));
}

void test_util_to_token68(void) {
  std::string x = "AAA++B/=";
  util::to_token68(x);
  assert_stdstring_equal("AAA--B_", x);

  x = "AAA++B/B";
  util::to_token68(x);
  assert_stdstring_equal("AAA--B_B", x);
}

void test_util_percent_encode_token(void) {
  std::array<char, 64> buf;

  assert_stdsv_equal(
    "h2"sv, as_string_view(
              std::ranges::begin(buf),
              util::percent_encode_token("h2"sv, std::ranges::begin(buf))));

  assert_size("h2"sv.size(), ==, util::percent_encode_tokenlen("h2"sv));

  assert_stdsv_equal(
    "h3~"sv, as_string_view(
               std::ranges::begin(buf),
               util::percent_encode_token("h3~"sv, std::ranges::begin(buf))));

  assert_size("h3~"sv.size(), ==, util::percent_encode_tokenlen("h3~"sv));

  assert_stdsv_equal("100%25"sv,
                     as_string_view(std::ranges::begin(buf),
                                    util::percent_encode_token(
                                      "100%"sv, std::ranges::begin(buf))));
  assert_size("100%25"sv.size(), ==, util::percent_encode_tokenlen("100%"sv));

  assert_stdsv_equal("http%202"sv,
                     as_string_view(std::ranges::begin(buf),
                                    util::percent_encode_token(
                                      "http 2"sv, std::ranges::begin(buf))));

  assert_size("http%202"sv.size(), ==,
              util::percent_encode_tokenlen("http 2"sv));
}

void test_util_percent_decode(void) {
  {
    std::string s = "%66%6F%6f%62%61%72";
    assert_stdstring_equal("foobar", util::percent_decode(std::ranges::begin(s),
                                                          std::ranges::end(s)));
  }
  {
    std::string s = "%66%6";
    assert_stdstring_equal("f%6", util::percent_decode(s));
  }
  {
    std::string s = "%66%";
    assert_stdstring_equal(
      "f%", util::percent_decode(std::ranges::begin(s), std::ranges::end(s)));
  }
  BlockAllocator balloc(1024, 1024);

  assert_stdsv_equal("foobar"sv,
                     util::percent_decode(balloc, "%66%6F%6f%62%61%72"sv));

  assert_stdsv_equal("f%6"sv, util::percent_decode(balloc, "%66%6"sv));

  assert_stdsv_equal("f%"sv, util::percent_decode(balloc, "%66%"sv));
}

void test_util_quote_string(void) {
  BlockAllocator balloc(4096, 4096);
  assert_stdsv_equal("alpha"sv, util::quote_string(balloc, "alpha"sv));
  assert_stdsv_equal(""sv, util::quote_string(balloc, ""sv));
  assert_stdsv_equal("\\\"alpha\\\""sv,
                     util::quote_string(balloc, "\"alpha\""sv));

  assert_size("\\\"alpha\\\""sv.size(), ==,
              util::quote_stringlen("\"alpha\""sv));
  assert_size(0, ==, util::quote_stringlen(""sv));
}

void test_util_utox(void) {
  std::array<char, 16> buf;

  assert_stdsv_equal(
    "0"sv, (std::string_view{std::ranges::begin(buf),
                             util::utox(0, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "1"sv, (std::string_view{std::ranges::begin(buf),
                             util::utox(1, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "F"sv, (std::string_view{std::ranges::begin(buf),
                             util::utox(15, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "10"sv, (std::string_view{std::ranges::begin(buf),
                              util::utox(16, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "3B9ACA07"sv,
    (std::string_view{std::ranges::begin(buf),
                      util::utox(1000000007, std::ranges::begin(buf))}));
  assert_stdsv_equal("B5EA98F3663B14A"sv,
                     (std::string_view{std::ranges::begin(buf),
                                       util::utox(819278614785929546,
                                                  std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "100000000"sv,
    (std::string_view{std::ranges::begin(buf),
                      util::utox(1LL << 32, std::ranges::begin(buf))}));
}

void test_util_http_date(void) {
  assert_stdstring_equal(
    "Thu, 01 Jan 1970 00:00:00 GMT"s,
    util::format_http_date(std::chrono::system_clock::time_point()));
  assert_stdstring_equal(
    "Wed, 29 Feb 2012 09:15:16 GMT"s,
    util::format_http_date(std::chrono::system_clock::from_time_t(1330506916)));

  std::array<char, 30> http_buf;

  assert_stdsv_equal(
    "Thu, 01 Jan 1970 00:00:00 GMT"sv,
    util::format_http_date(http_buf.data(),
                           std::chrono::system_clock::time_point()));
  assert_stdsv_equal(
    "Wed, 29 Feb 2012 09:15:16 GMT"sv,
    util::format_http_date(http_buf.data(),
                           std::chrono::system_clock::from_time_t(1330506916)));
}

void test_util_select_h2(void) {
  const unsigned char *out = nullptr;
  unsigned char outlen = 0;

  // Check single entry and select it.
  const unsigned char t1[] = "\x2h2";
  assert_true(util::select_h2(&out, &outlen, t1, sizeof(t1) - 1));
  assert_memory_equal(NGHTTP2_PROTO_VERSION_ID_LEN, NGHTTP2_PROTO_VERSION_ID,
                      out);
  assert_uchar(NGHTTP2_PROTO_VERSION_ID_LEN, ==, outlen);

  out = nullptr;
  outlen = 0;

  // Check the case where id is correct but length is invalid and too
  // long.
  const unsigned char t2[] = "\x3h2";
  assert_false(util::select_h2(&out, &outlen, t2, sizeof(t2) - 1));

  // Check the case where h2 is located after bogus ID.
  const unsigned char t3[] = "\x2h3\x2h2";
  assert_true(util::select_h2(&out, &outlen, t3, sizeof(t3) - 1));

  assert_memory_equal(NGHTTP2_PROTO_VERSION_ID_LEN, NGHTTP2_PROTO_VERSION_ID,
                      out);
  assert_uchar(NGHTTP2_PROTO_VERSION_ID_LEN, ==, outlen);

  out = nullptr;
  outlen = 0;

  // Check the case that last entry's length is invalid and too long.
  const unsigned char t4[] = "\x2h3\x6h2";
  assert_false(util::select_h2(&out, &outlen, t4, sizeof(t4) - 1));

  // Check the case that all entries are not supported.
  const unsigned char t5[] = "\x2h3\x2h4";
  assert_false(util::select_h2(&out, &outlen, t5, sizeof(t5) - 1));
}

void test_util_ipv6_numeric_addr(void) {
  assert_true(util::ipv6_numeric_addr("::1"));
  assert_true(
    util::ipv6_numeric_addr("2001:0db8:85a3:0042:1000:8a2e:0370:7334"));
  // IPv4
  assert_false(util::ipv6_numeric_addr("127.0.0.1"));
  // not numeric address
  assert_false(util::ipv6_numeric_addr("localhost"));
}

void test_util_count_digit(void) {
  assert_size(1, ==, util::count_digit(0u));
  assert_size(1, ==, util::count_digit(1u));
  assert_size(1, ==, util::count_digit(9u));
  assert_size(2, ==, util::count_digit(10u));
  assert_size(2, ==, util::count_digit(99u));
  assert_size(3, ==, util::count_digit(100u));
  assert_size(3, ==, util::count_digit(999u));
  assert_size(4, ==, util::count_digit(1'000u));
  assert_size(4, ==, util::count_digit(9'999u));
  assert_size(5, ==, util::count_digit(10'000u));
  assert_size(5, ==, util::count_digit(99'999u));
  assert_size(6, ==, util::count_digit(100'000u));
  assert_size(6, ==, util::count_digit(999'999u));
  assert_size(7, ==, util::count_digit(1'000'000u));
  assert_size(7, ==, util::count_digit(9'999'999u));
  assert_size(8, ==, util::count_digit(10'000'000u));
  assert_size(8, ==, util::count_digit(99'999'999u));
  assert_size(9, ==, util::count_digit(100'000'000u));
  assert_size(9, ==, util::count_digit(999'999'999u));
  assert_size(10, ==, util::count_digit(1'000'000'000u));
  assert_size(10, ==, util::count_digit(9'999'999'999u));
  assert_size(11, ==, util::count_digit(10'000'000'000u));
  assert_size(11, ==, util::count_digit(99'999'999'999u));
  assert_size(12, ==, util::count_digit(100'000'000'000u));
  assert_size(12, ==, util::count_digit(999'999'999'999u));
  assert_size(13, ==, util::count_digit(1'000'000'000'000u));
  assert_size(13, ==, util::count_digit(9'999'999'999'999u));
  assert_size(14, ==, util::count_digit(10'000'000'000'000u));
  assert_size(14, ==, util::count_digit(99'999'999'999'999u));
  assert_size(15, ==, util::count_digit(100'000'000'000'000u));
  assert_size(15, ==, util::count_digit(999'999'999'999'999u));
  assert_size(16, ==, util::count_digit(1'000'000'000'000'000u));
  assert_size(16, ==, util::count_digit(9'999'999'999'999'999u));
  assert_size(17, ==, util::count_digit(10'000'000'000'000'000u));
  assert_size(17, ==, util::count_digit(99'999'999'999'999'999u));
  assert_size(18, ==, util::count_digit(100'000'000'000'000'000u));
  assert_size(18, ==, util::count_digit(999'999'999'999'999'999u));
  assert_size(19, ==, util::count_digit(1'000'000'000'000'000'000u));
  assert_size(19, ==, util::count_digit(9'999'999'999'999'999'999u));
  assert_size(20, ==, util::count_digit(10'000'000'000'000'000'000u));
  assert_size(20, ==, util::count_digit(std::numeric_limits<uint64_t>::max()));
}

void test_util_utos(void) {
  char buf[32];

  assert_stdstring_equal("123"s, (std::string{buf, util::utos(123u, buf)}));

  assert_stdstring_equal("0"s, util::utos(0u));
  assert_stdstring_equal("123"s, util::utos(123u));
  assert_stdstring_equal("123"s, util::utos(static_cast<uint8_t>(123)));
  assert_stdstring_equal("123"s, util::utos(static_cast<uint16_t>(123)));
  assert_stdstring_equal("18446744073709551615"s,
                         util::utos(18'446'744'073'709'551'615u));

  assert_stdsv_equal("0"sv, (std::string_view{buf, util::utos(0u, buf)}));
  assert_stdsv_equal("1"sv, (std::string_view{buf, util::utos(1u, buf)}));
  assert_stdsv_equal("9"sv, (std::string_view{buf, util::utos(9u, buf)}));
  assert_stdsv_equal("10"sv, (std::string_view{buf, util::utos(10u, buf)}));
  assert_stdsv_equal("99"sv, (std::string_view{buf, util::utos(99u, buf)}));
  assert_stdsv_equal("100"sv, (std::string_view{buf, util::utos(100u, buf)}));
  assert_stdsv_equal("999"sv, (std::string_view{buf, util::utos(999u, buf)}));
  assert_stdsv_equal("1000"sv,
                     (std::string_view{buf, util::utos(1'000u, buf)}));
  assert_stdsv_equal("9999"sv,
                     (std::string_view{buf, util::utos(9'999u, buf)}));
  assert_stdsv_equal("10000"sv,
                     (std::string_view{buf, util::utos(10'000u, buf)}));
  assert_stdsv_equal("99999"sv,
                     (std::string_view{buf, util::utos(99'999u, buf)}));
  assert_stdsv_equal("100000"sv,
                     (std::string_view{buf, util::utos(100'000u, buf)}));
  assert_stdsv_equal("999999"sv,
                     (std::string_view{buf, util::utos(999'999u, buf)}));
  assert_stdsv_equal("1000000"sv,
                     (std::string_view{buf, util::utos(1'000'000u, buf)}));
  assert_stdsv_equal("9999999"sv,
                     (std::string_view{buf, util::utos(9'999'999u, buf)}));
  assert_stdsv_equal("10000000"sv,
                     (std::string_view{buf, util::utos(10'000'000u, buf)}));
  assert_stdsv_equal("99999999"sv,
                     (std::string_view{buf, util::utos(99'999'999u, buf)}));
  assert_stdsv_equal("100000000"sv,
                     (std::string_view{buf, util::utos(100'000'000u, buf)}));
  assert_stdsv_equal("999999999"sv,
                     (std::string_view{buf, util::utos(999'999'999u, buf)}));
  assert_stdsv_equal("1000000000"sv,
                     (std::string_view{buf, util::utos(1'000'000'000u, buf)}));
  assert_stdsv_equal("9999999999"sv,
                     (std::string_view{buf, util::utos(9'999'999'999u, buf)}));
  assert_stdsv_equal("10000000000"sv,
                     (std::string_view{buf, util::utos(10'000'000'000u, buf)}));
  assert_stdsv_equal("99999999999"sv,
                     (std::string_view{buf, util::utos(99'999'999'999u, buf)}));
  assert_stdsv_equal(
    "100000000000"sv,
    (std::string_view{buf, util::utos(100'000'000'000u, buf)}));
  assert_stdsv_equal(
    "999999999999"sv,
    (std::string_view{buf, util::utos(999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "1000000000000"sv,
    (std::string_view{buf, util::utos(1'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "9999999999999"sv,
    (std::string_view{buf, util::utos(9'999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "10000000000000"sv,
    (std::string_view{buf, util::utos(10'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "99999999999999"sv,
    (std::string_view{buf, util::utos(99'999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "100000000000000"sv,
    (std::string_view{buf, util::utos(100'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "999999999999999"sv,
    (std::string_view{buf, util::utos(999'999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "1000000000000000"sv,
    (std::string_view{buf, util::utos(1'000'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "9999999999999999"sv,
    (std::string_view{buf, util::utos(9'999'999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "10000000000000000"sv,
    (std::string_view{buf, util::utos(10'000'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "99999999999999999"sv,
    (std::string_view{buf, util::utos(99'999'999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "100000000000000000"sv,
    (std::string_view{buf, util::utos(100'000'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "999999999999999999"sv,
    (std::string_view{buf, util::utos(999'999'999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "1000000000000000000"sv,
    (std::string_view{buf, util::utos(1'000'000'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "9999999999999999999"sv,
    (std::string_view{buf, util::utos(9'999'999'999'999'999'999u, buf)}));
  assert_stdsv_equal(
    "10000000000000000000"sv,
    (std::string_view{buf, util::utos(10'000'000'000'000'000'000u, buf)}));
  assert_stdsv_equal(
    "18446744073709551615"sv,
    (std::string_view{buf,
                      util::utos(std::numeric_limits<uint64_t>::max(), buf)}));
}

void test_util_make_string_ref_uint(void) {
  BlockAllocator balloc(1024, 1024);

  assert_stdsv_equal("0"sv, util::make_string_ref_uint(balloc, 0u));
  assert_stdsv_equal("123"sv, util::make_string_ref_uint(balloc, 123u));
  assert_stdsv_equal(
    "18446744073709551615"sv,
    util::make_string_ref_uint(balloc, 18446744073709551615ULL));
}

void test_util_utos_unit(void) {
  assert_stdstring_equal("0", util::utos_unit(0u));
  assert_stdstring_equal("1023", util::utos_unit(1023u));
  assert_stdstring_equal("1K", util::utos_unit(1024u));
  assert_stdstring_equal("1K", util::utos_unit(1025u));
  assert_stdstring_equal("1M", util::utos_unit(1u << 20));
  assert_stdstring_equal("1G", util::utos_unit(1u << 30));
  assert_stdstring_equal("1024G", util::utos_unit(1ULL << 40));
}

void test_util_utos_funit(void) {
  assert_stdstring_equal("0", util::utos_funit(0u));
  assert_stdstring_equal("1023", util::utos_funit(1023u));
  assert_stdstring_equal("1.00K", util::utos_funit(1024u));
  assert_stdstring_equal("1.00K", util::utos_funit(1025u));
  assert_stdstring_equal("1.09K", util::utos_funit(1119u));
  assert_stdstring_equal("1.27K", util::utos_funit(1300u));
  assert_stdstring_equal("1.00M", util::utos_funit(1u << 20));
  assert_stdstring_equal("1.18M", util::utos_funit(1234567u));
  assert_stdstring_equal("1.00G", util::utos_funit(1u << 30));
  assert_stdstring_equal("4492450797.23G",
                         util::utos_funit(4823732313248234343ULL));
  assert_stdstring_equal("1024.00G", util::utos_funit(1ULL << 40));
}

void test_util_parse_uint_with_unit(void) {
  constexpr auto badval = std::numeric_limits<uint64_t>::max();

  assert_uint64(0, ==, util::parse_uint_with_unit("0").value_or(badval));
  assert_uint64(1023, ==, util::parse_uint_with_unit("1023").value_or(badval));
  assert_uint64(1024, ==, util::parse_uint_with_unit("1k").value_or(badval));
  assert_uint64(2048, ==, util::parse_uint_with_unit("2K").value_or(badval));
  assert_uint64(1 << 20, ==, util::parse_uint_with_unit("1m").value_or(badval));
  assert_uint64(1 << 21, ==, util::parse_uint_with_unit("2M").value_or(badval));
  assert_uint64(1 << 30, ==, util::parse_uint_with_unit("1g").value_or(badval));
  assert_uint64(1LL << 31, ==,
                util::parse_uint_with_unit("2G").value_or(badval));
  assert_uint64(
    9223372036854775807LL, ==,
    util::parse_uint_with_unit("9223372036854775807").value_or(badval));
  // check overflow case
  {
    auto rv = util::parse_uint_with_unit("9223372036854775808");

    assert_false(rv.has_value());
    assert_true(Error::INTEGER_OVERFLOW == rv.error());
  }
  {
    auto rv = util::parse_uint_with_unit("10000000000000000000");

    assert_false(rv.has_value());
    assert_true(Error::INTEGER_OVERFLOW == rv.error());
  }
  {
    auto rv = util::parse_uint_with_unit("9223372036854775807G");

    assert_false(rv.has_value());
    assert_true(Error::INTEGER_OVERFLOW == rv.error());
  }
  // bad characters
  {
    auto rv = util::parse_uint_with_unit("1.1");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint_with_unit("1a");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint_with_unit("a1");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint_with_unit("1T");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint_with_unit("");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
}

void test_util_parse_uint(void) {
  constexpr auto badval = std::numeric_limits<uint64_t>::max();

  assert_uint64(0, ==, util::parse_uint("0").value_or(badval));
  assert_uint64(1023, ==, util::parse_uint("1023").value_or(badval));
  assert_false(util::parse_uint("1k"));
  assert_uint64(9223372036854775807LL, ==,
                util::parse_uint("9223372036854775807").value_or(badval));
  // check overflow case
  {
    auto rv = util::parse_uint("9223372036854775808");

    assert_false(rv.has_value());
    assert_true(Error::INTEGER_OVERFLOW == rv.error());
  }
  {
    auto rv = util::parse_uint("10000000000000000000");

    assert_false(rv.has_value());
    assert_true(Error::INTEGER_OVERFLOW == rv.error());
  }
  // bad characters
  {
    auto rv = util::parse_uint("1.1");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint("1a");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint("a1");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint("1T");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_uint("");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
}

void test_util_parse_duration_with_unit(void) {
  constexpr auto inf = std::numeric_limits<double>::infinity();

  assert_double(0., ==, util::parse_duration_with_unit("0").value_or(inf));
  assert_double(123., ==, util::parse_duration_with_unit("123").value_or(inf));
  assert_double(123., ==, util::parse_duration_with_unit("123s").value_or(inf));
  assert_double(0.500, ==,
                util::parse_duration_with_unit("500ms").value_or(inf));
  assert_double(123., ==, util::parse_duration_with_unit("123S").value_or(inf));
  assert_double(0.500, ==,
                util::parse_duration_with_unit("500MS").value_or(inf));
  assert_double(180, ==, util::parse_duration_with_unit("3m").value_or(inf));
  assert_double(3600 * 5, ==,
                util::parse_duration_with_unit("5h").value_or(inf));

  // check overflow case
  {
    auto rv = util::parse_duration_with_unit("9223372036854775808");

    assert_false(rv.has_value());
    assert_true(Error::INTEGER_OVERFLOW == rv.error());
  }
  // bad characters
  {
    auto rv = util::parse_duration_with_unit("0u");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_duration_with_unit("0xs");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_duration_with_unit("0mt");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_duration_with_unit("0mss");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_duration_with_unit("s");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
  {
    auto rv = util::parse_duration_with_unit("ms");

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
}

void test_util_duration_str(void) {
  assert_stdstring_equal("0", util::duration_str(0.));
  assert_stdstring_equal("1s", util::duration_str(1.));
  assert_stdstring_equal("500ms", util::duration_str(0.5));
  assert_stdstring_equal("1500ms", util::duration_str(1.5));
  assert_stdstring_equal("2m", util::duration_str(120.));
  assert_stdstring_equal("121s", util::duration_str(121.));
  assert_stdstring_equal("1h", util::duration_str(3600.));
}

void test_util_format_duration(void) {
  assert_stdstring_equal("0us",
                         util::format_duration(std::chrono::microseconds(0)));
  assert_stdstring_equal("999us",
                         util::format_duration(std::chrono::microseconds(999)));
  assert_stdstring_equal(
    "1.00ms", util::format_duration(std::chrono::microseconds(1000)));
  assert_stdstring_equal(
    "1.09ms", util::format_duration(std::chrono::microseconds(1090)));
  assert_stdstring_equal(
    "1.01ms", util::format_duration(std::chrono::microseconds(1009)));
  assert_stdstring_equal(
    "999.99ms", util::format_duration(std::chrono::microseconds(999990)));
  assert_stdstring_equal(
    "1.00s", util::format_duration(std::chrono::microseconds(1000000)));
  assert_stdstring_equal(
    "1.05s", util::format_duration(std::chrono::microseconds(1050000)));

  assert_stdstring_equal("0us", util::format_duration(0.));
  assert_stdstring_equal("999us", util::format_duration(0.000999));
  assert_stdstring_equal("1.00ms", util::format_duration(0.001));
  assert_stdstring_equal("1.09ms", util::format_duration(0.00109));
  assert_stdstring_equal("1.01ms", util::format_duration(0.001009));
  assert_stdstring_equal("999.99ms", util::format_duration(0.99999));
  assert_stdstring_equal("1.00s", util::format_duration(1.));
  assert_stdstring_equal("1.05s", util::format_duration(1.05));
}

void test_util_starts_with(void) {
  assert_true(util::starts_with("foo"sv, "foo"sv));
  assert_true(util::starts_with("fooo"sv, "foo"sv));
  assert_true(util::starts_with("ofoo"sv, ""sv));
  assert_false(util::starts_with("ofoo"sv, "foo"sv));

  assert_true(util::istarts_with("FOO"sv, "fOO"sv));
  assert_true(util::istarts_with("ofoo"sv, ""sv));
  assert_true(util::istarts_with("fOOo"sv, "Foo"sv));
  assert_false(util::istarts_with("ofoo"sv, "foo"sv));
}

void test_util_ends_with(void) {
  assert_true(util::ends_with("foo"sv, "foo"sv));
  assert_true(util::ends_with("foo"sv, ""sv));
  assert_true(util::ends_with("ofoo"sv, "foo"sv));
  assert_false(util::ends_with("ofoo"sv, "fo"sv));

  assert_true(util::iends_with("fOo"sv, "Foo"sv));
  assert_true(util::iends_with("foo"sv, ""sv));
  assert_true(util::iends_with("oFoo"sv, "fOO"sv));
  assert_false(util::iends_with("ofoo"sv, "fo"sv));
}

void test_util_parse_http_date(void) {
  assert_int64(1001939696, ==,
               util::parse_http_date("Mon, 1 Oct 2001 12:34:56 GMT"sv)
                 .value_or(std::numeric_limits<time_t>::max()));

  {
    auto rv = util::parse_http_date("Mon, 1 Oct 200X 12:34:56 GMT"sv);

    assert_false(rv.has_value());
    assert_true(Error::INVALID_ARGUMENT == rv.error());
  }
}

void test_util_localtime_date(void) {
  std::array<char, 30> buf;

#ifdef HAVE_STD_CHRONO_TIME_ZONE
  assert_stdsv_equal(
    "2001-10-02T00:34:56.123+12:00"sv,
    util::format_iso8601(buf.data(),
                         std::chrono::system_clock::time_point(
                           std::chrono::milliseconds(1001939696123LL)),
                         std::chrono::locate_zone("Pacific/Auckland"sv)));

  assert_stdsv_equal(
    "20011002T003456.123+1200"sv,
    util::format_iso8601_basic(buf.data(),
                               std::chrono::system_clock::time_point(
                                 std::chrono::milliseconds(1001939696123LL)),
                               std::chrono::locate_zone("Pacific/Auckland"sv)));

  assert_stdsv_equal(
    "02/Oct/2001:00:34:56 +1200"sv,
    util::format_common_log(buf.data(),
                            std::chrono::system_clock::from_time_t(1001939696),
                            std::chrono::locate_zone("Pacific/Auckland"sv)));

  assert_stdsv_equal(
    "2001-10-01T12:34:56.123Z"sv,
    util::format_iso8601(buf.data(),
                         std::chrono::system_clock::time_point(
                           std::chrono::milliseconds(1001939696123LL)),
                         std::chrono::locate_zone("GMT"sv)));

  assert_stdsv_equal(
    "20011001T123456.123Z"sv,
    util::format_iso8601_basic(buf.data(),
                               std::chrono::system_clock::time_point(
                                 std::chrono::milliseconds(1001939696123LL)),
                               std::chrono::locate_zone("GMT"sv)));

  assert_stdsv_equal(
    "01/Oct/2001:12:34:56 +0000"sv,
    util::format_common_log(buf.data(),
                            std::chrono::system_clock::from_time_t(1001939696),
                            std::chrono::locate_zone("GMT"sv)));
#else // !defined(HAVE_STD_CHRONO_TIME_ZONE)
  auto tz = getenv("TZ");
  if (tz) {
    tz = strdup(tz);
  }
#  ifdef __linux__
  setenv("TZ", "NZST-12:00:00:00", 1);
#  else  // !defined(__linux__)
  setenv("TZ", ":Pacific/Auckland", 1);
#  endif // !defined(__linux__)
  tzset();

  assert_stdsv_equal(
    "2001-10-02T00:34:56.123+12:00"sv,
    util::format_iso8601(buf.data(),
                         std::chrono::system_clock::time_point(
                           std::chrono::milliseconds(1001939696123LL))));

  assert_stdsv_equal(
    "20011002T003456.123+1200"sv,
    util::format_iso8601_basic(buf.data(),
                               std::chrono::system_clock::time_point(
                                 std::chrono::milliseconds(1001939696123LL))));

  assert_stdsv_equal(
    "02/Oct/2001:00:34:56 +1200"sv,
    util::format_common_log(
      buf.data(), std::chrono::system_clock::from_time_t(1001939696)));

  if (tz) {
    setenv("TZ", tz, 1);
    free(tz);
  } else {
    unsetenv("TZ");
  }
  tzset();
#endif   // !defined(HAVE_STD_CHRONO_TIME_ZONE)
}

void test_util_get_uint64(void) {
  {
    auto v = std::to_array<unsigned char>(
      {0x01, 0x12, 0x34, 0x56, 0xFF, 0x9A, 0xAB, 0xBC});

    auto n = util::get_uint64(v.data());

    assert_uint64(0x01123456FF9AABBCULL, ==, n);
  }
  {
    auto v = std::to_array<unsigned char>(
      {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});

    auto n = util::get_uint64(v.data());

    assert_uint64(0xFFFFFFFFFFFFFFFFULL, ==, n);
  }
}

void test_util_parse_config_str_list(void) {
  auto res = util::parse_config_str_list("a"sv);
  assert_size(1, ==, res.size());
  assert_stdstring_equal("a", res[0]);

  res = util::parse_config_str_list("a,"sv);
  assert_size(2, ==, res.size());
  assert_stdstring_equal("a", res[0]);
  assert_stdstring_equal("", res[1]);

  res = util::parse_config_str_list(":a::"sv, ':');
  assert_size(4, ==, res.size());
  assert_stdstring_equal("", res[0]);
  assert_stdstring_equal("a", res[1]);
  assert_stdstring_equal("", res[2]);
  assert_stdstring_equal("", res[3]);

  res = util::parse_config_str_list(""sv);
  assert_true(res.empty());

  res = util::parse_config_str_list("alpha,bravo,charlie"sv);
  assert_size(3, ==, res.size());
  assert_stdstring_equal("alpha", res[0]);
  assert_stdstring_equal("bravo", res[1]);
  assert_stdstring_equal("charlie", res[2]);
}

void test_util_make_http_hostport(void) {
  BlockAllocator balloc(4096, 4096);

  assert_stdsv_equal("localhost"sv,
                     util::make_http_hostport(balloc, "localhost"sv, 80));
  assert_stdsv_equal("[::1]"sv, util::make_http_hostport(balloc, "::1"sv, 443));
  assert_stdsv_equal("localhost:3000"sv,
                     util::make_http_hostport(balloc, "localhost"sv, 3000));
}

void test_util_make_hostport(void) {
  std::array<char, util::max_hostport> hostport_buf;
  assert_stdsv_equal(
    "localhost:80"sv,
    util::make_hostport("localhost"sv, 80, std::ranges::begin(hostport_buf)));
  assert_stdsv_equal(
    "[::1]:443"sv,
    util::make_hostport("::1"sv, 443, std::ranges::begin(hostport_buf)));

  BlockAllocator balloc(4096, 4096);
  assert_stdsv_equal("localhost:80"sv,
                     util::make_hostport(balloc, "localhost"sv, 80));
  assert_stdsv_equal("[::1]:443"sv, util::make_hostport(balloc, "::1"sv, 443));
}

void test_util_random_alpha_digit(void) {
  std::random_device rd;
  std::mt19937 gen(rd());
  std::array<uint8_t, 19> data;

  auto p = util::random_alpha_digit(std::ranges::begin(data),
                                    std::ranges::end(data), gen);

  assert_true(std::ranges::end(data) == p);

  for (auto b : data) {
    assert_true(('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') ||
                ('0' <= b && b <= '9'));
  }
}

void test_util_format_hex(void) {
  BlockAllocator balloc(4096, 4096);

  assert_stdsv_equal("0ff0"sv, util::format_hex(balloc, "\x0f\xf0"sv));
  assert_stdsv_equal(""sv, util::format_hex(balloc, ""sv));

  std::string o;
  o.resize(4);

  assert_true(std::ranges::end(o) ==
              util::format_hex("\xbe\xef"sv, std::ranges::begin(o)));
  assert_stdstring_equal("beef"s, o);
  assert_stdstring_equal("beef"s, util::format_hex("\xbe\xef"sv));

  std::array<char, 64> buf;

  assert_stdsv_equal(
    "00"sv, (std::string_view{std::ranges::begin(buf),
                              util::format_hex(static_cast<uint8_t>(0u),
                                               std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "ec"sv, (std::string_view{std::ranges::begin(buf),
                              util::format_hex(static_cast<uint8_t>(0xECU),
                                               std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "00000000"sv,
    (std::string_view{std::ranges::begin(buf),
                      util::format_hex(0u, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "0000ab01"sv,
    (std::string_view{std::ranges::begin(buf),
                      util::format_hex(0xAB01U, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "deadbeefbaadf00d"sv,
    (std::string_view{
      std::ranges::begin(buf),
      util::format_hex(0xDEADBEEFBAADF00DU, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "ffffffffffffffff"sv,
    (std::string_view{std::ranges::begin(buf),
                      util::format_hex(std::numeric_limits<uint64_t>::max(),
                                       std::ranges::begin(buf))}));

  std::vector<char> char_vec;
  util::format_hex(0xDEADBEEF, std::back_inserter(char_vec));

  assert_stdsv_equal("deadbeef"sv,
                     (std::string_view{std::ranges::begin(char_vec),
                                       std::ranges::end(char_vec)}));

  std::vector<uint8_t> uint8_vec;
  util::format_hex(0xDEADBEEF, std::back_inserter(uint8_vec));

  assert_stdsv_equal(
    "deadbeef"sv,
    (std::string_view{reinterpret_cast<const char *>(uint8_vec.data()),
                      uint8_vec.size()}));
}

void test_util_format_upper_hex_uint8(void) {
  std::array<char, 64> buf;

  assert_stdsv_equal("00"sv, (std::string_view{std::ranges::begin(buf),
                                               util::format_upper_hex_uint8(
                                                 0, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "0A"sv, (std::string_view{
              std::ranges::begin(buf),
              util::format_upper_hex_uint8(0xA, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "7C"sv, (std::string_view{
              std::ranges::begin(buf),
              util::format_upper_hex_uint8(0x07C, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "EB"sv, (std::string_view{
              std::ranges::begin(buf),
              util::format_upper_hex_uint8(0xEB, std::ranges::begin(buf))}));
  assert_stdsv_equal(
    "FF"sv, (std::string_view{
              std::ranges::begin(buf),
              util::format_upper_hex_uint8(0xFF, std::ranges::begin(buf))}));
}

void test_util_is_hex_string(void) {
  assert_true(util::is_hex_string(""sv));
  assert_true(util::is_hex_string("0123456789abcdef"sv));
  assert_true(util::is_hex_string("0123456789ABCDEF"sv));
  assert_false(util::is_hex_string("000"sv));
  assert_false(util::is_hex_string("XX"sv));
}

void test_util_decode_hex(void) {
  BlockAllocator balloc(4096, 4096);

  assert_stdsv_equal("\x0f\xf0"sv,
                     as_string_view(util::decode_hex(balloc, "0ff0"sv)));
  assert_stdsv_equal(""sv, as_string_view(util::decode_hex(balloc, ""sv)));
}

void test_util_extract_host(void) {
  assert_stdsv_equal("foo"sv, util::extract_host("foo"sv).value_or(""));
  assert_stdsv_equal("foo"sv, util::extract_host("foo:"sv).value_or(""));
  assert_stdsv_equal("foo"sv, util::extract_host("foo:0"sv).value_or(""));
  assert_stdsv_equal("[::1]"sv, util::extract_host("[::1]"sv).value_or(""));
  assert_stdsv_equal("[::1]"sv, util::extract_host("[::1]:"sv).value_or(""));

  assert_false(util::extract_host(":foo"sv).has_value());
  assert_false(util::extract_host("[::1"sv).has_value());
  assert_false(util::extract_host("[::1]0"sv).has_value());
  assert_false(util::extract_host(""sv).has_value());
}

void test_util_split_hostport(void) {
  static constexpr auto empty = std::pair<std::string_view, std::string_view>{};

  assert_true(std::make_pair("foo"sv, ""sv) ==
              util::split_hostport("foo"sv).value_or(empty));
  assert_true(std::make_pair("foo"sv, "80"sv) ==
              util::split_hostport("foo:80"sv).value_or(empty));
  assert_true(std::make_pair("::1"sv, "80"sv) ==
              util::split_hostport("[::1]:80"sv).value_or(empty));
  assert_true(std::make_pair("::1"sv, ""sv) ==
              util::split_hostport("[::1]"sv).value_or(empty));

  assert_false(util::split_hostport(""sv).has_value());
  assert_false(util::split_hostport("[::1]:"sv).has_value());
  assert_false(util::split_hostport("foo:"sv).has_value());
  assert_false(util::split_hostport("[::1:"sv).has_value());
  assert_false(util::split_hostport("[::1]80"sv).has_value());
}

void test_util_split_str(void) {
  assert_true(util::split_str(""sv, ',').empty());
  assert_true(std::vector<std::string_view>{"alpha"sv} ==
              util::split_str("alpha"sv, ','));
  assert_true((std::vector<std::string_view>{"alpha"sv, ""sv}) ==
              util::split_str("alpha,"sv, ','));
  assert_true((std::vector<std::string_view>{"alpha"sv, "bravo"sv}) ==
              util::split_str("alpha,bravo"sv, ','));
  assert_true(
    (std::vector<std::string_view>{"alpha"sv, "bravo"sv, "charlie"sv}) ==
    util::split_str("alpha,bravo,charlie"sv, ','));

  BlockAllocator balloc(4096, 4096);

  assert_true((std::vector<std::string_view>{"alpha"sv, "bravo"sv}) ==
              util::split_str(balloc, "alpha,bravo"sv, ','));

  assert_true(
    (std::vector<std::string_view>{"alpha"sv, "bravo"sv, "charlie"sv}) ==
    util::split_str("alpha,bravo,charlie"sv, ',', 3));
  assert_true(util::split_str(""sv, ',', 1).empty());
  assert_true(util::split_str(""sv, ',', 2).empty());
  assert_true((std::vector<std::string_view>{"alpha"sv, "bravo,charlie"sv}) ==
              util::split_str("alpha,bravo,charlie"sv, ',', 2));
  assert_true(std::vector<std::string_view>{"alpha"sv} ==
              util::split_str("alpha"sv, ',', 2));
  assert_true((std::vector<std::string_view>{"alpha"sv, ""sv}) ==
              util::split_str("alpha,"sv, ',', 2));
  assert_true(std::vector<std::string_view>{"alpha,bravo,charlie"sv} ==
              util::split_str("alpha,bravo,charlie"sv, ',', 1));
}

void test_util_rstrip(void) {
  BlockAllocator balloc(4096, 4096);

  assert_stdsv_equal("alpha"sv, util::rstrip(balloc, "alpha"sv));
  assert_stdsv_equal("alpha"sv, util::rstrip(balloc, "alpha "sv));
  assert_stdsv_equal("alpha"sv, util::rstrip(balloc, "alpha \t"sv));
  assert_stdsv_equal(""sv, util::rstrip(balloc, ""sv));
  assert_stdsv_equal(""sv, util::rstrip(balloc, "\t\t\t   "sv));
}

void test_util_contains(void) {
  assert_true(util::contains("alphabravo"sv, 'a'));
  assert_true(util::contains("alphabravo"sv, 'o'));
  assert_false(util::contains("alphabravo"sv, 'x'));
  assert_false(util::contains(""sv, ' '));
}

void test_util_hex_to_uint(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    if (!util::is_hex_digit(c)) {
      assert_uint32(256, ==, util::hex_to_uint(c));
    }
  }

  for (uint32_t i = 0; i < 10; ++i) {
    assert_uint32(i, ==, util::hex_to_uint(static_cast<char>('0' + i)));
  }

  for (uint32_t i = 0; i < 6; ++i) {
    assert_uint32(i + 10, ==, util::hex_to_uint(static_cast<char>('A' + i)));
  }

  for (uint32_t i = 0; i < 6; ++i) {
    assert_uint32(i + 10, ==, util::hex_to_uint(static_cast<char>('a' + i)));
  }
}

void test_util_is_alpha(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
      assert_true(util::is_alpha(c));
    } else {
      assert_false(util::is_alpha(c));
    }
  }
}

void test_util_is_digit(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    if ('0' <= c && c <= '9') {
      assert_true(util::is_digit(c));
    } else {
      assert_false(util::is_digit(c));
    }
  }
}

void test_util_is_hex_digit(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    if (util::is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f')) {
      assert_true(util::is_hex_digit(c));
    } else {
      assert_false(util::is_hex_digit(c));
    }
  }
}

void test_util_in_rfc3986_unreserved_chars(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    switch (c) {
    case '-':
    case '.':
    case '_':
    case '~':
      assert_true(util::in_rfc3986_unreserved_chars(c));
      break;
    default:
      if (util::is_digit(c) || util::is_alpha(c)) {
        assert_true(util::in_rfc3986_unreserved_chars(c));
      } else {
        assert_false(util::in_rfc3986_unreserved_chars(c));
      }
    }
  }
}

void test_util_in_rfc3986_sub_delims(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    switch (c) {
    case '!':
    case '$':
    case '&':
    case '\'':
    case '(':
    case ')':
    case '*':
    case '+':
    case ',':
    case ';':
    case '=':
      assert_true(util::in_rfc3986_sub_delims(c));
      break;
    default:
      assert_false(util::in_rfc3986_sub_delims(c));
    }
  }
}

void test_util_in_token(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    switch (c) {
    case '!':
    case '#':
    case '$':
    case '%':
    case '&':
    case '\'':
    case '*':
    case '+':
    case '-':
    case '.':
    case '^':
    case '_':
    case '`':
    case '|':
    case '~':
      assert_true(util::in_token(c));
      break;
    default:
      if (util::is_digit(c) || util::is_alpha(c)) {
        assert_true(util::in_token(c));
      } else {
        assert_false(util::in_token(c));
      }
    }
  }
}

void test_util_in_attr_char(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    switch (c) {
    case '%':
    case '\'':
    case '*':
      assert_false(util::in_attr_char(c));
      break;
    default:
      if (util::in_token(c)) {
        assert_true(util::in_attr_char(c));
      } else {
        assert_false(util::in_attr_char(c));
      }
    }
  }
}

void test_util_upcase(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    if ('a' <= c && c <= 'z') {
      assert_char(static_cast<char>(c - 'a' + 'A'), ==, util::upcase(c));
    } else {
      assert_char(c, ==, util::upcase(c));
    }
  }
}

void test_util_lowcase(void) {
  for (size_t i = 0; i < 256; ++i) {
    auto c = static_cast<char>(i);
    if ('A' <= c && c <= 'Z') {
      assert_char(static_cast<char>(c - 'A' + 'a'), ==, util::lowcase(c));
    } else {
      assert_char(c, ==, util::lowcase(c));
    }
  }
}

void test_util_to_numeric_addr(void) {
  addrinfo hints{
    .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
    .ai_family = AF_UNSPEC,
  };

  addrinfo *res;

  auto rv = getaddrinfo("192.168.0.1", "443", &hints, &res);

  assert_int(0, ==, rv);

  assert_stdstring_equal("192.168.0.1:443"s,
                         util::to_numeric_addr(res->ai_addr, res->ai_addrlen));

  freeaddrinfo(res);

  rv = getaddrinfo("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "4443", &hints,
                   &res);

  assert_int(0, ==, rv);

  assert_stdstring_equal("[2001:db8:85a3::8a2e:370:7334]:4443"s,
                         util::to_numeric_addr(res->ai_addr, res->ai_addrlen));

  freeaddrinfo(res);
}

void test_util_fieldeq(void) {
  static constexpr auto a = "https://example.com/path"sv;
  static constexpr auto b = "https://nghttp2.org"sv;

  urlparse_url u1, u2;

  assert_int(0, ==, urlparse_parse_url(a.data(), a.size(), 0, &u1));
  assert_int(0, ==, urlparse_parse_url(b.data(), b.size(), 0, &u2));

  assert_true(util::fieldeq(a.data(), u1, b.data(), u2, URLPARSE_SCHEMA));
  assert_true(util::fieldeq(a.data(), u1, b.data(), u2, URLPARSE_PORT));
  assert_false(util::fieldeq(a.data(), u1, b.data(), u2, URLPARSE_PATH));
  assert_false(util::fieldeq(a.data(), u1, b.data(), u2, URLPARSE_HOST));
}

void test_util_sha256(void) {
  static constexpr auto s = "All your base are belong to us."sv;
  std::array<uint8_t, 32> md;

  assert_true(util::sha256(md, s).has_value());
  auto mdhex = util::format_hex(md);

  assert_stdsv_equal(
    "c2c565a8c7dc220ed7d9ff2f34b40dae7864ef0b8189557f0d3b7360ef34e1cd"sv,
    mdhex);
}

void test_util_sha1(void) {
  static constexpr auto s = "All your base are belong to us."sv;
  std::array<uint8_t, 20> md;

  assert_true(util::sha1(md, s).has_value());
  auto mdhex = util::format_hex(md);

  assert_stdsv_equal("6e65eeb0bab294dadf2297a7aa2315703ed5b958"sv, mdhex);
}

} // namespace shrpx
