/*
 * Copyright (c)2013-2020 ZeroTier, Inc.
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file in the project's root directory.
 *
 * Change Date: 2024-01-01
 *
 * On the date above, in accordance with the Business Source License, use
 * of this software will be governed by version 2.0 of the Apache License.
 */
/****/

#ifndef ZT_TRACE_HPP
#define ZT_TRACE_HPP

#include "Constants.hpp"
#include "SharedPtr.hpp"
#include "Mutex.hpp"
#include "InetAddress.hpp"
#include "Address.hpp"
#include "MAC.hpp"
#include "Containers.hpp"
#include "Utils.hpp"

#define ZT_TRACE_F_VL1           0x01U
#define ZT_TRACE_F_VL2           0x02U
#define ZT_TRACE_F_VL2_FILTER    0x04U
#define ZT_TRACE_F_VL2_MULTICAST 0x08U

namespace ZeroTier {

class RuntimeEnvironment;
class Identity;
class Peer;
class Path;
class Network;
class MembershipCredential;
class OwnershipCredential;
class RevocationCredential;
class TagCredential;
class CapabilityCredential;
struct NetworkConfig;

/**
 * Remote tracing and trace logging handler
 *
 * These methods are called when things happen that may be of interested to
 * someone debugging ZeroTier or its virtual networks. The codeLocation parameter
 * is an arbitrary pseudo-random identifier of the form 0xNNNNNNNN that could be
 * easily found by searching the code base. This makes it easy to locate the
 * specific line where a trace originated without relying on brittle non-portable
 * things like source file and line number. The same identifier should be used
 * for the same 'place' in the code across versions. These could eventually be
 * turned into constants that are semi-official and stored in a database to
 * provide extra debug context.
 */
class Trace
{
public:
	struct RuleResultLog : public TriviallyCopyable
	{
		uint8_t l[ZT_MAX_NETWORK_RULES / 2]; // ZT_MAX_NETWORK_RULES 4-bit fields

		ZT_INLINE void log(const unsigned int rn,const uint8_t thisRuleMatches,const uint8_t thisSetMatches) noexcept
		{
			l[rn >> 1U] |= ( ((thisRuleMatches + 1U) << 2U) | (thisSetMatches + 1U) ) << ((rn & 1U) << 2U);
		}
		ZT_INLINE void logSkipped(const unsigned int rn,const uint8_t thisSetMatches) noexcept
		{
			l[rn >> 1U] |= (thisSetMatches + 1U) << ((rn & 1U) << 2U);
		}
		ZT_INLINE void clear() noexcept
		{
			memoryZero(this);
		}
	};

	explicit Trace(const RuntimeEnvironment *renv);

	void unexpectedError(
		void *tPtr,
		uint32_t codeLocation,
		const char *message,
		...);

	ZT_INLINE void resettingPathsInScope(
		void *const tPtr,
		const uint32_t codeLocation,
		const Identity &reporter,
		const InetAddress &from,
		const InetAddress &oldExternal,
		const InetAddress &newExternal,
		const InetAddress::IpScope scope)
	{
		if ((_f & ZT_TRACE_F_VL1) != 0)
			_resettingPathsInScope(tPtr,codeLocation,reporter,from,oldExternal,newExternal,scope);
	}

	ZT_INLINE void tryingNewPath(
		void *const tPtr,
		const uint32_t codeLocation,
		const Identity &trying,
		const InetAddress &physicalAddress,
		const InetAddress &triggerAddress,
		uint64_t triggeringPacketId,
		uint8_t triggeringPacketVerb,
		const Identity &triggeringPeer)
	{
		if ((_f & ZT_TRACE_F_VL1) != 0)
			_tryingNewPath(tPtr,codeLocation,trying,physicalAddress,triggerAddress,triggeringPacketId,triggeringPacketVerb,triggeringPeer);
	}

	ZT_INLINE void learnedNewPath(
		void *const tPtr,
		const uint32_t codeLocation,
		uint64_t packetId,
		const Identity &peerIdentity,
		const InetAddress &physicalAddress,
		const InetAddress &replaced)
	{
		if ((_f & ZT_TRACE_F_VL1) != 0)
			_learnedNewPath(tPtr,codeLocation,packetId,peerIdentity,physicalAddress,replaced);
	}

	ZT_INLINE void incomingPacketDropped(
		void *const tPtr,
		const uint32_t codeLocation,
		uint64_t packetId,
		uint64_t networkId,
		const Identity &peerIdentity,
		const InetAddress &physicalAddress,
		uint8_t hops,
		uint8_t verb,
		const ZT_TracePacketDropReason reason)
	{
		if ((_f & ZT_TRACE_F_VL1) != 0)
			_incomingPacketDropped(tPtr,codeLocation,packetId,networkId,peerIdentity,physicalAddress,hops,verb,reason);
	}

	ZT_INLINE void outgoingNetworkFrameDropped(
		void *const tPtr,
		const uint32_t codeLocation,
		uint64_t networkId,
		const MAC &sourceMac,
		const MAC &destMac,
		uint16_t etherType,
		uint16_t frameLength,
		const uint8_t *frameData,
		ZT_TraceFrameDropReason reason)
	{
		if ((_f & ZT_TRACE_F_VL2) != 0)
			_outgoingNetworkFrameDropped(tPtr,codeLocation,networkId,sourceMac,destMac,etherType,frameLength,frameData,reason);
	}

	ZT_INLINE void incomingNetworkFrameDropped(
		void *const tPtr,
		const uint32_t codeLocation,
		uint64_t networkId,
		const MAC &sourceMac,
		const MAC &destMac,
		const Identity &peerIdentity,
		const InetAddress &physicalAddress,
		uint8_t hops,
		uint16_t frameLength,
		const uint8_t *frameData,
		uint8_t verb,
		bool credentialRequestSent,
		ZT_TraceFrameDropReason reason)
	{
		if ((_f & ZT_TRACE_F_VL2) != 0)
			_incomingNetworkFrameDropped(tPtr,codeLocation,networkId,sourceMac,destMac,peerIdentity,physicalAddress,hops,frameLength,frameData,verb,credentialRequestSent,reason);
	}

	ZT_INLINE void networkConfigRequestSent(
		void *const tPtr,
		const uint32_t codeLocation,
		uint64_t networkId)
	{
		if ((_f & ZT_TRACE_F_VL2) != 0)
			_networkConfigRequestSent(tPtr,codeLocation,networkId);
	}

	ZT_INLINE void networkFilter(
		void *const tPtr,
		const uint32_t codeLocation,
		uint64_t networkId,
		const uint8_t primaryRuleSetLog[512],
		const uint8_t matchingCapabilityRuleSetLog[512],
		uint32_t matchingCapabilityId,
		int64_t matchingCapabilityTimestamp,
		const Address &source,
		const Address &dest,
		const MAC &sourceMac,
		const MAC &destMac,
		uint16_t frameLength,
		const uint8_t *frameData,
		uint16_t etherType,
		uint16_t vlanId,
		bool noTee,
		bool inbound,
		int accept)
	{
		if ((_f & ZT_TRACE_F_VL2_FILTER) != 0) {
			_networkFilter(
				tPtr,
				codeLocation,
				networkId,
				primaryRuleSetLog,
				matchingCapabilityRuleSetLog,
				matchingCapabilityId,
				matchingCapabilityTimestamp,
				source,
				dest,
				sourceMac,
				destMac,
				frameLength,
				frameData,
				etherType,
				vlanId,
				noTee,
				inbound,
				accept);
		}
	}

	ZT_INLINE void credentialRejected(
		void *const tPtr,
		const uint32_t codeLocation,
		uint64_t networkId,
		const Identity &identity,
		uint32_t credentialId,
		int64_t credentialTimestamp,
		uint8_t credentialType,
		ZT_TraceCredentialRejectionReason reason)
	{
		if ((_f & ZT_TRACE_F_VL2) != 0)
			_credentialRejected(tPtr,codeLocation,networkId,identity,credentialId,credentialTimestamp,credentialType,reason);
	}

private:
	void _resettingPathsInScope(
		void *tPtr,
		uint32_t codeLocation,
		const Identity &reporter,
		const InetAddress &from,
		const InetAddress &oldExternal,
		const InetAddress &newExternal,
		InetAddress::IpScope scope);
	void _tryingNewPath(
		void *tPtr,
		uint32_t codeLocation,
		const Identity &trying,
		const InetAddress &physicalAddress,
		const InetAddress &triggerAddress,
		uint64_t triggeringPacketId,
		uint8_t triggeringPacketVerb,
		const Identity &triggeringPeer);
	void _learnedNewPath(
		void *tPtr,
		uint32_t codeLocation,
		uint64_t packetId,
		const Identity &peerIdentity,
		const InetAddress &physicalAddress,
		const InetAddress &replaced);
	void _incomingPacketDropped(
		void *tPtr,
		uint32_t codeLocation,
		uint64_t packetId,
		uint64_t networkId,
		const Identity &peerIdentity,
		const InetAddress &physicalAddress,
		uint8_t hops,
		uint8_t verb,
		ZT_TracePacketDropReason reason);
	void _outgoingNetworkFrameDropped(
		void *tPtr,
		uint32_t codeLocation,
		uint64_t networkId,
		const MAC &sourceMac,
		const MAC &destMac,
		uint16_t etherType,
		uint16_t frameLength,
		const uint8_t *frameData,
		ZT_TraceFrameDropReason reason);
	void _incomingNetworkFrameDropped(
		void *tPtr,
		uint32_t codeLocation,
		uint64_t networkId,
		const MAC &sourceMac,
		const MAC &destMac,
		const Identity &peerIdentity,
		const InetAddress &physicalAddress,
		uint8_t hops,
		uint16_t frameLength,
		const uint8_t *frameData,
		uint8_t verb,
		bool credentialRequestSent,
		ZT_TraceFrameDropReason reason);
	void _networkConfigRequestSent(
		void *tPtr,
		uint32_t codeLocation,
		uint64_t networkId);
	void _networkFilter(
		void *tPtr,
		uint32_t codeLocation,
		uint64_t networkId,
		const uint8_t primaryRuleSetLog[512],
		const uint8_t matchingCapabilityRuleSetLog[512],
		uint32_t matchingCapabilityId,
		int64_t matchingCapabilityTimestamp,
		const Address &source,
		const Address &dest,
		const MAC &sourceMac,
		const MAC &destMac,
		uint16_t frameLength,
		const uint8_t *frameData,
		uint16_t etherType,
		uint16_t vlanId,
		bool noTee,
		bool inbound,
		int accept);
	void _credentialRejected(
		void *tPtr,
		uint32_t codeLocation,
		uint64_t networkId,
		const Identity &identity,
		uint32_t credentialId,
		int64_t credentialTimestamp,
		uint8_t credentialType,
		ZT_TraceCredentialRejectionReason reason);

	const RuntimeEnvironment *const RR;
	volatile unsigned int _f; // faster than atomic, but may not "instantly" change... will after next memory fence
};

} // namespace ZeroTier

#endif