#ifndef PACKETPP_SOMEIP_LAYER
#define PACKETPP_SOMEIP_LAYER

#include "Layer.h"
#include <unordered_set>

/// @file

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

/**
 * @class SomeIpLayer
 * Represents a SOME/IP protocol layer
 */
class SomeIpLayer : public Layer
{
public:
	/**
	 * SOME/IP message types
	 */
	enum class MsgType : uint8_t
	{
		/** A request expecting a response (even void) */
		REQUEST = 0x00,
		/** Acknowledgment for REQUEST(optional) */
		REQUEST_ACK = 0x40,
		/** A fire&forget request */
		REQUEST_NO_RETURN = 0x01,
		/** Acknowledgment for REQUEST_NO_RETURN(informational) */
		REQUEST_NO_RETURN_ACK = 0x41,
		/** A request of a notification expecting no response */
		NOTIFICATION = 0x02,
		/** Acknowledgment for NOTIFICATION(informational) */
		NOTIFICATION_ACK = 0x42,
		/** The response message */
		RESPONSE = 0x80,
		/** The Acknowledgment for RESPONSE(informational) */
		RESPONSE_ACK = 0xC0,
		/** The response containing an error */
		ERRORS = 0x81,
		/** Acknowledgment for ERROR(informational) */
		ERROR_ACK = 0xC1,
		/** A TP request expecting a response (even void) */
		TP_REQUEST = 0x20,
		/** A TP fire&forget request */
		TP_REQUEST_NO_RETURN = 0x21,
		/** A TP request of a notification/event callback expecting no response */
		TP_NOTIFICATION = 0x22,
		/** The TP response message */
		TP_RESPONSE = 0xa0,
		/** The TP response containing an error */
		TP_ERROR = 0xa1,
	};

	/**
	 * @struct someiphdr
	 * Represents a SOME/IP protocol header
	 */
#pragma pack(push, 1)
	struct someiphdr
	{
		/** Service ID */
		uint16_t serviceID;
		/** Method ID. Most significant bit 0 when E2E communication. 1 when SOME/IP event */
		uint16_t methodID;
		/** Length. Also covers payload. Excludes serviceID, methodID and length field itself */
		uint32_t length;
		/** Client ID */
		uint16_t clientID;
		/** Session ID */
		uint16_t sessionID;
		/** Protocol Version */
		uint8_t protocolVersion;
		/** Interface Version */
		uint8_t interfaceVersion;
		/** Message Type */
		uint8_t msgType;
		/** Return Code */
		uint8_t returnCode;
	};
#pragma pack(pop)

	/**
	 * 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 someiphdr)
	 * @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
	 */
	SomeIpLayer(uint8_t *data, size_t dataLen, Layer *prevLayer, Packet *packet)
		: Layer(data, dataLen, prevLayer, packet)
	{
		m_Protocol = SomeIP;
	}

	/**
	 * Construct a new layer object
	 * @param[in] serviceID Service ID
	 * @param[in] methodID Method ID
	 * @param[in] clientID Client ID
	 * @param[in] sessionID Session ID
	 * @param[in] interfaceVersion Interface Version
	 * @param[in] type Type of the message
	 * @param[in] returnCode Return Code
	 * @param[in] data A pointer to the raw data
	 * @param[in] dataLen Size of the data in bytes
	 * holds the reference to a data buffer. This option can be used to reduce the number of copies to generate packets.
	 */
	SomeIpLayer(uint16_t serviceID, uint16_t methodID, uint16_t clientID, uint16_t sessionID, uint8_t interfaceVersion,
				MsgType type, uint8_t returnCode, const uint8_t *const data = nullptr, size_t dataLen = 0);

	/**
	 * Destroy the layer object
	 */
	~SomeIpLayer() {}

	/**
	 * A static method that creates a SOME/IP or SOME/IP-TP layer from packet raw data. Returns PayloadLayer if data is
	 * not valid.
	 * @param[in] data A pointer to the raw data
	 * @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
	 * @return Layer* A newly allocated layer
	 */
	static Layer* parseSomeIpLayer(uint8_t *data, size_t dataLen, Layer* prevLayer, Packet* packet);

	/**
	 * Get a pointer to the basic SOME/IP header. Notice this points directly to the data, so every change will change
	 * the actual packet data
	 * @return A pointer to the someiphdr
	 */
	someiphdr *getSomeIpHeader() const { return (someiphdr *)m_Data; }

	/**
	 * Checks if given port is a SOME/IP protocol port (only Service Discovery ports are checked for now)
	 * @param[in] port Port to check
	 * @return true if SOME/IP protocol port, false if not
	 */
	static bool isSomeIpPort(uint16_t port);

	/**
	 * Adds port to a list of ports where pcap checks for SOME/IP communication.
	 * Each port must be removed at the end in order to have no memory leak.
	 * @param[in] port Port to add
	 */
	static void addSomeIpPort(uint16_t port);

	/**
	 * Removes port from a list of ports where pcap checks for SOME/IP communication.
	 * @param[in] port Port to remove
	 */
	static void removeSomeIpPort(uint16_t port);

	/**
	 * Removes all ports from a list of ports where pcap checks for SOME/IP communication.
	 */
	static void removeAllSomeIpPorts();

	/**
	 * Get the messageID
	 * @return uint32_t returned in host endian
	 */
	uint32_t getMessageID() const;

	/**
	 * Set the Message ID
	 * @param[in] messageID messageID to set
	 */
	void setMessageID(uint32_t messageID);

	/**
	 * Get the serviceID
	 * @return uint16_t returned in host endian
	 */
	uint16_t getServiceID() const;

	/**
	 * Set the Service ID
	 * @param[in] serviceID serviceID to set
	 */
	void setServiceID(uint16_t serviceID);

	/**
	 * Get the methodID
	 * @return uint16_t returned in host endian
	 */
	uint16_t getMethodID() const;

	/**
	 * Set the Method ID
	 * @param[in] methodID methodID to set
	 */
	void setMethodID(uint16_t methodID);

	/**
	 * Get the Length Field of the SOME/IP header
	 * @return uint32_t The length field of the SOME/IP header
	 */
	uint32_t getLengthField() const;

	/**
	 * Get the requestID
	 * @return uint32_t returned in host endian
	 */
	uint32_t getRequestID() const;

	/**
	 * Set the Request ID
	 * @param[in] requestID requestID to set
	 */
	void setRequestID(uint32_t requestID);

	/**
	 * Get the sessionID
	 * @return uint16_t returned in host endian
	 */
	uint16_t getSessionID() const;

	/**
	 * Set the Session ID
	 * @param[in] sessionID sessionID to set
	 */
	void setSessionID(uint16_t sessionID);

	/**
	 * Get the clientID
	 * @return uint16_t returned in host endian
	 */
	uint16_t getClientID() const;

	/**
	 * Set the Client ID
	 * @param[in] clientID clientID to set
	 */
	void setClientID(uint16_t clientID);

	/**
	 * Get the protocolVersion
	 * @return uint8_t
	 */
	uint8_t getProtocolVersion() const;

	/**
	 * Set the Protocol Version
	 * @param[in] version version to set
	 */
	void setProtocolVersion(uint8_t version);

	/**
	 * Get the interfaceVersion
	 * @return uint8_t
	 */
	uint8_t getInterfaceVersion() const;

	/**
	 * Set the Interface Version
	 * @param[in] version version to set
	 */
	void setInterfaceVersion(uint8_t version);

	/**
	 * Get the message type
	 * @return uint8_t
	 */
	uint8_t getMessageTypeAsInt() const;

	/**
	 * Get the message type
	 * @return SomeIpLayer::MsgType
	 */
	SomeIpLayer::MsgType getMessageType() const;

	/**
	 * Set the Message Type
	 * @param[in] type Type to set
	 */
	void setMessageType(MsgType type);

	/**
	 * Set the Message Type
	 * @param[in] type Type to set
	 */
	void setMessageType(uint8_t type);

	/**
	 * Get the returnCode
	 * @return uint8_t
	 */
	uint8_t getReturnCode() const;

	/**
	 * Set the returnCode
	 * @param[in] returnCode ReturnCode to set
	 */
	void setReturnCode(uint8_t returnCode);

	/**
	 * Set the length field of the SOME/IP header
	 * @param[in] payloadLength Length of the payload
	 */
	void setPayloadLength(uint32_t payloadLength);

	/**
	 * @return A pointer for the layer payload, meaning the first byte after the header
	 */
	uint8_t *getPduPayload() const { return m_Data + getSomeIpHeaderLen(); }

	/**
	 * @return The size in bytes of the payload
	 */
	size_t getPduPayloadSize() const { return getHeaderLen() - getSomeIpHeaderLen(); }

	/**
	 * Get the Length of the SOME/IP header inc payload
	 * @return size_t
	 */
	size_t getHeaderLen() const { return sizeof(uint32_t) * 2 + getLengthField(); }

	/**
	 * Does nothing for this layer
	 */
	virtual void computeCalculateFields() {}

	/**
	 * Identifies the following next layers: SomeIpLayer, SomeIpTpLayer, SomeIpSdLayer. Otherwise sets PayloadLayer
	 */
	void parseNextLayer();

	/**
	 * @return The string representation of the SOME/IP layer
	 */
	virtual std::string toString() const;

	/**
	 * @return The OSI model layer of this layer
	 */
	OsiModelLayer getOsiModelLayer() const { return OsiModelApplicationLayer; }

protected:
	SomeIpLayer() {}

private:
	static const uint8_t SOMEIP_PROTOCOL_VERSION = 1;
	virtual size_t getSomeIpHeaderLen() const { return sizeof(someiphdr); }

	/* Using unordered_set since insertion and search should be almost constant time */
	static std::unordered_set<uint16_t> m_SomeIpPorts;
};

/**
 * @class SomeIpTpLayer
 * Represents an SOME/IP Transport Protocol Layer
 */
class SomeIpTpLayer : public SomeIpLayer
{
public:
	/**
	 * @struct someiptphdr
	 * Represents an SOME/IP-TP protocol header.
	 */
#pragma pack(push, 1)
	struct someiptphdr : someiphdr
	{
		/** Contains the offset and the more segments flag. 28 bit offset field measured in 16 bytes + 3 bit reserved +
		 * 1 bit more segments flag */
		uint32_t offsetAndFlag;
	};
#pragma pack(pop)

	/**
	 * 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 @ref someiptphdr)
	 * @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
	 */
	SomeIpTpLayer(uint8_t *data, size_t dataLen, Layer *prevLayer, Packet *packet)
		: SomeIpLayer(data, dataLen, prevLayer, packet) {}

	/**
	 * A constructor that creates empty layer and sets values
	 * @param[in] serviceID Service ID
	 * @param[in] methodID Method ID
	 * @param[in] clientID Client ID
	 * @param[in] sessionID Session ID
	 * @param[in] interfaceVersion Interface Version
	 * @param[in] type Type of the message
	 * @param[in] returnCode Return Code
	 * @param[in] offset Offset indicating the data offset in increments of 16 bytes
	 * @param[in] moreSegmentsFlag Flag indicating whether more SOME/IP-TP Packets will follow
	 * @param[in] data A pointer to the raw data
	 * @param[in] dataLen Size of the data in bytes
	 */
	SomeIpTpLayer(uint16_t serviceID, uint16_t methodID, uint16_t clientID, uint16_t sessionID,
				  uint8_t interfaceVersion, MsgType type, uint8_t returnCode, uint32_t offset, bool moreSegmentsFlag,
				  const uint8_t *const data = nullptr, size_t dataLen = 0);

	/**
	 * Destroy the layer object
	 */
	~SomeIpTpLayer() {}

	/**
	 * Get a pointer to the basic SOME/IP-TP header. Notice this points directly to the data, so every change will
	 * change the actual packet data
	 * @return A pointer to the @ref someiptphdr
	 */
	someiptphdr *getSomeIpTpHeader() const { return (someiptphdr *)m_Data; }

	/**
	 * Get the Offset. Offset is returned in multiple of 16 bytes.
	 * @return The offset value
	 */
	uint32_t getOffset() const;

	/**
	 * Set the Offset. Already has to be in multiples of 16 bytes.
	 * If 32 bytes have already been transmitted, the offset has to be set to 2.
	 * @param[in] offset Offset to set. Already has to be in multiples of 16 bytes.
	 */
	void setOffset(uint32_t offset);

	/**
	 * Get the More Segments Flag
	 * @return true if the More Segments Flag is set, false if it is not set
	 */
	bool getMoreSegmentsFlag() const;

	/**
	 * Set the More Segments Flag
	 * @param[in] flag True if the More Segments Flag shall be set, false for resetting
	 */
	void setMoreSegmentsFlag(bool flag);

	/**
	 * Sets the message type in this layer with enabling the TP flag
	 */
	void computeCalculateFields();

	/**
	 * @return The string representation of the SOME/IP-TP layer
	 */
	std::string toString() const;

private:
	static const uint32_t SOMEIP_TP_MORE_FLAG_MASK = 0x01;
	static const uint32_t SOMEIP_TP_OFFSET_MASK = 0xFFFFFFF0;

	size_t getSomeIpHeaderLen() const { return sizeof(someiptphdr); }

	static uint8_t setTpFlag(uint8_t messageType);
};

} // namespace pcpp
#endif /* PACKETPP_SOMEIP_LAYER */
