/** @file
 * @brief Network packet capture definitions
 *
 * Definitions for capturing network packets.
 */

/*
 * Copyright (c) 2021 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef ZEPHYR_INCLUDE_NET_CAPTURE_H_
#define ZEPHYR_INCLUDE_NET_CAPTURE_H_

#include <zephyr/kernel.h>
#include <zephyr/device.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Network packet capture support functions
 * @defgroup net_capture Network packet capture
 * @ingroup networking
 * @{
 */

/** @cond INTERNAL_HIDDEN */

struct net_if;
struct net_pkt;
struct device;

struct net_capture_interface_api {
	/** Cleanup the setup. This will also disable capturing. After this
	 * call, the setup function can be called again.
	 */
	int (*cleanup)(const struct device *dev);

	/** Enable / start capturing data */
	int (*enable)(const struct device *dev, struct net_if *iface);

	/** Disable / stop capturing data */
	int (*disable)(const struct device *dev);

	/** Is capturing enabled (returns true) or disabled (returns false).
	 */
	bool (*is_enabled)(const struct device *dev);

	/** Send captured data */
	int (*send)(const struct device *dev, struct net_if *iface, struct net_pkt *pkt);
};

/** @endcond */

/**
 * @brief Setup network packet capturing support.
 *
 * @param remote_addr The value tells the tunnel remote/outer endpoint
 *        IP address. The IP address can be either IPv4 or IPv6 address.
 *        This address is used to select the network interface where the tunnel
 *        is created.
 * @param my_local_addr The local/inner IP address of the tunnel. Can contain
 *        also port number which is used as UDP source port.
 * @param peer_addr The peer/inner IP address of the tunnel. Can contain
 *        also port number which is used as UDP destination port.
 * @param dev Network capture device. This is returned to the caller.
 *
 * @return 0 if ok, <0 if network packet capture setup failed
 */
int net_capture_setup(const char *remote_addr, const char *my_local_addr, const char *peer_addr,
		      const struct device **dev);

/**
 * @brief Cleanup network packet capturing support.
 *
 * @details This should be called after the capturing is done and resources
 *          can be released.
 *
 * @param dev Network capture device. User must allocate using the
 *            net_capture_setup() function.
 *
 * @return 0 if ok, <0 if network packet capture cleanup failed
 */
static inline int net_capture_cleanup(const struct device *dev)
{
#if defined(CONFIG_NET_CAPTURE)
	const struct net_capture_interface_api *api =
		(const struct net_capture_interface_api *)dev->api;

	return api->cleanup(dev);
#else
	ARG_UNUSED(dev);

	return -ENOTSUP;
#endif
}

/**
 * @brief Enable network packet capturing support.
 *
 * @details This creates tunnel network interface where all the
 * captured packets are pushed. The captured network packets are
 * placed in UDP packets that are sent to tunnel peer.
 *
 * @param dev Network capture device
 * @param iface Network interface we are starting to capture packets.
 *
 * @return 0 if ok, <0 if network packet capture enable failed
 */
static inline int net_capture_enable(const struct device *dev, struct net_if *iface)
{
#if defined(CONFIG_NET_CAPTURE)
	const struct net_capture_interface_api *api =
		(const struct net_capture_interface_api *)dev->api;

	return api->enable(dev, iface);
#else
	ARG_UNUSED(dev);
	ARG_UNUSED(iface);

	return -ENOTSUP;
#endif
}

/**
 * @brief Is network packet capture enabled or disabled.
 *
 * @param dev Network capture device. If set to NULL, then the
 *            default capture device is used.
 *
 * @return True if enabled, False if network capture is disabled.
 */
static inline bool net_capture_is_enabled(const struct device *dev)
{
#if defined(CONFIG_NET_CAPTURE)
	const struct net_capture_interface_api *api;

	if (dev == NULL) {
		/* TODO: Go through all capture devices instead of one */
		dev = device_get_binding("NET_CAPTURE0");
		if (dev == NULL) {
			return false;
		}
	}

	api = (const struct net_capture_interface_api *)dev->api;

	return api->is_enabled(dev);
#else
	ARG_UNUSED(dev);

	return false;
#endif
}

/**
 * @brief Disable network packet capturing support.
 *
 * @param dev Network capture device
 *
 * @return 0 if ok, <0 if network packet capture disable failed
 */
static inline int net_capture_disable(const struct device *dev)
{
#if defined(CONFIG_NET_CAPTURE)
	const struct net_capture_interface_api *api =
		(const struct net_capture_interface_api *)dev->api;

	return api->disable(dev);
#else
	ARG_UNUSED(dev);

	return -ENOTSUP;
#endif
}

/** @cond INTERNAL_HIDDEN */

/**
 * @brief Send captured packet.
 *
 * @param dev Network capture device
 * @param iface Network interface the packet is being sent
 * @param pkt The network packet that is sent
 *
 * @return 0 if ok, <0 if network packet capture send failed
 */
static inline int net_capture_send(const struct device *dev, struct net_if *iface,
				   struct net_pkt *pkt)
{
#if defined(CONFIG_NET_CAPTURE)
	const struct net_capture_interface_api *api =
		(const struct net_capture_interface_api *)dev->api;

	return api->send(dev, iface, pkt);
#else
	ARG_UNUSED(dev);
	ARG_UNUSED(iface);
	ARG_UNUSED(pkt);

	return -ENOTSUP;
#endif
}

/**
 * @brief Check if the network packet needs to be captured or not.
 *        This is called for every network packet being sent.
 *
 * @param iface Network interface the packet is being sent
 * @param pkt The network packet that is sent
 */
#if defined(CONFIG_NET_CAPTURE)
void net_capture_pkt(struct net_if *iface, struct net_pkt *pkt);
#else
static inline void net_capture_pkt(struct net_if *iface, struct net_pkt *pkt)
{
	ARG_UNUSED(iface);
	ARG_UNUSED(pkt);
}
#endif

/** @cond INTERNAL_HIDDEN */

/**
 * @brief Special variant for net_capture_pkt() which returns the status
 *        of the send message.
 *
 * @param iface Network interface the packet is being sent
 * @param pkt The network packet that is sent
 *
 * @return 0 if captured packet was handled ok, <0 if the capture failed
 */
#if defined(CONFIG_NET_CAPTURE)
int net_capture_pkt_with_status(struct net_if *iface, struct net_pkt *pkt);
#else
static inline int net_capture_pkt_with_status(struct net_if *iface, struct net_pkt *pkt)
{
	ARG_UNUSED(iface);
	ARG_UNUSED(pkt);

	return -ENOTSUP;
}
#endif

/** @endcond */

/** The type and direction of the captured data. */
enum net_capture_packet_type {
	NET_CAPTURE_HOST,      /**< Packet was sent to us by somebody else */
	NET_CAPTURE_BROADCAST, /**< Packet was broadcast by somebody else */
	NET_CAPTURE_MULTICAST, /**< Packet was multicast, but not broadcast, by somebody else */
	NET_CAPTURE_OTHERHOST, /**< Packet was sent by somebody else to somebody else */
	NET_CAPTURE_OUTGOING,  /**< Packet was sent by us */
};

#define NET_CAPTURE_LL_ADDRLEN 8 /** Maximum length of a link-layer address */

/** The context information for cooked mode capture */
struct net_capture_cooked {
	/** Link-layer address type */
	uint16_t hatype;
	/** Link-layer address length */
	uint16_t halen;
	/** Link-layer address */
	uint8_t addr[NET_CAPTURE_LL_ADDRLEN];
};

/**
 * @brief Initialize cooked mode capture context.
 *
 * @param cooked Cooked context struct allocated by user.
 * @param hatype Link-layer address type
 * @param halen Link-layer address length (maximum is 8 bytes)
 * @param addr Link-layer address
 *
 * @return 0 if ok, <0 if context initialization failed
 */
#if defined(CONFIG_NET_CAPTURE_COOKED_MODE)
int net_capture_cooked_setup(struct net_capture_cooked *ctx,
			     uint16_t hatype,
			     uint16_t halen,
			     uint8_t *addr);
#else
static inline int net_capture_cooked_setup(struct net_capture_cooked *ctx,
					   uint16_t hatype,
					   uint16_t halen,
					   uint8_t *addr)
{
	ARG_UNUSED(ctx);
	ARG_UNUSED(hatype);
	ARG_UNUSED(halen);
	ARG_UNUSED(addr);

	return -ENOTSUP;
}
#endif

/**
 * @brief Capture arbitrary data from source that does not have an interface.
 *        This can be used if you do not have a network interface that
 *        you want to capture from. For example low level modem device
 *        below PPP containing HDLC frames, CANBUS data or Bluetooth packets etc.
 *        The data given to this function should only contain full link
 *        layer packets so that packet boundary is not lost.
 *
 * @param ctx Cooked mode capture context.
 * @param buf Data to capture.
 * @param len Length of the data.
 * @param type The direction and type of the packet (did we sent it etc).
 * @param ptype Protocol type id. These are the ETH_P_* types set in ethernet.h
 */
#if defined(CONFIG_NET_CAPTURE_COOKED_MODE)
void net_capture_data(struct net_capture_cooked *ctx,
		      const uint8_t *data, size_t len,
		      enum net_capture_packet_type type,
		      uint16_t ptype);
#else
static inline void net_capture_data(struct net_capture_cooked *ctx,
				    const uint8_t *data, size_t len,
				    enum net_capture_packet_type type,
				    uint16_t ptype)
{
	ARG_UNUSED(ctx);
	ARG_UNUSED(data);
	ARG_UNUSED(len);
	ARG_UNUSED(type);
	ARG_UNUSED(ptype);
}
#endif

struct net_capture_info {
	const struct device *capture_dev;
	struct net_if *capture_iface;
	struct net_if *tunnel_iface;
	struct sockaddr *peer;
	struct sockaddr *local;
	bool is_enabled;
};

/**
 * @typedef net_capture_cb_t
 * @brief Callback used while iterating over capture devices
 *
 * @param info Information about capture device
 * @param user_data A valid pointer to user data or NULL
 */
typedef void (*net_capture_cb_t)(struct net_capture_info *info, void *user_data);

/**
 * @brief Go through all the capture devices in order to get
 *        information about them. This is mainly useful in
 *        net-shell to print data about currently active
 *        captures.
 *
 * @param cb Callback to call for each capture device
 * @param user_data User supplied data
 */
#if defined(CONFIG_NET_CAPTURE)
void net_capture_foreach(net_capture_cb_t cb, void *user_data);
#else
static inline void net_capture_foreach(net_capture_cb_t cb, void *user_data)
{
	ARG_UNUSED(cb);
	ARG_UNUSED(user_data);
}
#endif

/** @endcond */

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_NET_CAPTURE_H_ */
