//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012- OpenVPN Inc.
//
//    SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
//

// A collection of functions for rendering and parsing hexadecimal strings

#ifndef OPENVPN_COMMON_HEXSTR_H
#define OPENVPN_COMMON_HEXSTR_H

#include <string>
#include <iomanip>
#include <sstream>
#include <algorithm>

#include <openvpn/common/clamp_typerange.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/string.hpp>

using namespace openvpn::numeric_util;

namespace openvpn {

/**
 *  Renders an integer value within the hexadecimal range (0-15)
 *  to a hexadecimal character.
 *
 *  @param c    Integer to render as a hexadecimal character.
 *  @param caps Boolean (default false) which sets the outout to
 *              be either lower case (false) or upper case (true).
 *
 *  @return Returns a char with the hexadecimal representation of
 *          the input value.  If the value is out-of-range (outside
 *          of 0-15), it will be replaced with a questionmark (?).
 */
inline char render_hex_char(const int c, const bool caps = false)
{
    if (c < 0)
        return '?';
    if (c < 10)
        return '0' + static_cast<char>(c);
    else if (c < 16)
        return static_cast<char>((caps ? 'A' : 'a') - 10 + c);
    else
        return '?';
}

/**
 *  Parses a character in the range {0..9,A-F,a-f} to an
 *  integer value.  Used to convert hexadecimal character to integer.
 *  Only a single character is parsed by this function.
 *
 *  @param c   Character to be be parsed.
 *
 *  @return Returns an integer value of the hexadecimal input.  If the
 *          input character is invalid, outside of {0..9,A-F,a-f}, it will
 *          return -1.
 */
inline int parse_hex_char(const int c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    else if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    else if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    else
        return -1;
}


/**
 *  Class which Renders a single byte as hexadecimal
 */
class RenderHexByte
{
  public:
    /**
     *  Initializes a new object
     *
     *  @param byte  Unsigned char (one byte) to be processed
     *  @param caps  Boolean (default false) which sets the outout to
     *               be either lower case (false) or upper case (true).
     */
    RenderHexByte(const unsigned char byte, const bool caps = false)
    {
        c[0] = render_hex_char(byte >> 4, caps);
        c[1] = render_hex_char(byte & 0x0F, caps);
    }

    char char1() const
    {
        return c[0];
    }
    char char2() const
    {
        return c[1];
    }

    /**
     *  Retrieve the hexadecimal representation of the value.
     *
     *  @warning The result is a non-NULL terminated string.
     *
     *  @return Returns a non-NULL terminated 2 byte string with the hexadecimal
     *          representation of the initial value.  The return value is guaranteed
     *          to always be 2 bytes.
     */
    const char *str2() const
    {
        return c;
    } // Note: length=2, NOT null terminated

  private:
    char c[2];
};


/**
 *  Render a byte buffer (unsigned char *) as a hexadecimal string.
 *
 *  @param data  Unsigned char pointer to buffer to render.
 *  @param size  size_t of the number of bytes to parse from the buffer.
 *  @param caps  Boolean (default false) which sets the outout to
 *               be either lower case (false) or upper case (true).
 *
 *  @return Returns a std::string of the complete hexadecimal representation
 */
inline std::string render_hex(const unsigned char *data, size_t size, const bool caps = false)
{
    if (!data)
        return "NULL";
    std::string ret;
    ret.reserve(size * 2 + 1);
    while (size--)
    {
        const RenderHexByte b(*data++, caps);
        ret += b.char1();
        ret += b.char2();
    }
    return ret;
}


/**
 *  Render a byte buffer (void *) as a hexadecimal string.
 *
 *  @param data  Void pointer to buffer to render.
 *  @param size  size_t of the number of bytes to parse from the buffer.
 *  @param caps  Boolean (default false) which sets the outout to
 *               be either lower case (false) or upper case (true).
 *
 *  @return Returns a std::string of the complete hexadecimal representation.
 */
inline std::string render_hex(const void *data, const size_t size, const bool caps = false)
{
    return render_hex((const unsigned char *)data, size, caps);
}


/**
 *  Variant of \c render_hex(const unsiged char *,...) which adds a
 *  separator between each byte
 *
 *  @param data  Unsigned char pointer to buffer to render.
 *  @param size  size_t of the number of bytes to parse from the buffer.
 *  @param sep   A single character to use as the separator.
 *  @param caps  Boolean (default false) which sets the outout to
 *               be either lower case (false) or upper case (true).
 *
 *  @return Returns a std::string of the complete hexadecimal representation
 *          with each byte separated by a given character.
 */
inline std::string render_hex_sep(const unsigned char *data, size_t size, const char sep, const bool caps = false)
{
    if (!data)
        return "NULL";
    std::string ret;
    ret.reserve(size * 3);
    bool prsep = false;
    while (size--)
    {
        if (prsep)
            ret += sep;
        const RenderHexByte b(*data++, caps);
        ret += b.char1();
        ret += b.char2();
        prsep = true;
    }
    return ret;
}

/**
 *  Variant of \c render_hex(const void *,...) which adds a
 *  separator between each byte

 *  @param data  Void pointer to buffer to render.
 *  @param size  size_t of the number of bytes to parse from the buffer.
 *  @param sep   A single character to use as the separator.
 *  @param caps  Boolean (default false) which sets the outout to
 *               be either lower case (false) or upper case (true).
 *
 *  @return Returns a std::string of the complete hexadecimal representation
 *          with each byte separated by a given character.
 */
inline std::string render_hex_sep(const void *data, const size_t size, const char sep, const bool caps = false)
{
    return render_hex_sep((const unsigned char *)data, size, sep, caps);
}


/**
 *  Render a std::vector<T> container as a hexadecimal string.
 *  T must be a data type compatible with
 *  RenderHexByte(const unsigned char,...)
 *
 *  @param data   std::vector<T> containing the data to render
 *  @param caps   Boolean (default false) which sets the outout to
 *                be either lower case (false) or upper case (true).
 *
 *  @return Returns a std::string of the complete hexadecimal representation.
 */
template <typename V>
inline std::string render_hex_generic(const V &data, const bool caps = false)
{
    std::string ret;
    ret.reserve(data.size() * 2 + 1);
    for (size_t i = 0; i < data.size(); ++i)
    {
        const RenderHexByte b(data[i], caps);
        ret += b.char1();
        ret += b.char2();
    }
    return ret;
}


/**
 *  Renders a combined hexadecimal and character dump of a buffer,
 *  with the typical 16 bytes split between hexadecimal and character
 *  separation per line.
 *
 *  @param  data  Unsigned char pointer to the buffer to dump.
 *  @param  size  Size of the buffer to render.
 *
 *  @return Returns a string containing a preformatted output of the
 *          hexadecimal dump.
 */
inline std::string dump_hex(const unsigned char *data, size_t size)
{
    if (!data)
        return "NULL\n";
    const unsigned int mask = 0x0F; // N bytes per line - 1
    std::ostringstream os;
    os << std::hex;
    std::string chars;
    size_t i;
    for (i = 0; i < size; ++i)
    {
        if (!(i & mask))
        {
            if (i)
            {
                os << "  " << chars << std::endl;
                chars.clear();
            }
            os << std::setfill(' ') << std::setw(8) << i << ":";
        }
        const unsigned char c = data[i];
        os << ' ' << std::setfill('0') << std::setw(2) << (unsigned int)c;
        if (string::is_printable(c))
            chars += c;
        else
            chars += '.';
    }
    if (i)
        os << string::spaces(clamp_to_typerange<int>(2 + (((i - 1) & mask) ^ mask) * 3)) << chars << std::endl;
    return os.str();
}

/**
 *  Renders a combined hexadecimal and character dump of a buffer,
 *  with the typical 16 bytes split between hexadecimal and character
 *  separation per line.
 *
 *  @param  data  Void pointer to the buffer to dump.
 *  @param  size  Size of the buffer to render.
 *
 *  @return Returns a string containing a preformatted output of the
 *          hexadecimal dump.
 */
inline std::string dump_hex(void *data, size_t size)
{
    return dump_hex((const unsigned char *)data, size);
}

/**
 *  Renders a combined hexadecimal and character dump of a std::string buffer,
 *  with the typical 16 bytes split between hexadecimal and character
 *  separation per line.
 *
 *  @param  str  std::string containing the buffer to render
 *
 *  @return Returns a string containing a preformatted output of the
 *          hexadecimal dump.
 */
inline std::string dump_hex(const std::string &str)
{
    return dump_hex((const unsigned char *)str.c_str(), str.length());
}


/**
 *  Renders a combined hexadecimal and character dump of a std::vector<T>
 *  based buffer, with the typical 16 bytes split between hexadecimal and
 *  character separation per line.
 *
 *  @param  data  std::vector<T> containing the buffer to render
 *
 *  @return Returns a string containing a preformatted output of the
 *          hexadecimal dump.
 */
template <typename V>
inline std::string dump_hex(const V &data)
{
    return dump_hex(data.c_data(), data.size());
}

/**
 *  Declaration of a hexadecimal parsing error exception class
 */
OPENVPN_SIMPLE_EXCEPTION(parse_hex_error);


/**
 *  Parses a std::string containing a hexadecimal value into
 *  a std::vector<T>.
 *
 *  @param dest  std::vector<T> destination buffer to use.
 *  @param str   std::string& containing the hexadecimal string to parse.
 *
 *  @throws parse_hex_error will throw this exception if the input is
 *                          invalid/not parsable as a hexadecimal number.
 */
template <typename V>
inline void parse_hex(V &dest, const std::string &str)
{
    using vvalue_t = typename V::value_type;
    const int len = int(str.length());
    int i;
    for (i = 0; i <= len - 2; i += 2)
    {
        const int high = parse_hex_char(str[i]);
        const int low = parse_hex_char(str[i + 1]);
        if (high == -1 || low == -1)
            throw parse_hex_error();
        dest.push_back(static_cast<vvalue_t>((high << 4) + low));
    }
    if (i != len)
        throw parse_hex_error(); // straggler char
}


/**
 *  Parses a char buffer (C string) containing a hexadecimal
 *  string into a templated (T) variable.  The input buffer
 *  MUST be NULL terminated.
 *
 *  @warning There are _NO_ overflow checks.
 *
 *  @param str    Char pointer (char *) to the buffer to be parsed.
 *  @param retval Return buffer where the parsed value is stored.
 *
 *  @return Returns true on successful parsing, otherwise false.
 */
template <typename T>
inline bool parse_hex_number(const char *str, T &retval)
{
    if (!str[0])
        return false; // empty string
    size_t i = 0;
    T ret = T(0);
    while (true)
    {
        const char c = str[i++];
        const int hd = parse_hex_char(c);
        if (hd >= 0)
        {
            ret *= T(16);
            ret += T(hd);
        }
        else if (!c)
        {
            retval = ret;
            return true;
        }
        else
            return false; // non-hex-digit
    }
}


/**
 *  Variant of \c parse_hex_number(const char *, ...) which takes a std::string
 *  as the input.
 *
 *  @param str    std::string containing the hexadecimal string to be parsed.
 *  @param retval Return buffer where the parsed value is stored.
 *
 *  @return Returns true on successful parsing, otherwise false.
 */
template <typename T>
inline bool parse_hex_number(const std::string &str, T &retval)
{
    return parse_hex_number(str.c_str(), retval);
}


/**
 *  Parses a std::string containing a hexadecimal
 *  string into a templated (T) variable.
 *
 *  @remark Currently doesn't detect overflow
 *
 *  @param str    std::string containing the hexadecimal
 *                string to be parsed.
 *
 *  @return Returns a template T variable containing the
 *          parsed value on success.  Will throw the parse_hex_error
 *          exception on parsing errors.
 *
 */
template <typename T>
inline T parse_hex_number(const std::string &str)
{
    T ret;
    if (!parse_hex_number<T>(str.c_str(), ret))
        throw parse_hex_error();
    return ret;
}

/**
 *  Renders a templated T variable containing a numeric value
 *  into a std::string containing a hexadecimal representation.
 *
 *  @param value  Numeric (T) value to represent as hexadecimal.
 *  @param caps   Boolean (default false) which sets the outout to
 *                be either lower case (false) or upper case (true).
 *
 *  @return Retuns a std::string containing the hexadecimal
 *          representation on succes.  Will throw a parse_hex_error
 *          exception on parsing errors.
 */
template <typename T>
std::string render_hex_number(T value, const bool caps = false)
{
    unsigned char buf[sizeof(T)];
    for (size_t i = sizeof(T); i-- > 0;)
    {
        buf[i] = value & 0xFF;
        value >>= 8;
    }
    return render_hex(buf, sizeof(T), caps);
}


/**
 *  Renders a single byte as a hexadecimal string
 *
 *  @param uc     Unsigned char (byte) to be represented as hexadecimal.
 *  @param caps   Boolean (default false) which sets the outout to
 *                be either lower case (false) or upper case (true).
 *
 *  @return Returns a std::string with the hexadecimal representation
 *          of the input value.  The result will always contain only
 *          two characters.
 */
inline std::string render_hex_number(unsigned char uc, const bool caps = false)
{
    RenderHexByte b(uc, caps);
    return std::string(b.str2(), 2);
}

} // namespace openvpn

#endif // OPENVPN_COMMON_HEXSTR_H
