text: /** Various utility functions.
   *
   * Copyright (c) 2000-2021, Jeroen T. Vermeulen.
   *
   * See COPYING for copyright license.  If you did not receive a file called
   * COPYING with this source code, please notify the distributor of this
   * mistake, or contact the author.
   */
  #include "pqxx-source.hxx"
  
  #include <cerrno>
  #include <cmath>
  #include <cstdlib>
  #include <cstring>
  #include <new>
  
  #if defined(PQXX_HAVE_SLEEP_FOR)
  #  include <thread>
  #endif
  
  // For select() on recent POSIX systems.
  #if __has_include(<sys/select.h>)
  #  include <sys/select.h>
  #endif
  
  
  // For select() on some older POSIX systems.
  #if __has_include(<sys / types.h>)
  #  include <sys/types.h>
  #endif
  #if __has_include(<unistd.h>)
  #  include <unistd.h>
  #endif
  #if __has_include(<sys/time.h>)
  #  include <sys/time.h>
  #endif
  
  
  extern "C"
  {
  #include <libpq-fe.h>
  }
  
  #include "pqxx/except"
  #include "pqxx/util"
  
  #include "pqxx/internal/concat.hxx"
  
  
  using namespace std::literals;
  
  pqxx::thread_safety_model pqxx::describe_thread_safety()
  {
    thread_safety_model model;
    model.safe_libpq = (PQisthreadsafe() != 0);
    // Sadly I'm not aware of any way to avoid this just yet.
    model.safe_kerberos = false;
  
    model.description = internal::concat(
      (model.safe_libpq ? ""sv :
                          "Using a libpq build that is not thread-safe.\n"sv),
      (model.safe_kerberos ?
         ""sv :
         "Kerberos is not thread-safe.  If your application uses Kerberos, "
         "protect all calls to Kerberos or libpqxx using a global lock.\n"sv));
    return model;
  }
  
  
  std::string pqxx::internal::describe_object(
    std::string_view class_name, std::string_view obj_name)
  {
    if (std::empty(obj_name))
      return std::string{class_name};
    else
      return pqxx::internal::concat(class_name, " '", obj_name, "'");
  }
  
  
  void pqxx::internal::check_unique_register(
    void const *old_guest, std::string_view old_class, std::string_view old_name,
    void const *new_guest, std::string_view new_class, std::string_view new_name)
  {
    if (new_guest == nullptr)
      throw internal_error{"Null pointer registered."};
  
    if (old_guest != nullptr)
      throw usage_error{
        (old_guest == new_guest) ?
          concat("Started twice: ", describe_object(old_class, old_name), ".") :
          concat(
            "Started new ", describe_object(new_class, new_name), " while ",
            describe_object(new_class, new_name), " was still active.")};
  }
  
  
  void pqxx::internal::check_unique_unregister(
    void const *old_guest, std::string_view old_class, std::string_view old_name,
    void const *new_guest, std::string_view new_class, std::string_view new_name)
  {
    if (new_guest != old_guest)
    {
      if (new_guest == nullptr)
        throw usage_error{concat(
          "Expected to close ", describe_object(old_class, old_name),
          ", but got null pointer instead.")};
      if (old_guest == nullptr)
        throw usage_error{concat(
          "Closed while not open: ", describe_object(new_class, new_name))};
      else
        throw usage_error{concat(
          "Closed ", describe_object(new_class, new_name),
          "; expected to close ", describe_object(old_class, old_name))};
    }
  }
  
  
  namespace
  {
  /// Translate a number (must be between 0 and 16 exclusive) to a hex digit.
  constexpr char hex_digit(int c) noexcept
  {
    constexpr char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7',
                            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    return hex[c];
  }
  
  
  /// Translate a hex digit to a nibble.  Return -1 if it's not a valid digit.
  constexpr int nibble(int c) noexcept
  {
    if (c >= '0' and c <= '9')
      return c - '0';
    else if (c >= 'a' and c <= 'f')
      return 10 + (c - 'a');
    else if (c >= 'A' and c <= 'F')
      return 10 + (c - 'A');
    else
      return -1;
  }
  } // namespace
  
  
  void pqxx::internal::esc_bin(
    std::string_view binary_data, char buffer[]) noexcept
  {
    auto here{buffer};
    *here++ = '\\';
    *here++ = 'x';
  
    for (auto const byte : binary_data)
    {
      auto uc{static_cast<unsigned char>(byte)};
      *here++ = hex_digit(uc >> 4);
      *here++ = hex_digit(uc & 0x0f);
    }
  
    *here++ = '\0';
  }
  
  
  std::string pqxx::internal::esc_bin(std::string_view binary_data)
  {
    auto const bytes{size_esc_bin(std::size(binary_data))};
    std::string buf;
    buf.resize(bytes);
    esc_bin(binary_data, buf.data());
    // Strip off the trailing zero.
    buf.resize(bytes - 1);
    return buf;
  }
  
  
  void pqxx::internal::unesc_bin(
    std::string_view escaped_data, std::byte buffer[])
  {
    auto const in_size{std::size(escaped_data)};
    if (in_size < 2)
      throw pqxx::failure{"Binary data appears truncated."};
    if ((in_size % 2) != 0)
      throw pqxx::failure{"Invalid escaped binary length."};
    char const *in{escaped_data.data()};
    char const *const end{in + in_size};
    if (*in++ != '\\' or *in++ != 'x')
      throw pqxx::failure(
        "Escaped binary data did not start with '\\x'`.  Is the server or libpq "
        "too old?");
    auto out{buffer};
    while (in != end)
    {
      int hi{nibble(*in++)};
      if (hi < 0)
        throw pqxx::failure{"Invalid hex-escaped data."};
      int lo{nibble(*in++)};
      if (lo < 0)
        throw pqxx::failure{"Invalid hex-escaped data."};
      *out++ = static_cast<std::byte>((hi << 4) | lo);
    }
  }
  
  
  std::string pqxx::internal::unesc_bin(std::string_view escaped_data)
  {
    auto const bytes{size_unesc_bin(std::size(escaped_data))};
    std::string buf;
    buf.resize(bytes);
    unesc_bin(escaped_data, reinterpret_cast<std::byte *>(buf.data()));
    return buf;
  }
  
  
  void pqxx::internal::wait_for(unsigned int microseconds)
  {
  #if defined(PQXX_HAVE_SLEEP_FOR)
    std::this_thread::sleep_for(std::chrono::microseconds{microseconds});
  #else
    // MinGW still does not have a functioning <thread> header.  Work around this
    // using select().
    // Not worth optimising for though -- they'll have to fix it at some point.
    timeval tv{microseconds / 1'000'000u, microseconds % 1'000'000u};
    select(0, nullptr, nullptr, nullptr, &tv);
  #endif
  }
  