#ifndef PACKETPP_ETH_LAYER
#define PACKETPP_ETH_LAYER

#include "Layer.h"
#include "MacAddress.h"

/// @file

/**
 * \namespace pcpp
 * \brief The main namespace for the PcapPlusPlus lib
 */
namespace pcpp
{

	/**
	 * @struct ether_header
	 * Represents an Ethernet II header
	 */
#pragma pack(push, 1)
	struct ether_header
	{
		/** Destination MAC */
		uint8_t dstMac[6];
		/** Source MAC */
		uint8_t srcMac[6];
		/** EtherType */
		uint16_t etherType;
	};
#pragma pack(pop)

	/* Ethernet protocol ID's */

	/** IP */
#define PCPP_ETHERTYPE_IP            0x0800
	/** Address resolution */
#define PCPP_ETHERTYPE_ARP           0x0806
	/** Transparent Ethernet Bridging */
#define PCPP_ETHERTYPE_ETHBRIDGE     0x6558
	/** Reverse ARP */
#define PCPP_ETHERTYPE_REVARP        0x8035
	/** AppleTalk protocol */
#define PCPP_ETHERTYPE_AT            0x809B
	/** AppleTalk ARP */
#define PCPP_ETHERTYPE_AARP          0x80F3
	/** IEEE 802.1Q VLAN tagging */
#define PCPP_ETHERTYPE_VLAN          0x8100
	/** IPX */
#define PCPP_ETHERTYPE_IPX           0x8137
	/** IP protocol version 6 */
#define PCPP_ETHERTYPE_IPV6          0x86dd
	/** used to test interfaces */
#define PCPP_ETHERTYPE_LOOPBACK      0x9000
	/** PPPoE discovery */
#define PCPP_ETHERTYPE_PPPOED        0x8863
	/** PPPoE session */
#define PCPP_ETHERTYPE_PPPOES        0x8864
	/** MPLS */
#define PCPP_ETHERTYPE_MPLS          0x8847
	/** Point-to-point protocol (PPP) */
#define PCPP_ETHERTYPE_PPP           0x880B
	/** RDMA over Converged Ethernet (RoCEv1) */
#define PCPP_ETHERTYPE_ROCEV1        0x8915
	/** IEEE 802.1ad Provider Bridge, Q-in-Q */
#define PCPP_ETHERTYPE_IEEE_802_1AD  0x88A8
	/** Wake on LAN */
#define PCPP_ETHERTYPE_WAKE_ON_LAN   0x0842


	/**
	 * @class EthLayer
	 * Represents an Ethernet II protocol layer
	 */
	class EthLayer : public Layer
	{
	public:
		/**
		 * A constructor that creates the layer from an existing packet raw data
		 * @param[in] data A pointer to the raw data (will be casted to ether_header)
		 * @param[in] dataLen Size of the data in bytes
		 * @param[in] packet A pointer to the Packet instance where layer will be stored in
		 */
		EthLayer(uint8_t* data, size_t dataLen, Packet* packet) : Layer(data, dataLen, NULL, packet) { m_Protocol = Ethernet; }

		/**
		 * A constructor that creates the layer from an existing packet raw data
		 * @param[in] data A pointer to the raw data (will be casted to ether_header)
		 * @param[in] dataLen Size of the data in bytes
		 * @param[in] prevLayer A pointer to the previous layer
		 * @param[in] packet A pointer to the Packet instance where layer will be stored in
		 */
		EthLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) : Layer(data, dataLen, prevLayer, packet) { m_Protocol = Ethernet; }

		/**
		 * A constructor that creates a new Ethernet header and allocates the data
		 * @param[in] sourceMac The source MAC address
		 * @param[in] destMac The destination MAC address
		 * @param[in] etherType The EtherType to be used. It's an optional parameter, a value of 0 will be set if not provided
		 */
		EthLayer(const MacAddress& sourceMac, const MacAddress& destMac, uint16_t etherType = 0);

		~EthLayer() {}

		/**
		 * Get a pointer to the Ethernet header. Notice this points directly to the data, so every change will change the actual packet data
		 * @return A pointer to the ether_header
		 */
		inline ether_header* getEthHeader() const { return (ether_header*)m_Data; }

		/**
		 * Get the source MAC address
		 * @return The source MAC address
		 */
		inline MacAddress getSourceMac() const { return MacAddress(getEthHeader()->srcMac); }

		/**
		 * Set source MAC address
		 * @param sourceMac Source MAC to set
		 */
		inline void setSourceMac(const MacAddress& sourceMac) { sourceMac.copyTo(getEthHeader()->srcMac); }

		/**
		 * Get the destination MAC address
		 * @return The destination MAC address
		 */
		inline MacAddress getDestMac() const { return MacAddress(getEthHeader()->dstMac); }

		/**
		 * Set destination MAC address
		 * @param destMac Destination MAC to set
		 */
		inline void setDestMac(const MacAddress& destMac) { destMac.copyTo(getEthHeader()->dstMac); }

		// implement abstract methods

		/**
		 * Currently identifies the following next layers: IPv4Layer, IPv6Layer, ArpLayer, VlanLayer, PPPoESessionLayer, PPPoEDiscoveryLayer,
		 * MplsLayer.
		 * Otherwise sets PayloadLayer
		 */
		void parseNextLayer();

		/**
		 * @return Size of ether_header
		 */
		size_t getHeaderLen() const { return sizeof(ether_header); }

		/**
		 * Calculate ether_header#etherType for known protocols: IPv4, IPv6, ARP, VLAN
		 */
		void computeCalculateFields();

		std::string toString() const;

		OsiModelLayer getOsiModelLayer() const { return OsiModelDataLinkLayer; }

		/**
		 * A static method that validates the input data
		 * @param[in] data The pointer to the beginning of a byte stream of an Ethernet II packet
		 * @param[in] dataLen The length of the byte stream
		 * @return True if the data is valid and can represent an Ethernet II packet
		 */
		static bool isDataValid(const uint8_t* data, size_t dataLen);
	};

} // namespace pcpp

#endif /* PACKETPP_ETH_LAYER */
