//               Copyright Maarten L. Hekkelman.
//   Distributed under the Boost Software License, Version 1.0.
//      (See accompanying file LICENSE_1_0.txt or copy at
//            http://www.boost.org/LICENSE_1_0.txt)

#include "zeep/crypto.hpp"

#include <algorithm>
#include <bit>
#include <cassert>
#include <cctype>
#include <climits>
#include <cstdint>
#include <cstring>
#include <random>
#include <streambuf>

namespace zeep
{

// --------------------------------------------------------------------
// encoding/decoding

const char kBase64CharTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

const uint8_t kBase64IndexTable[128] = {
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	62,  // +
	128, // not used
	128, // not used
	128, // not used
	63,  // /
	52,  // 0
	53,  // 1
	54,  // 2
	55,  // 3
	56,  // 4
	57,  // 5
	58,  // 6
	59,  // 7
	60,  // 8
	61,  // 9
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	0,   // A
	1,   // B
	2,   // C
	3,   // D
	4,   // E
	5,   // F
	6,   // G
	7,   // H
	8,   // I
	9,   // J
	10,  // K
	11,  // L
	12,  // M
	13,  // N
	14,  // O
	15,  // P
	16,  // Q
	17,  // R
	18,  // S
	19,  // T
	20,  // U
	21,  // V
	22,  // W
	23,  // X
	24,  // Y
	25,  // Z
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	26,  // a
	27,  // b
	28,  // c
	29,  // d
	30,  // e
	31,  // f
	32,  // g
	33,  // h
	34,  // i
	35,  // j
	36,  // k
	37,  // l
	38,  // m
	39,  // n
	40,  // o
	41,  // p
	42,  // q
	43,  // r
	44,  // s
	45,  // t
	46,  // u
	47,  // v
	48,  // w
	49,  // x
	50,  // y
	51,  // z
	128, // not used
	128, // not used
	128, // not used
	128, // not used
	128, // not used
};

inline uint8_t sextet(char ch)
{
	if (ch < '+' or ch > 'z' or kBase64IndexTable[static_cast<uint8_t>(ch)] >= 128)
		throw invalid_base64();

	return kBase64IndexTable[static_cast<uint8_t>(ch)];
}

std::string encode_base64(std::string_view data, size_t wrap_width)
{
	std::string::size_type n = data.length();
	std::string::size_type m = 4 * (n / 3);
	if (n % 3)
		m += 4;

	if (wrap_width != 0)
		m += (m / wrap_width) + 1;

	std::string result;
	result.reserve(m);

	auto ch = data.begin();
	size_t l = 0;

	while (n > 0)
	{
		char s[4] = { '=', '=', '=', '=' };

		switch (n)
		{
			case 1:
			{
				uint8_t i = *ch++;
				s[0] = kBase64CharTable[i >> 2];
				s[1] = kBase64CharTable[(i << 4) bitand 0x03f];

				n -= 1;
				break;
			}

			case 2:
			{
				uint8_t i1 = *ch++;
				uint8_t i2 = *ch++;

				s[0] = kBase64CharTable[i1 >> 2];
				s[1] = kBase64CharTable[(i1 << 4 bitor i2 >> 4) bitand 0x03f];
				s[2] = kBase64CharTable[(i2 << 2) bitand 0x03f];

				n -= 2;
				break;
			}

			default:
			{
				uint8_t i1 = *ch++;
				uint8_t i2 = *ch++;
				uint8_t i3 = *ch++;

				s[0] = kBase64CharTable[i1 >> 2];
				s[1] = kBase64CharTable[(i1 << 4 bitor i2 >> 4) bitand 0x03f];
				s[2] = kBase64CharTable[(i2 << 2 bitor i3 >> 6) bitand 0x03f];
				s[3] = kBase64CharTable[i3 bitand 0x03f];

				n -= 3;
				break;
			}
		}

		if (wrap_width == 0)
			result.append(s, s + 4);
		else
		{
			for (char i : s)
			{
				if (l == wrap_width)
				{
					result.append(1, '\n');
					l = 0;
				}

				result.append(1, i);
				++l;
			}
		}
	}

	if (wrap_width != 0)
		result.append(1, '\n');

	assert(result.length() == m);

	return result;
}

std::string decode_base64(std::string_view data)
{
	size_t n = data.length();
	size_t m = 3 * (n / 4);

	std::string result;
	result.reserve(m);

	auto i = data.begin();

	while (i != data.end())
	{
		uint8_t sxt[4] = {};
		int b = 0, c = 3;

		while (b < 4)
		{
			if (i == data.end())
				break;

			char ch = *i++;

			switch (ch)
			{
				case ' ':
				case '\t':
				case '\n':
				case '\r':
					break;

				case '=':
					if (b == 2 and *i++ == '=')
					{
						c = 1;
						b = 4;
					}
					else if (b == 3)
					{
						c = 2;
						b = 4;
					}
					else
						throw invalid_base64();
					break;

				default:
					sxt[b] = sextet(ch);
					++b;
					break;
			}
		}

		if (b == 4)
		{
			result.append(1, static_cast<char>(sxt[0] << 2 bitor sxt[1] >> 4));
			if (c >= 2)
				result.append(1, static_cast<char>(sxt[1] << 4 bitor sxt[2] >> 2));
			if (c == 3)
				result.append(1, static_cast<char>(sxt[2] << 6 bitor sxt[3]));
		}
		else if (b != 0)
			throw invalid_base64();
	}

	return result;
}

std::string encode_base64url(std::string_view data)
{
	std::string result = encode_base64(data);

	while (not result.empty() and result.back() == '=')
		result.pop_back();

	for (auto p = result.find('+'); p != std::string::npos; p = result.find('+', p + 1))
		result.replace(p, 1, 1, '-');
	for (auto p = result.find('/'); p != std::string::npos; p = result.find('/', p + 1))
		result.replace(p, 1, 1, '_');

	return result;
}

std::string decode_base64url(std::string data)
{
	for (auto p = data.find('-'); p != std::string::npos; p = data.find('-', p + 1))
		data.replace(p, 1, 1, '+');
	for (auto p = data.find('_'); p != std::string::npos; p = data.find('_', p + 1))
		data.replace(p, 1, 1, '/');

	switch (data.length() % 4)
	{
		case 0: break;
		case 2: data.append(2, '='); break;
		case 3: data.append(1, '='); break;
		default: throw invalid_base64();
	}

	return decode_base64(data);
}

// --------------------------------------------------------------------

const char kBase32CharTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

const uint8_t kBase32IndexTable[128] = {
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	26, // 2
	27, // 3
	28, // 4
	29, // 5
	30, // 6
	31, // 7
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	0,  // A
	1,  // B
	2,  // C
	3,  // D
	4,  // E
	5,  // F
	6,  // G
	7,  // H
	8,  // I
	9,  // J
	10, // K
	11, // L
	12, // M
	13, // N
	14, // O
	15, // P
	16, // Q
	17, // R
	18, // S
	19, // T
	20, // U
	21, // V
	22, // W
	23, // X
	24, // Y
	25, // Z
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	0,  // a
	1,  // b
	2,  // c
	3,  // d
	4,  // e
	5,  // f
	6,  // g
	7,  // h
	8,  // i
	9,  // j
	10, // k
	11, // l
	12, // m
	13, // n
	14, // o
	15, // p
	16, // q
	17, // r
	18, // s
	19, // t
	20, // u
	21, // v
	22, // w
	23, // x
	24, // y
	25, // z
	32, // not used
	32, // not used
	32, // not used
	32, // not used
	32, // not used
};

inline size_t quintet(char ch)
{
	if (ch < '2' or ch > 'z' or kBase32IndexTable[static_cast<uint8_t>(ch)] >= 32)
		throw invalid_base32();

	return kBase32IndexTable[static_cast<uint8_t>(ch)];
}

std::string encode_base32(std::string_view data, size_t wrap_width)
{
	std::string::size_type n = data.length();
	std::string::size_type m = 8 * (n / 5);
	if (n % 5)
		m += 8;

	if (wrap_width != 0)
		m += (m / wrap_width) + 1;

	std::string result;
	result.reserve(m);

	auto ch = data.begin();
	size_t l = 0;

	while (n > 0)
	{
		char s[8] = { '=', '=', '=', '=', '=', '=', '=', '=' };

		switch (n)
		{
			case 1:
			{
				uint8_t i1 = *ch++;

				s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f];
				s[1] = kBase32CharTable[(i1 << 2) bitand 0x01f];

				n -= 1;
				break;
			}

			case 2:
			{
				uint8_t i1 = *ch++;
				uint8_t i2 = *ch++;
				//	i1	i2
				s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f];               //  5
				s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; //  3	2
				s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f];               //		5
				s[3] = kBase32CharTable[(i2 << 4) bitand 0x01f];               //		1

				n -= 2;
				break;
			}

			case 3:
			{
				uint8_t i1 = *ch++;
				uint8_t i2 = *ch++;
				uint8_t i3 = *ch++;
				//	i1	i2	i3
				s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f];               //  5
				s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; //  3	2
				s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f];               //		5
				s[3] = kBase32CharTable[(i2 << 4 bitor i3 >> 4) bitand 0x01f]; //		1	4
				s[4] = kBase32CharTable[(i3 << 1) bitand 0x01f];               //			4

				n -= 3;
				break;
			}

			case 4:
			{
				uint8_t i1 = *ch++;
				uint8_t i2 = *ch++;
				uint8_t i3 = *ch++;
				uint8_t i4 = *ch++;
				//	i1	i2	i3	i4
				s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f];               //  5
				s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; //  3	2
				s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f];               //		5
				s[3] = kBase32CharTable[(i2 << 4 bitor i3 >> 4) bitand 0x01f]; //		1	4
				s[4] = kBase32CharTable[(i3 << 1) bitand 0x01f];               //			4	1
				s[5] = kBase32CharTable[(i4 >> 2) bitand 0x01f];               //				5
				s[6] = kBase32CharTable[(i4 << 3) bitand 0x01f];               //				2

				n -= 4;
				break;
			}

			default:
			{
				uint8_t i1 = *ch++;
				uint8_t i2 = *ch++;
				uint8_t i3 = *ch++;
				uint8_t i4 = *ch++;
				uint8_t i5 = *ch++;
				//	i1	i2	i3	i4	i5
				s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f];               //  5
				s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; //  3	2
				s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f];               //		5
				s[3] = kBase32CharTable[(i2 << 4 bitor i3 >> 4) bitand 0x01f]; //		1	4
				s[4] = kBase32CharTable[(i3 << 1 bitor i4 >> 7) bitand 0x01f]; //			4	1
				s[5] = kBase32CharTable[(i4 >> 2) bitand 0x01f];               //				5
				s[6] = kBase32CharTable[(i4 << 3 bitor i5 >> 5) bitand 0x01f]; //				2	3
				s[7] = kBase32CharTable[(i5) bitand 0x01f];                    //					5

				n -= 5;
				break;
			}
		}

		if (wrap_width == 0)
			result.append(s, s + 8);
		else
		{
			for (char i : s)
			{
				if (l == wrap_width)
				{
					result.append(1, '\n');
					l = 0;
				}

				result.append(1, i);
				++l;
			}
		}
	}

	if (wrap_width != 0)
		result.append(1, '\n');

	assert(result.length() == m);

	return result;
}

std::string decode_base32(std::string_view data)
{
	size_t n = data.length();
	size_t m = 8 * (n / 5);

	std::string result;
	result.reserve(m);

	auto i = data.begin();

	int padding = 0;
	while (i != data.end() and not padding)
	{
		uint8_t qnt[5] = {};
		int c = 0;

		while (c + padding != 8 and i != data.end())
		{
			auto ch = *i++;

			if (std::isspace(ch))
				continue;

			if (padding)
			{
				if (ch != '=')
					throw invalid_base32();

				++padding;
				continue;
			}

			if (ch == '=')
			{
				++padding;
				continue;
			}

			if (ch < '2' or ch > 'z' or kBase32IndexTable[static_cast<uint8_t>(ch)] >= 32)
				throw invalid_base32();

			auto b = kBase32IndexTable[static_cast<uint8_t>(ch)];

			switch (c++)
			{
				case 0: qnt[0] |= b << 3; break;
				case 1:
					qnt[0] |= b >> 2;
					qnt[1] = b << 6;
					break;
				case 2: qnt[1] |= b << 1; break;
				case 3:
					qnt[1] |= b >> 4;
					qnt[2] = b << 4;
					break;
				case 4:
					qnt[2] |= b >> 1;
					qnt[3] = b << 7;
					break;
				case 5: qnt[3] |= b << 2; break;
				case 6:
					qnt[3] |= b >> 3;
					qnt[4] = b << 5;
					break;
				case 7: qnt[4] |= b; break;
				default:;
			}
		}

		if (c + padding != 8)
			throw invalid_base32();

		switch (c)
		{
			case 2: result.append(qnt, qnt + 1); break;
			case 4: result.append(qnt, qnt + 2); break;
			case 5: result.append(qnt, qnt + 3); break;
			case 7: result.append(qnt, qnt + 4); break;
			case 8: result.append(qnt, qnt + 5); break;
			default:
				throw invalid_base32();
		}
	}

	while (i != data.end())
	{
		if (not std::isspace(*i++))
			throw invalid_base32();
	}

	return result;
}

// --------------------------------------------------------------------
// hex

std::string encode_hex(std::string_view data)
{
	// convert to hex
	const char kHexChars[] = "0123456789abcdef";
	std::string result;
	result.reserve(data.length() * 2);

	for (uint8_t b : data)
	{
		result += kHexChars[b >> 4];
		result += kHexChars[b bitand 0x0f];
	}

	return result;
}

std::string decode_hex(std::string_view data)
{
	if (data.length() % 2 == 1)
		throw invalid_hex();

	std::string result;
	result.reserve(data.length() / 2);

	for (std::string::size_type i = 0; i < data.length(); i += 2)
	{
		uint8_t n[2] = {};

		for (int j = 0; j < 2; ++j)
		{
			auto ch = data[i + j];
			if (ch >= '0' and ch <= '9')
				n[j] = ch - '0';
			else if (ch >= 'a' and ch <= 'f')
				n[j] = ch - 'a' + 10;
			else if (ch >= 'A' and ch <= 'F')
				n[j] = ch - 'A' + 10;
			else
				throw invalid_hex();
		}

		result.push_back(static_cast<char>(n[0] << 4 bitor n[1]));
	}

	return result;
}

// --------------------------------------------------------------------
// random

std::string random_hash()
{
	std::random_device rng;

	union
	{
		uint32_t data[4];
		char s[4 * 4];
	} v = { { rng(), rng(), rng(), rng() } };

	return { v.s, v.s + sizeof(v) };
}

// --------------------------------------------------------------------
// hashes

// --------------------------------------------------------------------

#if _WIN32
# pragma warning(disable : 4146) // unary minus operator applied to unsigned type, result still unsigned
#endif

constexpr inline uint32_t rotl32(uint32_t n, unsigned int c)
{
	const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2.

	// assert ( (c<=mask) &&"rotate by type width or more");
	c &= mask;
	return (n << c) bitor (n >> ((-c) & mask));
}

constexpr inline uint32_t rotr32(uint32_t n, unsigned int c)
{
	const unsigned int mask = (CHAR_BIT * sizeof(n) - 1);

	// assert ( (c<=mask) &&"rotate by type width or more");
	c &= mask;
	return (n >> c) bitor (n << ((-c) & mask));
}

// --------------------------------------------------------------------

struct hash_impl
{
	virtual ~hash_impl() = default;

	virtual void write_bit_length(uint64_t l, uint8_t *b) = 0;
	virtual void transform(const uint8_t *data) = 0;
	virtual std::string final() = 0;
};

// --------------------------------------------------------------------

constexpr auto F1(uint32_t x, uint32_t y, uint32_t z)
{
	return (z xor (x bitand (y xor z)));
}

constexpr auto F2(uint32_t x, uint32_t y, uint32_t z)
{
	return F1(z, x, y);
}

constexpr auto F3(uint32_t x, uint32_t y, uint32_t z)
{
	return (x xor y xor z);
}

constexpr auto F4(uint32_t x, uint32_t y, uint32_t z)
{
	return (y xor (x bitor compl z));
}

template <typename F>
constexpr void STEP(F f, uint32_t &w, uint32_t x, uint32_t y, uint32_t z, uint32_t data, uint32_t s)
{
	w += f(x, y, z) + data;
	w = rotl32(w, s);
	w += x;
}

struct md5_hash_impl : public hash_impl // NOLINT(hicpp-member-init)
{
	using word_type = uint32_t;
	static const size_t word_count = 4;
	static const size_t block_size = 64;
	static const size_t digest_size = word_count * sizeof(word_type);

	word_type m_h[word_count];

	virtual void init()
	{
		m_h[0] = 0x67452301;
		m_h[1] = 0xefcdab89;
		m_h[2] = 0x98badcfe;
		m_h[3] = 0x10325476;
	}

	void write_bit_length(uint64_t l, uint8_t *p) override
	{
		for (int i = 0; i < 8; ++i)
			*p++ = static_cast<uint8_t>((l >> (i * 8)));
	}

	void transform(const uint8_t *data) override
	{
		uint32_t a = m_h[0], b = m_h[1], c = m_h[2], d = m_h[3];
		uint32_t in[16];

		for (unsigned int &i : in)
		{
			i = static_cast<uint32_t>(data[0]) << 0 |
			    static_cast<uint32_t>(data[1]) << 8 |
			    static_cast<uint32_t>(data[2]) << 16 |
			    static_cast<uint32_t>(data[3]) << 24;
			data += 4;
		}

		STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
		STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
		STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
		STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
		STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
		STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
		STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
		STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
		STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
		STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
		STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
		STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
		STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
		STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
		STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
		STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);

		STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
		STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
		STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
		STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
		STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
		STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
		STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
		STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
		STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
		STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
		STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
		STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
		STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
		STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
		STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
		STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);

		STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
		STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
		STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
		STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
		STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
		STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
		STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
		STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
		STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
		STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
		STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
		STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
		STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
		STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
		STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
		STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);

		STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
		STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
		STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
		STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
		STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
		STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
		STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
		STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
		STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
		STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
		STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
		STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
		STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
		STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
		STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
		STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);

		m_h[0] += a;
		m_h[1] += b;
		m_h[2] += c;
		m_h[3] += d;
	}

	std::string final() override
	{
		std::string result;
		result.reserve(digest_size);

		for (unsigned int i : m_h)
		{
			for (size_t j = 0; j < sizeof(word_type); ++j)
			{
				auto b = static_cast<char>(i >> (j * 8));
				result.push_back(b);
			}
		}

		return result;
	}
};

// --------------------------------------------------------------------

struct sha1_hash_impl : public hash_impl // NOLINT(hicpp-member-init)
{
	using word_type = uint32_t;
	static const size_t word_count = 5;
	static const size_t block_size = 64;
	static const size_t digest_size = word_count * sizeof(word_type);

	word_type m_h[word_count];

	virtual void init()
	{
		m_h[0] = 0x67452301;
		m_h[1] = 0xEFCDAB89;
		m_h[2] = 0x98BADCFE;
		m_h[3] = 0x10325476;
		m_h[4] = 0xC3D2E1F0;
	}

	void write_bit_length(uint64_t l, uint8_t *b) override
	{
		if constexpr (std::endian::native == std::endian::big)
			memcpy(b, &l, sizeof(l));
		else
		{
			b[0] = static_cast<uint8_t>(l >> 56);
			b[1] = static_cast<uint8_t>(l >> 48);
			b[2] = static_cast<uint8_t>(l >> 40);
			b[3] = static_cast<uint8_t>(l >> 32);
			b[4] = static_cast<uint8_t>(l >> 24);
			b[5] = static_cast<uint8_t>(l >> 16);
			b[6] = static_cast<uint8_t>(l >> 8);
			b[7] = static_cast<uint8_t>(l >> 0);
		}
	}

	void transform(const uint8_t *data) override
	{
		union // NOLINT(hicpp-member-init)
		{
			uint8_t s[64];
			uint32_t w[80];
		} w;

		if constexpr (std::endian::native == std::endian::big)
			memcpy(w.s, data, 64);
		else
		{

			auto p = data;
			for (size_t i = 0; i < 16; ++i)
			{
				w.s[i * 4 + 3] = *p++;
				w.s[i * 4 + 2] = *p++;
				w.s[i * 4 + 1] = *p++;
				w.s[i * 4 + 0] = *p++;
			}
		}

		for (size_t i = 16; i < 80; ++i)
			w.w[i] = rotl32(w.w[i - 3] xor w.w[i - 8] xor w.w[i - 14] xor w.w[i - 16], 1);

		word_type wv[word_count];

		for (size_t i = 0; i < word_count; ++i)
			wv[i] = m_h[i];

		for (size_t i = 0; i < 80; ++i)
		{
			uint32_t f, k;
			if (i < 20)
			{
				f = (wv[1] bitand wv[2]) bitor ((compl wv[1]) bitand wv[3]);
				k = 0x5A827999;
			}
			else if (i < 40)
			{
				f = wv[1] xor wv[2] xor wv[3];
				k = 0x6ED9EBA1;
			}
			else if (i < 60)
			{
				f = (wv[1] bitand wv[2]) bitor (wv[1] bitand wv[3]) bitor (wv[2] bitand wv[3]);
				k = 0x8F1BBCDC;
			}
			else
			{
				f = wv[1] xor wv[2] xor wv[3];
				k = 0xCA62C1D6;
			}

			uint32_t t = rotl32(wv[0], 5) + f + wv[4] + k + w.w[i];

			wv[4] = wv[3];
			wv[3] = wv[2];
			wv[2] = rotl32(wv[1], 30);
			wv[1] = wv[0];
			wv[0] = t;
		}

		for (size_t i = 0; i < word_count; ++i)
			m_h[i] += wv[i];
	}

	std::string final() override
	{
		std::string result(digest_size, '\0');

		if constexpr (std::endian::native == std::endian::big)
			memcpy(const_cast<char *>(result.data()), &m_h, digest_size);
		else
		{
			auto s = result.begin();
			for (unsigned int i : m_h)
			{
				*s++ = static_cast<char>(i >> 24);
				*s++ = static_cast<char>(i >> 16);
				*s++ = static_cast<char>(i >> 8);
				*s++ = static_cast<char>(i >> 0);
			}
		}

		return result;
	}
};

// --------------------------------------------------------------------

struct sha256_hash_impl : public hash_impl // NOLINT(hicpp-member-init)
{
	using word_type = uint32_t;
	static const size_t word_count = 8;
	static const size_t block_size = 64;
	static const size_t digest_size = word_count * sizeof(word_type);

	word_type m_h[word_count];

	virtual void init()
	{
		m_h[0] = 0x6a09e667;
		m_h[1] = 0xbb67ae85;
		m_h[2] = 0x3c6ef372;
		m_h[3] = 0xa54ff53a;
		m_h[4] = 0x510e527f;
		m_h[5] = 0x9b05688c;
		m_h[6] = 0x1f83d9ab;
		m_h[7] = 0x5be0cd19;
	}

	void write_bit_length(uint64_t l, uint8_t *b) override
	{
		if constexpr (std::endian::native == std::endian::big)
			memcpy(b, &l, sizeof(l));
		else
		{

			b[0] = static_cast<uint8_t>(l >> 56);
			b[1] = static_cast<uint8_t>(l >> 48);
			b[2] = static_cast<uint8_t>(l >> 40);
			b[3] = static_cast<uint8_t>(l >> 32);
			b[4] = static_cast<uint8_t>(l >> 24);
			b[5] = static_cast<uint8_t>(l >> 16);
			b[6] = static_cast<uint8_t>(l >> 8);
			b[7] = static_cast<uint8_t>(l >> 0);
		}
	}

	void transform(const uint8_t *data) override
	{
		static const uint32_t k[] = {
			0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
			0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
			0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
			0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
			0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
			0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
			0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
			0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
		};

		word_type wv[word_count];
		for (size_t i = 0; i < word_count; ++i)
			wv[i] = m_h[i];

		union // NOLINT(hicpp-member-init)
		{
			uint8_t s[64];
			uint32_t w[64];
		} w;

		if constexpr (std::endian::native == std::endian::big)
			memcpy(w.w, data, 64);
		else
		{
			auto p = data;
			for (size_t i = 0; i < 16; ++i)
			{
				w.s[i * 4 + 3] = *p++;
				w.s[i * 4 + 2] = *p++;
				w.s[i * 4 + 1] = *p++;
				w.s[i * 4 + 0] = *p++;
			}
		}

		for (size_t i = 16; i < 64; ++i)
		{
			auto s0 = rotr32(w.w[i - 15], 7) xor rotr32(w.w[i - 15], 18) xor (w.w[i - 15] >> 3);
			auto s1 = rotr32(w.w[i - 2], 17) xor rotr32(w.w[i - 2], 19) xor (w.w[i - 2] >> 10);
			w.w[i] = w.w[i - 16] + s0 + w.w[i - 7] + s1;
		}

		for (size_t i = 0; i < 64; ++i)
		{
			uint32_t S1 = rotr32(wv[4], 6) xor rotr32(wv[4], 11) xor rotr32(wv[4], 25);
			uint32_t ch = (wv[4] bitand wv[5]) xor (compl wv[4] bitand wv[6]);
			uint32_t t1 = wv[7] + S1 + ch + k[i] + w.w[i];
			uint32_t S0 = rotr32(wv[0], 2) xor rotr32(wv[0], 13) xor rotr32(wv[0], 22);
			uint32_t maj = (wv[0] bitand wv[1]) xor (wv[0] bitand wv[2]) xor (wv[1] bitand wv[2]);
			uint32_t t2 = S0 + maj;

			wv[7] = wv[6];
			wv[6] = wv[5];
			wv[5] = wv[4];
			wv[4] = wv[3] + t1;
			wv[3] = wv[2];
			wv[2] = wv[1];
			wv[1] = wv[0];
			wv[0] = t1 + t2;
		}

		for (size_t i = 0; i < word_count; ++i)
			m_h[i] += wv[i];
	}

	std::string final() override
	{
		std::string result(digest_size, '\0');

		if constexpr (std::endian::native == std::endian::big)
			memcpy(const_cast<char *>(result.data()), &m_h, digest_size);
		else
		{
			auto s = result.begin();
			for (unsigned int i : m_h)
			{
				*s++ = static_cast<char>(i >> 24);
				*s++ = static_cast<char>(i >> 16);
				*s++ = static_cast<char>(i >> 8);
				*s++ = static_cast<char>(i >> 0);
			}
		}

		return result;
	}
};

// --------------------------------------------------------------------

template <typename I>
class hash_base : public I
{
  public:
	using word_type = typename I::word_type;
	static const size_t word_count = I::word_count;
	static const size_t block_size = I::block_size;
	static const size_t digest_size = I::digest_size;

	hash_base() // NOLINT(hicpp-member-init)
	{
		I::init();
	}

	void init() override
	{
		I::init();

		m_data_length = 0;
		m_bit_length = 0;
	}

	void update(std::string_view data);
	void update(const uint8_t *data, size_t n);

	using I::transform;
	std::string final() override;

  private:
	uint8_t m_data[block_size];
	uint32_t m_data_length = 0;
	int64_t m_bit_length = 0;
};

template <typename I>
void hash_base<I>::update(std::string_view data)
{
	update(reinterpret_cast<const uint8_t *>(data.data()), data.size());
}

template <typename I>
void hash_base<I>::update(const uint8_t *p, size_t length)
{
	m_bit_length += length * 8;

	if (m_data_length > 0)
	{
		uint32_t n = block_size - m_data_length;
		if (n > length)
			n = static_cast<uint32_t>(length);

		memcpy(m_data + m_data_length, p, n);
		m_data_length += n;

		if (m_data_length == block_size)
		{
			transform(m_data);
			m_data_length = 0;
		}

		p += n;
		length -= n;
	}

	while (length >= block_size)
	{
		transform(p);
		p += block_size;
		length -= block_size;
	}

	if (length > 0)
	{
		memcpy(m_data, p, length);
		m_data_length += static_cast<uint32_t>(length);
	}
}

template <typename I>
std::string hash_base<I>::final()
{
	m_data[m_data_length] = 0x80;
	++m_data_length;
	std::fill(m_data + m_data_length, m_data + block_size, static_cast<uint8_t>(0));

	if (block_size - m_data_length < 8)
	{
		transform(m_data);
		std::fill(m_data, m_data + block_size - 8, static_cast<uint8_t>(0));
	}

	I::write_bit_length(m_bit_length, m_data + block_size - 8);

	transform(m_data);
	std::fill(m_data, m_data + block_size, static_cast<uint8_t>(0));

	auto result = I::final();
	init();
	return result;
}

using MD5 = hash_base<md5_hash_impl>;
using SHA1 = hash_base<sha1_hash_impl>;
using SHA256 = hash_base<sha256_hash_impl>;

// --------------------------------------------------------------------

std::string sha1(std::string_view data)
{
	SHA1 h;
	h.init();
	h.update(data);
	return h.final();
}

std::string sha1(std::streambuf &data)
{
	SHA1 h;
	h.init();

	while (data.in_avail() > 0)
	{
		uint8_t buffer[256];
		auto n = data.sgetn(reinterpret_cast<char *>(buffer), sizeof(buffer));
		h.update(buffer, n);
	}

	return h.final();
}

std::string sha256(std::string_view data)
{
	SHA256 h;
	h.init();
	h.update(data);
	return h.final();
}

std::string md5(std::string_view data)
{
	MD5 h;
	h.init();
	h.update(data);
	return h.final();
}

// --------------------------------------------------------------------
// hmac

template <typename H>
class HMAC
{
  public:
	static const size_t block_size = H::block_size;
	static const size_t digest_size = H::digest_size;

	explicit HMAC(std::string_view key)
		: m_ipad(block_size, '\x36')
		, m_opad(block_size, '\x5c')
	{
		std::string key_data{ key };

		if (key.length() > block_size)
		{
			H keyHash;
			keyHash.update(key);
			key_data = keyHash.final();
		}

		assert(key_data.length() < block_size);

		for (size_t i = 0; i < key_data.length(); ++i)
		{
			m_opad[i] ^= key_data[i];
			m_ipad[i] ^= key_data[i];
		}
	}

	HMAC &update(std::string_view data)
	{
		if (not m_inner_updated)
		{
			m_inner.update(m_ipad);
			m_inner_updated = true;
		}

		m_inner.update(data);
		return *this;
	}

	std::string final()
	{
		H outer;
		outer.update(m_opad);
		outer.update(m_inner.final());
		m_inner_updated = false;
		return outer.final();
	}

  private:
	std::string m_ipad, m_opad;
	bool m_inner_updated = false;
	H m_inner;
};

std::string hmac_sha1(std::string_view message, std::string_view key)
{
	return HMAC<SHA1>(key).update(message).final();
}

std::string hmac_sha256(std::string_view message, std::string_view key)
{
	return HMAC<SHA256>(key).update(message).final();
}

std::string hmac_md5(std::string_view message, std::string_view key)
{
	return HMAC<MD5>(key).update(message).final();
}

// --------------------------------------------------------------------
// password/key derivation

template <typename HMAC>
std::string pbkdf2(std::string_view salt,
	std::string_view password, unsigned iterations, unsigned keyLength)
{
	std::string result;

	HMAC hmac(password);

	uint32_t i = 1;
	while (result.length() < keyLength)
	{
		hmac.update(salt);
		for (int j = 0; j < 4; ++j)
		{
			char b = static_cast<char>(i >> ((3 - j) * CHAR_BIT));
			hmac.update(std::string{ b });
		}
		auto derived = hmac.final();
		auto buffer = derived;

		for (unsigned c = 1; c < iterations; ++c)
		{
			buffer = hmac.update(buffer).final();

			for (size_t ix = 0; ix < buffer.length(); ++ix)
				derived[ix] ^= buffer[ix];
		}

		result.append(derived);
		++i;
	}

	if (result.length() > keyLength)
		result.erase(result.begin() + keyLength, result.end());

	return result;
}

/// create password hash according to PBKDF2 with HmacSHA1
std::string pbkdf2_hmac_sha1(std::string_view salt,
	std::string_view password, unsigned iterations, unsigned keyLength)
{
	return pbkdf2<HMAC<SHA1>>(salt, password, iterations, keyLength);
}

/// create password hash according to PBKDF2 with HmacSHA256
std::string pbkdf2_hmac_sha256(std::string_view salt,
	std::string_view password, unsigned iterations, unsigned keyLength)
{
	return pbkdf2<HMAC<SHA256>>(salt, password, iterations, keyLength);
}

} // namespace zeep