/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * (c) ZeroTier, Inc.
 * https://www.zerotier.com/
 */

#include "CertificateOfMembership.hpp"

#include "ECC.hpp"
#include "Network.hpp"
#include "Node.hpp"
#include "RuntimeEnvironment.hpp"
#include "Switch.hpp"
#include "Topology.hpp"

namespace ZeroTier {

CertificateOfMembership::CertificateOfMembership(uint64_t timestamp, uint64_t timestampMaxDelta, uint64_t nwid, const Identity& issuedTo)
{
	_qualifiers[0].id = COM_RESERVED_ID_TIMESTAMP;
	_qualifiers[0].value = timestamp;
	_qualifiers[0].maxDelta = timestampMaxDelta;
	_qualifiers[1].id = COM_RESERVED_ID_NETWORK_ID;
	_qualifiers[1].value = nwid;
	_qualifiers[1].maxDelta = 0;
	_qualifiers[2].id = COM_RESERVED_ID_ISSUED_TO;
	_qualifiers[2].value = issuedTo.address().toInt();
	_qualifiers[2].maxDelta = 0xffffffffffffffffULL;

	// Include hash of full identity public key in COM for hardening purposes. Pack it in
	// using the original COM format. Format may be revised in the future to make this cleaner.
	uint64_t idHash[6];
	issuedTo.publicKeyHash(idHash);
	for (unsigned long i = 0; i < 4; ++i) {
		_qualifiers[i + 3].id = (uint64_t)(i + 3);
		_qualifiers[i + 3].value = Utils::ntoh(idHash[i]);
		_qualifiers[i + 3].maxDelta = 0xffffffffffffffffULL;
	}

	_qualifierCount = 7;
	memset(_signature.data, 0, ZT_ECC_SIGNATURE_LEN);
}

bool CertificateOfMembership::agreesWith(const CertificateOfMembership& other, const Identity& otherIdentity) const
{
	if ((_qualifierCount == 0) || (other._qualifierCount == 0)) {
		return false;
	}

	std::map<uint64_t, uint64_t> otherFields;
	for (unsigned int i = 0; i < other._qualifierCount; ++i) {
		otherFields[other._qualifiers[i].id] = other._qualifiers[i].value;
	}

	bool fullIdentityVerification = false;
	for (unsigned int i = 0; i < _qualifierCount; ++i) {
		const uint64_t qid = _qualifiers[i].id;
		if ((qid >= 3) && (qid <= 6)) {
			fullIdentityVerification = true;
		}
		std::map<uint64_t, uint64_t>::iterator otherQ(otherFields.find(qid));
		if (otherQ == otherFields.end()) {
			return false;
		}
		const uint64_t a = _qualifiers[i].value;
		const uint64_t b = otherQ->second;
		if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[i].maxDelta) {
			return false;
		}
	}

	// If this COM has a full hash of its identity, assume the other must have this as well.
	// Otherwise we are on a controller that does not incorporate these.
	if (fullIdentityVerification) {
		uint64_t idHash[6];
		otherIdentity.publicKeyHash(idHash);
		for (unsigned long i = 0; i < 4; ++i) {
			std::map<uint64_t, uint64_t>::iterator otherQ(otherFields.find((uint64_t)(i + 3)));
			if (otherQ == otherFields.end()) {
				return false;
			}
			if (otherQ->second != Utils::ntoh(idHash[i])) {
				return false;
			}
		}
	}

	return true;
}

bool CertificateOfMembership::sign(const Identity& with)
{
	uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
	unsigned int ptr = 0;
	for (unsigned int i = 0; i < _qualifierCount; ++i) {
		buf[ptr++] = Utils::hton(_qualifiers[i].id);
		buf[ptr++] = Utils::hton(_qualifiers[i].value);
		buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
	}

	try {
		_signature = with.sign(buf, ptr * sizeof(uint64_t));
		_signedBy = with.address();
		return true;
	}
	catch (...) {
		_signedBy.zero();
		return false;
	}
}

int CertificateOfMembership::verify(const RuntimeEnvironment* RR, void* tPtr) const
{
	if ((! _signedBy) || (_signedBy != Network::controllerFor(networkId())) || (_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) {
		return -1;
	}

	const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
	if (! id) {
		RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
		return 1;
	}

	uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
	unsigned int ptr = 0;
	for (unsigned int i = 0; i < _qualifierCount; ++i) {
		buf[ptr++] = Utils::hton(_qualifiers[i].id);
		buf[ptr++] = Utils::hton(_qualifiers[i].value);
		buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
	}
	return (id.verify(buf, ptr * sizeof(uint64_t), _signature) ? 0 : -1);
}

}	// namespace ZeroTier
