/*

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

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

#include <libtorrent/hasher.hpp>
#include <libtorrent/kademlia/item.hpp>
#include <libtorrent/bencode.hpp>
#include <libtorrent/kademlia/ed25519.hpp>
#include <libtorrent/aux_/numeric_cast.hpp>

#include <cstdio> // for snprintf
#include <cinttypes> // for PRId64 et.al.
#include <algorithm> // for copy

#if TORRENT_USE_ASSERTS
#include "libtorrent/bdecode.hpp"
#endif

namespace libtorrent { namespace dht {

namespace {

	int canonical_string(span<char const> v
		, sequence_number const seq
		, span<char const> salt
		, span<char> out)
	{
		// v must be valid bencoding!
#if TORRENT_USE_ASSERTS
		bdecode_node e;
		error_code ec;
		TORRENT_ASSERT(bdecode(v.data(), v.data() + v.size(), e, ec) == 0);
#endif
		char* ptr = out.data();

		auto left = out.size() - (ptr - out.data());
		// snprintf returns the number of characters it would have written had
		// there been room, which may exceed "left". Clamp the advance so ptr
		// never moves past the end of "out"; otherwise "left" goes negative and
		// the std::min below hands std::copy a negative count.
		if (!salt.empty())
		{
			int const n =
				std::snprintf(ptr, static_cast<std::size_t>(left), "4:salt%d:", int(salt.size()));
			ptr += std::min<std::ptrdiff_t>(n, left);
			left = out.size() - (ptr - out.data());
			std::copy(salt.begin(), salt.begin() + std::min(salt.size(), left), ptr);
			ptr += std::min(salt.size(), left);
			left = out.size() - (ptr - out.data());
		}
		int const n =
			std::snprintf(ptr, static_cast<std::size_t>(left), "3:seqi%" PRId64 "e1:v", seq.value);
		ptr += std::min<std::ptrdiff_t>(n, left);
		left = out.size() - (ptr - out.data());
		std::copy(v.begin(), v.begin() + std::min(v.size(), left), ptr);
		ptr += std::min(v.size(), left);
		TORRENT_ASSERT((ptr - out.data()) <= int(out.size()));
		return int(ptr - out.data());
	}
}

// calculate the target hash for an immutable item.
sha1_hash item_target_id(span<char const> v)
{
	return hasher(v).final();
}

// calculate the target hash for a mutable item.
sha1_hash item_target_id(span<char const> salt
	, public_key const& pk)
{
	hasher h(pk.bytes);
	if (!salt.empty()) h.update(salt);
	return h.final();
}

bool verify_mutable_item(
	span<char const> v
	, span<char const> salt
	, sequence_number const seq
	, public_key const& pk
	, signature const& sig)
{
	// the BEP44 salt is capped at 64 bytes. a larger salt can never be part of
	// a valid item, so fail validation rather than hand canonical_string an
	// input that doesn't fit its buffer.
	if (salt.size() > 64) return false;

	char str[1200];
	int len = canonical_string(v, seq, salt, str);

	return ed25519_verify(sig, {str, len}, pk);
}

// given the bencoded buffer ``v``, the salt (which is optional and may have
// a length of zero to be omitted), sequence number ``seq``, public key (32
// bytes ed25519 key) ``pk`` and a secret/private key ``sk`` (64 bytes ed25519
// key) a signature ``sig`` is produced. The ``sig`` pointer must point to
// at least 64 bytes of available space. This space is where the signature is
// written.
signature sign_mutable_item(
	span<char const> v
	, span<char const> salt
	, sequence_number const seq
	, public_key const& pk
	, secret_key const& sk)
{
	char str[1200];
	int const len = canonical_string(v, seq, salt, str);

	return ed25519_sign({str, len}, pk, sk);
}

item::item(public_key const& pk, span<char const> salt)
	: m_salt(salt.data(), static_cast<std::size_t>(salt.size()))
	, m_pk(pk)
	, m_mutable(true)
{}

item::item(entry v)
	: m_value(std::move(v))
{}

item::item(bdecode_node const& v)
{
	// TODO: implement ctor for entry from bdecode_node?
	m_value = v;
}

item::item(entry v, span<char const> salt
	, sequence_number const seq, public_key const& pk, secret_key const& sk)
{
	assign(std::move(v), salt, seq, pk, sk);
}

void item::assign(entry v)
{
	m_mutable = false;
	m_value = std::move(v);
}

void item::assign(entry v, span<char const> salt
	, sequence_number const seq, public_key const& pk, secret_key const& sk)
{
	std::array<char, 1000> buffer;
	int const bsize = bencode(buffer.begin(), v);
	TORRENT_ASSERT(bsize <= 1000);
	m_sig = sign_mutable_item(span<char const>(buffer).first(bsize)
		, salt, seq, pk, sk);
	m_salt.assign(salt.data(), static_cast<std::size_t>(salt.size()));
	m_pk = pk;
	m_seq = seq;
	m_mutable = true;
	m_value = std::move(v);
}

void item::assign(bdecode_node const& v)
{
	m_mutable = false;
	m_value = v;
}

bool item::assign(bdecode_node const& v, span<char const> salt
	, sequence_number const seq, public_key const& pk, signature const& sig)
{
	TORRENT_ASSERT(v.data_section().size() <= 1000);
	if (!verify_mutable_item(v.data_section(), salt, seq, pk, sig))
		return false;
	m_pk = pk;
	m_sig = sig;
	if (!salt.empty())
		m_salt.assign(salt.data(), static_cast<std::size_t>(salt.size()));
	else
		m_salt.clear();
	m_seq = seq;
	m_mutable = true;

	m_value = v;
	return true;
}

void item::assign(entry v, span<char const> salt
	, sequence_number const seq
	, public_key const& pk, signature const& sig)
{

	m_pk = pk;
	m_sig = sig;
	m_salt.assign(salt.data(), static_cast<std::size_t>(salt.size()));
	m_seq = seq;
	m_mutable = true;
	m_value = std::move(v);
}

} } // namespace libtorrent::dht
