// See the file "COPYING" in the main distribution directory for copyright.

#pragma once

#include <sys/types.h> // for u_char
#include <optional>
#include <vector>

#include "zeek/iosource/BPF_Program.h"
#include "zeek/iosource/IOSource.h"
#include "zeek/iosource/Packet.h"
#include "zeek/telemetry/Manager.h"

struct pcap_pkthdr;

namespace zeek::iosource
	{

/**
 * Base class for packet sources.
 */
class PktSrc : public IOSource
	{
public:
	static const uint32_t NETMASK_UNKNOWN = 0xffffffff;

	/**
	 * Struct for returning statistics on a packet source.
	 */
	struct Stats
		{
		/**
		 * Packets received by source after filtering (w/o drops).
		 */
		uint64_t received = 0;

		/**
		 * Packets dropped by source.
		 */
		uint64_t dropped = 0; // pkts dropped

		/**
		 * Total number of packets on link before filtering.
		 * Optional, can be left unset if not available.
		 */
		uint64_t link = 0;

		/**
		 * Bytes received by source after filtering (w/o drops).
		 */
		uint64_t bytes_received = 0;

		/**
		 * Packets filtered by the packet source.
		 */
		std::optional<uint64_t> filtered;
		};

	/**
	 * Constructor.
	 */
	PktSrc();

	/**
	 * Destructor.
	 */
	~PktSrc() override;

	/**
	 * Returns the path associated with the source. This is the interface
	 * name for live source, and a filename for offline sources.
	 */
	const std::string& Path() const;

	/**
	 * Returns true if this is a live source.
	 */
	bool IsLive() const;

	/**
	 * Returns the link type of the source.
	 */
	int LinkType() const;

	/**
	 * Returns the netmask associated with the source, or \c
	 * NETMASK_UNKNOWN if unknown.
	 */
	uint32_t Netmask() const;

	/**
	 * Returns true if the source has flagged an error.
	 */
	bool IsError() const;

	/**
	 * Return true if the source has been observed idle for the given
	 * wallclock interval.
	 *
	 * The default implementation looks at failing ExtractNextPacket() calls
	 * and keeps the wallclock timestamp when there was no packet available.
	 * The source is considered idle when there has not been a packet since
	 * \a interval seconds.
	 *
	 * Alternative implementations may check internally buffered packets
	 * or queue lengths.
	 *
	 * @param interval Interval in seconds.
	 *
	 * @return True if the source has been idle for \a interval seconds.
	 */
	virtual bool HasBeenIdleFor(double interval) const;

	/**
	 * If the source encountered an error, returns a corresponding error
	 * message. Returns an empty string otherwise.
	 */
	const char* ErrorMsg() const;

	/**
	 * Precompiles a BPF filter and associates the given index with it.
	 * The compiled filter will be then available via \a GetBPFFilter().
	 *
	 * This is primarily a helper for packet source implementations that
	 * want to apply BPF filtering to their packets.
	 *
	 * @param index The index to associate with the filter.
	 *
	 * @param BPF filter The filter string to precompile.
	 *
	 * @return True on success, false if a problem occurred.
	 */
	virtual bool PrecompileBPFFilter(int index, const std::string& filter);

	/**
	 * Returns the precompiled BPF filter associated with a given index,
	 * if any, as compiled by \a PrecompileBPFFilter().
	 *
	 * This is primarily a helper for packet source implementation that
	 * want to apply BPF filtering to their packets.
	 *
	 * @return The BPF filter associated, or null if none has been
	 * (successfully) compiled.
	 */
	detail::BPF_Program* GetBPFFilter(int index);

	/**
	 * Applies a precompiled BPF filter to a packet. This will close the
	 * source with an error message if no filter with that index has been
	 * compiled.
	 *
	 * This is primarily a helper for packet source implementation that
	 * want to apply BPF filtering to their packets.
	 *
	 * @param index The index of the filter to apply.
	 *
	 * @param hdr The header of the packet to filter.
	 *
	 * @param pkt The content of the packet to filter.
	 *
	 * @return True if it matches.
	 */
	bool ApplyBPFFilter(int index, const struct pcap_pkthdr* hdr, const u_char* pkt);

	/**
	 * Returns the packet currently being processed, if available.
	 *
	 * @param pkt A pointer to pass the content of the current packet
	 * back.
	 *
	 * @return True if the current packet is available, or false if not.
	 */
	bool GetCurrentPacket(const Packet** hdr);

	// PacketSource interface for derived classes to override.

	/**
	 * Precompiles a filter and associates a given index with it. The
	 * filter syntax is defined by the packet source's implementation.
	 *
	 * Derived classes can override this method to implement their own
	 * filtering. If not overridden, it uses the pcap-based BPF filtering
	 * by default.
	 *
	 * @param index The index to associate with the filter
	 *
	 * @param filter The filter string to precompile.
	 *
	 * @return True on success, false if a problem occurred or filtering
	 * is not supported.
	 */
	virtual bool PrecompileFilter(int index, const std::string& filter)
		{
		return PrecompileBPFFilter(index, filter);
		}

	/**
	 * Activates a precompiled filter with the given index.
	 *
	 * Derived classes must implement this to implement their filtering.
	 * If they want to use BPF but don't support it natively, they can
	 * call the corresponding helper method provided by \a PktSrc.
	 *
	 * @param index The index of the filter to activate.
	 *
	 * @return True on success, false if a problem occurred or the
	 * filtering is not supported.
	 */
	virtual bool SetFilter(int index) = 0;

	/**
	 * Returns current statistics about the source.
	 *
	 * Derived classes must implement this method.
	 *
	 * @param stats A statistics structure that the method fill out.
	 */
	virtual void Statistics(Stats* stats) = 0;

	/**
	 * Return the next timeout value for this source. This should be
	 * overridden by source classes where they have a timeout value
	 * that can wake up the poll.
	 *
	 * @return A value for the next time that the source thinks the
	 * poll should time out in seconds from the current time. Return
	 * -1 if this should not be considered.
	 */
	virtual double GetNextTimeout() override;

protected:
	friend class Manager;

	// Methods to use by derived classes.

	/**
	 * Structure to pass back information about the packet source to the
	 * base class. Derived class pass an instance of this to \a Opened().
	 */
	struct Properties
		{
		/**
		 * The path associated with the source. This is the interface
		 * name for live source, and a filename for offline sources.
		 */
		std::string path;

		/**
		 * A file descriptor suitable to use with \a select() for
		 * determining if there's input available from this source.
		 */
		int selectable_fd;

		/**
		 * The link type for packets from this source.
		 */
		int link_type;

		/**
		 * Returns the netmask associated with the source, or \c
		 * NETMASK_UNKNOWN if unknown.
		 */
		uint32_t netmask;

		/**
		 * True if the source is reading live inout, false for
		 * working offline.
		 */
		bool is_live;

		Properties();
		};

	/**
	 * Called from the implementations of \a Open() to signal that the
	 * source has been successfully opened.
	 *
	 * @param props A properties instance describing the now open source.
	 */
	void Opened(const Properties& props);

	/**
	 * Called from the implementations of \a Close() to signal that the
	 * source has been closed.
	 */
	void Closed();

	/**
	 * Can be called from derived classes to send an informational
	 * message to the user.
	 *
	 * @param msg The message to pass on.
	 */
	void Info(const std::string& msg);

	/**
	 * Can be called from derived classes to flag send an error.
	 *
	 * @param msg The message going with the error.
	 */
	void Error(const std::string& msg);

	/**
	 * Can be called from derived classes to flag a "weird" situation.
	 *
	 * @param msg The message to pass on.
	 *
	 * @param pkt The packet associated with the weird, or null if none.
	 */
	void Weird(const std::string& msg, const Packet* pkt);

	/**
	 * Can be called from derived classes to flag an internal error,
	 * which will abort execution.
	 *
	 * @param msg The message to pass on.
	 */
	void InternalError(const std::string& msg);

	// PktSrc interface for derived classes to implement.

	/**
	 * Called by the manager system to open the source.
	 *
	 * Derived classes must implement this method. If successful, the
	 * implementation must call \a Opened(); if not, it must call Error()
	 * with a corresponding message.
	 */
	virtual void Open() = 0;

	/**
	 * Called by the manager system to close the source.
	 *
	 * Derived classes must implement this method. If successful, the
	 * implementation must call \a Closed(); if not, it must call Error()
	 * with a corresponding message.
	 */
	virtual void Close() = 0;

	/**
	 * Provides the next packet from the source.
	 *
	 * @param pkt The packet structure to fill in with the packet's
	 * information. The callee keep ownership of the data but must
	 * guarantee that it stays available at least until \a
	 * DoneWithPacket() is called.  It is guaranteed that no two calls to
	 * this method will happen with \a DoneWithPacket() in between.
	 *
	 * @return True if a packet is available and *pkt* filled in. False
	 * if not packet is available or an error occurred (which must be
	 * flagged via Error()).
	 */
	virtual bool ExtractNextPacket(Packet* pkt) = 0;

	/**
	 * Signals that the data of previously extracted packet will no
	 * longer be needed.
	 */
	virtual void DoneWithPacket() = 0;

	/**
	 * Performs the actual filter compilation. This can be overridden to
	 * provide a different implementation of the compilation called by
	 * PrecompileBPFFilter(). This is primarily used by the pcap source
	 * use a different version of BPF_Filter::Compile;
	 *
	 * @param filter the filtering string being compiled.
	 *
	 * @return The compiled filter or nullptr if compilation failed.
	 */
	virtual detail::BPF_Program* CompileFilter(const std::string& filter);

private:
	// Internal helper for ExtractNextPacket().
	bool ExtractNextPacketInternal();

	// IOSource interface implementation.
	void InitSource() override;
	void Done() override;
	void Process() override;
	const char* Tag() override;

	Properties props;

	bool have_packet;
	Packet current_packet;
	// Did the previous call to ExtractNextPacket() yield a packet.
	bool had_packet;

	double idle_at_wallclock = 0.0;

	// For BPF filtering support.
	std::vector<detail::BPF_Program*> filters;

	std::string errbuf;
	};

	} // namespace zeek::iosource
