/*

Copyright (c) 2013-2021, Arvid Norberg
Copyright (c) 2016, Alden Torres
Copyright (c) 2017, Steven Siloti
Copyright (c) 2020, Mike Tzou
All rights reserved.

You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/

#ifndef TORRENT_SHA1_HASH_HPP_INCLUDED
#define TORRENT_SHA1_HASH_HPP_INCLUDED

#include <cctype>
#include <algorithm>
#include <string>
#include <cstring>
#include <array>

#include "libtorrent/aux_/disable_warnings_push.hpp"
#include <boost/range/combine.hpp>
#include "libtorrent/aux_/disable_warnings_pop.hpp"

#include "libtorrent/config.hpp"
#include "libtorrent/assert.hpp"
#include "libtorrent/aux_/byteswap.hpp"
#include "libtorrent/aux_/ffs.hpp"
#include "libtorrent/span.hpp"

#if TORRENT_USE_IOSTREAM
#include <iosfwd>
#endif // TORRENT_USE_IOSTREAM

namespace libtorrent {

	// This type holds an N digest or any other kind of N bits
	// sequence. It implements a number of convenience functions, such
	// as bit operations, comparison operators etc.
	//
	// This data structure is 32 bits aligned, like it's the case for
	// each SHA-N specification.
	template <std::ptrdiff_t N>
	class digest32
	{
		static_assert(N % 32 == 0, "N must be a multiple of 32");
		static constexpr std::ptrdiff_t number_size = N / 32;
		static constexpr int bits_in_byte = 8;
	public:

		using difference_type = std::ptrdiff_t;
		using index_type = std::ptrdiff_t;

		// the size of the hash in bytes
		static constexpr difference_type size() noexcept { return N / bits_in_byte; }

		// constructs an all-zero digest
		digest32() noexcept { clear(); }

		digest32(digest32 const&) noexcept = default;
		digest32& operator=(digest32 const&) noexcept = default;

		// returns an all-F digest. i.e. the maximum value
		// representable by an N bit number (N/8 bytes). This is
		// a static member function.
		static digest32 (max)() noexcept
		{
			digest32 ret;
			ret.m_number.fill(0xffffffff);
			return ret;
		}

		// returns an all-zero digest. i.e. the minimum value
		// representable by an N bit number (N/8 bytes). This is
		// a static member function.
		static digest32 (min)() noexcept
		{
			digest32 ret;
			// all bits are already 0
			return ret;
		}

		// copies N/8 bytes from the pointer provided, into the digest.
		// The passed in string MUST be at least N/8 bytes. 0-terminators
		// are ignored, ``s`` is treated like a raw memory buffer.
		explicit digest32(char const* s) noexcept
		{
			if (s == nullptr) clear();
			else std::memcpy(m_number.data(), s, size());
		}
#if TORRENT_ABI_VERSION == 1
		TORRENT_DEPRECATED
		explicit digest32(std::string const& s)
		{
			assign(s.data());
		}
#endif
		explicit digest32(span<char const> s) noexcept
		{
			assign(s);
		}
		void assign(span<char const> s) noexcept
		{
			TORRENT_ASSERT(s.size() >= N / bits_in_byte);
			auto const sl = s.size() < size() ? s.size() : size();
			std::memcpy(m_number.data(), s.data(), static_cast<std::size_t>(sl));
		}
		void assign(char const* str) noexcept { std::memcpy(m_number.data(), str, size()); }

		char const* data() const noexcept { return reinterpret_cast<char const*>(m_number.data()); }
		char* data() noexcept { return reinterpret_cast<char*>(m_number.data()); }

		// set the digest to all zeros.
		void clear() noexcept { m_number.fill(0); }

		// return true if the digest is all zero.
		bool is_all_zeros() const noexcept
		{
			return std::all_of(m_number.begin(), m_number.end()
				, [](std::uint32_t v) { return v == 0; });
		}

		// shift left or right ``n`` bits.
		digest32& operator<<=(int n) & noexcept;
		digest32& operator>>=(int n) & noexcept;

		// standard comparison operators
		bool operator==(digest32 const& n) const noexcept
		{
			return std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin());
		}
		bool operator!=(digest32 const& n) const noexcept
		{
			return !std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin());
		}
		bool operator<(digest32 const& n) const noexcept
		{
			for (auto const v : boost::combine(m_number, n.m_number))
			{
				std::uint32_t const lhs = aux::network_to_host(boost::get<0>(v));
				std::uint32_t const rhs = aux::network_to_host(boost::get<1>(v));
				if (lhs < rhs) return true;
				if (lhs > rhs) return false;
			}
			return false;
		}

		int count_leading_zeroes() const noexcept
		{
			return aux::count_leading_zeros(m_number);
		}

		// returns a bit-wise negated copy of the digest
		digest32 operator~() const noexcept
		{
			digest32 ret;
			for (auto const v : boost::combine(m_number, ret.m_number))
				boost::get<1>(v) = ~boost::get<0>(v);
			return ret;
		}

		// returns the bit-wise XOR of the two digests.
		digest32 operator^(digest32 const& n) const noexcept
		{
			digest32 ret = *this;
			ret ^= n;
			return ret;
		}

		// in-place bit-wise XOR with the passed in digest.
		digest32& operator^=(digest32 const& n) & noexcept
		{
			for (auto const v : boost::combine(m_number, n.m_number))
				boost::get<0>(v) ^= boost::get<1>(v);
			return *this;
		}

		// returns the bit-wise AND of the two digests.
		digest32 operator&(digest32 const& n) const noexcept
		{
			digest32 ret = *this;
			ret &= n;
			return ret;
		}

		// in-place bit-wise AND of the passed in digest
		digest32& operator&=(digest32 const& n) & noexcept
		{
			for (auto const v : boost::combine(m_number, n.m_number))
				boost::get<0>(v) &= boost::get<1>(v);
			return *this;
		}

		// in-place bit-wise OR of the two digests.
		digest32& operator|=(digest32 const& n) & noexcept
		{
			for (auto const v : boost::combine(m_number, n.m_number))
				boost::get<0>(v) |= boost::get<1>(v);
			return *this;
		}

		// accessors for specific bytes
		std::uint8_t& operator[](index_type i) noexcept
		{
			TORRENT_ASSERT(i < size());
			return reinterpret_cast<std::uint8_t*>(m_number.data())[i];
		}
		std::uint8_t const& operator[](index_type i) const noexcept
		{
			TORRENT_ASSERT(i < size());
			return reinterpret_cast<std::uint8_t const*>(m_number.data())[i];
		}

		using const_iterator = std::uint8_t const*;
		using iterator = std::uint8_t*;

		// start and end iterators for the hash. The value type
		// of these iterators is ``std::uint8_t``.
		const_iterator begin() const
		{ return reinterpret_cast<std::uint8_t const*>(m_number.data()); }
		const_iterator end() const
		{ return reinterpret_cast<std::uint8_t const*>(m_number.data()) + size(); }
		iterator begin()
		{ return reinterpret_cast<std::uint8_t*>(m_number.data()); }
		iterator end()
		{ return reinterpret_cast<std::uint8_t*>(m_number.data()) + size(); }

		// return a copy of the N/8 bytes representing the digest as a std::string.
		// It's still a binary string with N/8 binary characters.
		std::string to_string() const
		{
			return std::string(reinterpret_cast<char const*>(m_number.data()), size());
		}

#if TORRENT_USE_IOSTREAM
		// print a sha1_hash object to an ostream as 40 hexadecimal digits
		friend std::ostream& operator<<(std::ostream& os, digest32<N> const& val)
		{ val.stream_out(os); return os; }

		// read 40 hexadecimal digits from an istream into a sha1_hash
		friend std::istream& operator>>(std::istream& is, digest32<N>& val)
		{ val.stream_in(is); return is; }
#endif // TORRENT_USE_IOSTREAM

	private:

#if TORRENT_USE_IOSTREAM
		void stream_in(std::istream& is);
		void stream_out(std::ostream& os) const;
#endif // TORRENT_USE_IOSTREAM

		std::array<std::uint32_t, number_size> m_number;
	};

	// This type holds a SHA-1 digest or any other kind of 20 byte
	// sequence. It implements a number of convenience functions, such
	// as bit operations, comparison operators etc.
	//
	// In libtorrent it is primarily used to hold info-hashes, piece-hashes,
	// peer IDs, node IDs etc.
	using sha1_hash = digest32<160>;
	using sha256_hash = digest32<256>;

#if TORRENT_USE_IOSTREAM
	extern template void digest32<160>::stream_out(std::ostream&) const;
	extern template void digest32<256>::stream_out(std::ostream&) const;
	extern template void digest32<160>::stream_in(std::istream&);
	extern template void digest32<256>::stream_in(std::istream&);
#endif // TORRENT_USE_IOSTREAM

	extern template digest32<160>& digest32<160>::operator<<=(int) & noexcept;
	extern template digest32<256>& digest32<256>::operator<<=(int) & noexcept;
	extern template digest32<160>& digest32<160>::operator>>=(int) & noexcept;
	extern template digest32<256>& digest32<256>::operator>>=(int) & noexcept;
}

namespace std {
	template <std::ptrdiff_t N>
	struct hash<libtorrent::digest32<N>>
	{
		std::size_t operator()(libtorrent::digest32<N> const& k) const
		{
			std::size_t ret;
			static_assert(N >= sizeof(ret) * 8, "hash is not defined for small digests");
			// this is OK because digest32<N> is already a hash
			std::memcpy(&ret, k.data(), sizeof(ret));
			return ret;
		}
	};
}

#endif // TORRENT_SHA1_HASH_HPP_INCLUDED
