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

/**
 * @file
 * @brief PPP (Point-to-Point Protocol)
 */

#ifndef ZEPHYR_INCLUDE_NET_PPP_H_
#define ZEPHYR_INCLUDE_NET_PPP_H_

#include <zephyr/net/net_if.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_stats.h>
#include <zephyr/net/net_mgmt.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Point-to-point (PPP) L2/driver support functions
 * @defgroup ppp PPP L2/driver Support Functions
 * @since 2.0
 * @version 0.8.0
 * @ingroup networking
 * @{
 */

/** PPP maximum receive unit (MRU) */
#define PPP_MRU CONFIG_NET_PPP_MTU_MRU

/** PPP maximum transfer unit (MTU) */
#define PPP_MTU PPP_MRU

/** Max length of terminate description string */
#define PPP_MAX_TERMINATE_REASON_LEN 32

/** Length of network interface identifier */
#define PPP_INTERFACE_IDENTIFIER_LEN 8

/** PPP L2 API */
struct ppp_api {
	/**
	 * The net_if_api must be placed in first position in this
	 * struct so that we are compatible with network interface API.
	 */
	struct net_if_api iface_api;

	/** Start the device */
	int (*start)(const struct device *dev);

	/** Stop the device */
	int (*stop)(const struct device *dev);

	/** Send a network packet */
	int (*send)(const struct device *dev, struct net_pkt *pkt);

#if defined(CONFIG_NET_STATISTICS_PPP)
	/** Collect optional PPP specific statistics. This pointer
	 * should be set by driver if statistics needs to be collected
	 * for that driver.
	 */
	struct net_stats_ppp *(*get_stats)(const struct device *dev);
#endif
};

/* Make sure that the network interface API is properly setup inside
 * PPP API struct (it is the first one).
 */
BUILD_ASSERT(offsetof(struct ppp_api, iface_api) == 0);

/**
 * PPP protocol types.
 * See https://www.iana.org/assignments/ppp-numbers/ppp-numbers.xhtml
 * for details.
 */
enum ppp_protocol_type {
	PPP_IP     = 0x0021, /**< RFC 1332 */
	PPP_IPV6   = 0x0057, /**< RFC 5072 */
	PPP_IPCP   = 0x8021, /**< RFC 1332 */
	PPP_ECP    = 0x8053, /**< RFC 1968 */
	PPP_IPV6CP = 0x8057, /**< RFC 5072 */
	PPP_CCP    = 0x80FD, /**< RFC 1962 */
	PPP_LCP    = 0xc021, /**< RFC 1661 */
	PPP_PAP    = 0xc023, /**< RFC 1334 */
	PPP_CHAP   = 0xc223, /**< RFC 1334 */
	PPP_EAP    = 0xc227, /**< RFC 2284 */
};

/**
 * PPP phases
 */
enum ppp_phase {
	/** Physical-layer not ready */
	PPP_DEAD,
	/** Link is being established */
	PPP_ESTABLISH,
	/** Link authentication with peer */
	PPP_AUTH,
	/** Network connection establishment */
	PPP_NETWORK,
	/** Network running */
	PPP_RUNNING,
	/** Link termination */
	PPP_TERMINATE,
};

/** @cond INTERNAL_HIDDEN */

/**
 * PPP states, RFC 1661 ch. 4.2
 */
enum ppp_state {
	PPP_INITIAL,
	PPP_STARTING,
	PPP_CLOSED,
	PPP_STOPPED,
	PPP_CLOSING,
	PPP_STOPPING,
	PPP_REQUEST_SENT,
	PPP_ACK_RECEIVED,
	PPP_ACK_SENT,
	PPP_OPENED
};

/**
 * PPP protocol operations from RFC 1661
 */
enum ppp_packet_type {
	PPP_CONFIGURE_REQ  = 1,
	PPP_CONFIGURE_ACK  = 2,
	PPP_CONFIGURE_NACK = 3,
	PPP_CONFIGURE_REJ  = 4,
	PPP_TERMINATE_REQ  = 5,
	PPP_TERMINATE_ACK  = 6,
	PPP_CODE_REJ       = 7,
	PPP_PROTOCOL_REJ   = 8,
	PPP_ECHO_REQ       = 9,
	PPP_ECHO_REPLY     = 10,
	PPP_DISCARD_REQ    = 11
};

/** @endcond */

/**
 * LCP option types from RFC 1661 ch. 6
 */
enum lcp_option_type {
	/** Reserved option value (do not use) */
	LCP_OPTION_RESERVED = 0,

	/** Maximum-Receive-Unit */
	LCP_OPTION_MRU = 1,

	/** Async-Control-Character-Map */
	LCP_OPTION_ASYNC_CTRL_CHAR_MAP = 2,

	/** Authentication-Protocol */
	LCP_OPTION_AUTH_PROTO = 3,

	/** Quality-Protocol */
	LCP_OPTION_QUALITY_PROTO = 4,

	/** Magic-Number */
	LCP_OPTION_MAGIC_NUMBER = 5,

	/** Protocol-Field-Compression */
	LCP_OPTION_PROTO_COMPRESS = 7,

	/** Address-and-Control-Field-Compression */
	LCP_OPTION_ADDR_CTRL_COMPRESS = 8
} __packed;

/**
 * IPCP option types from RFC 1332
 */
enum ipcp_option_type {
	/** Reserved IPCP option value (do not use) */
	IPCP_OPTION_RESERVED = 0,

	/** IP Addresses */
	IPCP_OPTION_IP_ADDRESSES = 1,

	/** IP Compression Protocol */
	IPCP_OPTION_IP_COMP_PROTO = 2,

	/** IP Address */
	IPCP_OPTION_IP_ADDRESS = 3,

	/* RFC 1877 */

	/** Primary DNS Server Address */
	IPCP_OPTION_DNS1 = 129,

	/** Primary NBNS Server Address */
	IPCP_OPTION_NBNS1 = 130,

	/** Secondary DNS Server Address */
	IPCP_OPTION_DNS2 = 131,

	/** Secondary NBNS Server Address */
	IPCP_OPTION_NBNS2 = 132,
} __packed;

/**
 * IPV6CP option types from RFC 5072
 */
enum ipv6cp_option_type {
	/** Reserved IPV6CP option value (do not use) */
	IPV6CP_OPTION_RESERVED = 0,

	/** Interface identifier */
	IPV6CP_OPTION_INTERFACE_IDENTIFIER = 1,
} __packed;

/**
 * @typedef net_ppp_lcp_echo_reply_cb_t
 * @brief A callback function that can be called if a Echo-Reply needs to
 *        be received.
 * @param user_data User settable data that is passed to the callback
 *        function.
 * @param user_data_len Length of the user data.
 */
typedef void (*net_ppp_lcp_echo_reply_cb_t)(void *user_data,
					    size_t user_data_len);

struct ppp_my_option_data;
struct ppp_my_option_info;

/**
 * Generic PPP Finite State Machine
 */
struct ppp_fsm {
	/** Timeout timer */
	struct k_work_delayable timer;

	/** FSM callbacks */
	struct {
		/** Acknowledge Configuration Information */
		int (*config_info_ack)(struct ppp_fsm *fsm,
				       struct net_pkt *pkt,
				       uint16_t length);

		/** Add Configuration Information */
		struct net_pkt *(*config_info_add)(struct ppp_fsm *fsm);

		/** Length of Configuration Information */
		int  (*config_info_len)(struct ppp_fsm *fsm);

		/** Negative Acknowledge Configuration Information */
		int (*config_info_nack)(struct ppp_fsm *fsm,
					struct net_pkt *pkt,
					uint16_t length,
					bool rejected);

		/** Request peer's Configuration Information */
		int (*config_info_req)(struct ppp_fsm *fsm,
				       struct net_pkt *pkt,
				       uint16_t length,
				       struct net_pkt *ret_pkt);

		/** Reject Configuration Information */
		int (*config_info_rej)(struct ppp_fsm *fsm,
				       struct net_pkt *pkt,
				       uint16_t length);

		/** Reset Configuration Information */
		void (*config_info_reset)(struct ppp_fsm *fsm);

		/** FSM goes to OPENED state */
		void (*up)(struct ppp_fsm *fsm);

		/** FSM leaves OPENED state */
		void (*down)(struct ppp_fsm *fsm);

		/** Starting this protocol */
		void (*starting)(struct ppp_fsm *fsm);

		/** Quitting this protocol */
		void (*finished)(struct ppp_fsm *fsm);

		/** We received Protocol-Reject */
		void (*proto_reject)(struct ppp_fsm *fsm);

		/** Retransmit */
		void (*retransmit)(struct ppp_fsm *fsm);

		/** Any code that is not understood by PPP is passed to
		 * this FSM for further processing.
		 */
		enum net_verdict (*proto_extension)(struct ppp_fsm *fsm,
						    enum ppp_packet_type code,
						    uint8_t id,
						    struct net_pkt *pkt);
	} cb;

	/** My options */
	struct {
		/** Options information */
		const struct ppp_my_option_info *info;

		/** Options negotiation data */
		struct ppp_my_option_data *data;

		/** Number of negotiated options */
		size_t count;
	} my_options;

	/** Option bits */
	uint32_t flags;

	/** Number of re-transmissions left */;
	uint32_t retransmits;

	/** Number of NACK loops since last ACK */
	uint32_t nack_loops;

	/** Number of NACKs received */
	uint32_t recv_nack_loops;

	/** Reason for closing protocol */
	char terminate_reason[PPP_MAX_TERMINATE_REASON_LEN];

	/** PPP protocol number for this FSM */
	uint16_t protocol;

	/** Current state of PPP link */
	enum ppp_state state;

	/** Protocol/layer name of this FSM (for debugging) */
	const char *name;

	/** Current id */
	uint8_t id;

	/** Current request id */
	uint8_t req_id;

	/** Have received valid Ack, Nack or Reject to a Request */
	uint8_t ack_received : 1;
};

/** @cond INTERNAL_HIDDEN */

#define PPP_MY_OPTION_ACKED	BIT(0)
#define PPP_MY_OPTION_REJECTED	BIT(1)

struct ppp_my_option_data {
	uint32_t flags;
};

#define IPCP_NUM_MY_OPTIONS	3
#define IPV6CP_NUM_MY_OPTIONS	1

enum ppp_flags {
	PPP_CARRIER_UP,
};

/** @endcond */

/** Link control protocol options */
struct lcp_options {
	/** Magic number */
	uint32_t magic;

	/** Async char map */
	uint32_t async_map;

	/** Maximum Receive Unit value */
	uint16_t mru;

	/** Which authentication protocol was negotiated (0 means none) */
	uint16_t auth_proto;
};

#if defined(CONFIG_NET_L2_PPP_OPTION_MRU)
#define LCP_NUM_MY_OPTIONS	1
#endif

/** IPv4 control protocol options */
struct ipcp_options {
	/** IPv4 address */
	struct in_addr address;

	/** Primary DNS server address */
	struct in_addr dns1_address;

	/** Secondary DNS server address */
	struct in_addr dns2_address;
};

/** IPv6 control protocol options */
struct ipv6cp_options {
	/** Interface identifier */
	uint8_t iid[PPP_INTERFACE_IDENTIFIER_LEN];
};

/** PPP L2 context specific to certain network interface */
struct ppp_context {
	/** Flags representing PPP state, which are accessed from multiple
	 * threads.
	 */
	atomic_t flags;

	/** PPP startup worker. */
	struct k_work_delayable startup;

	/** LCP options */
	struct {
		/** Finite state machine for LCP */
		struct ppp_fsm fsm;

		/** Options that we want to request */
		struct lcp_options my_options;

		/** Options that peer want to request */
		struct lcp_options peer_options;

		/** Magic-Number value */
		uint32_t magic;
#if defined(CONFIG_NET_L2_PPP_OPTION_MRU)
		struct ppp_my_option_data my_options_data[LCP_NUM_MY_OPTIONS];
#endif
	} lcp;

#if defined(CONFIG_NET_IPV4)
	/** ICMP options */
	struct {
		/** Finite state machine for IPCP */
		struct ppp_fsm fsm;

		/** Options that we want to request */
		struct ipcp_options my_options;

		/** Options that peer want to request */
		struct ipcp_options peer_options;

		/** My options runtime data */
		struct ppp_my_option_data my_options_data[IPCP_NUM_MY_OPTIONS];
	} ipcp;
#endif

#if defined(CONFIG_NET_IPV6)
	/** IPV6CP options */
	struct {
		/** Finite state machine for IPV6CP */
		struct ppp_fsm fsm;

		/** Options that we want to request */
		struct ipv6cp_options my_options;

		/** Options that peer want to request */
		struct ipv6cp_options peer_options;

		/** My options runtime data */
		struct ppp_my_option_data my_options_data[IPV6CP_NUM_MY_OPTIONS];
	} ipv6cp;
#endif

#if defined(CONFIG_NET_L2_PPP_PAP)
	/** PAP options */
	struct {
		/** Finite state machine for PAP */
		struct ppp_fsm fsm;
	} pap;
#endif

#if defined(CONFIG_NET_SHELL)
	/** Network shell PPP command internal data */
	struct {
		/** Ping command internal data */
		struct {
			/** Callback to be called when Echo-Reply is received.
			 */
			net_ppp_lcp_echo_reply_cb_t cb;

			/** User specific data for the callback */
			void *user_data;

			/** User data length */
			size_t user_data_len;
		} echo_reply;

		/** Used when waiting Echo-Reply */
		struct k_sem wait_echo_reply;

		/** Echo-Req data value */
		uint32_t echo_req_data;

		/** Echo-Reply data value */
		uint32_t echo_reply_data;
	} shell;
#endif

	/** Network interface related to this PPP connection */
	struct net_if *iface;

	/** Network management callback structure */
	struct net_mgmt_event_callback mgmt_evt_cb;

	/** Current phase of PPP link */
	enum ppp_phase phase;

	/** Signal when PPP link is terminated */
	struct k_sem wait_ppp_link_terminated;

	/** Signal when PPP link is down */
	struct k_sem wait_ppp_link_down;

	/** This tells what features the PPP supports. */
	enum net_l2_flags ppp_l2_flags;

	/** This tells how many network protocols are open */
	int network_protos_open;

	/** This tells how many network protocols are up */
	int network_protos_up;

	/** Is PPP ready to receive packets */
	uint16_t is_ready_to_serve : 1;

	/** Is PPP L2 enabled or not */
	uint16_t is_enabled : 1;

	/** PPP enable pending */
	uint16_t is_enable_done : 1;

	/** IPCP status (up / down) */
	uint16_t is_ipcp_up : 1;

	/** IPCP open status (open / closed) */
	uint16_t is_ipcp_open : 1;

	/** IPV6CP status (up / down) */
	uint16_t is_ipv6cp_up : 1;

	/** IPV6CP open status (open / closed) */
	uint16_t is_ipv6cp_open : 1;

	/** PAP status (up / down) */
	uint16_t is_pap_up : 1;

	/** PAP open status (open / closed) */
	uint16_t is_pap_open : 1;
};

/**
 * @brief Initialize PPP L2 stack for a given interface
 *
 * @param iface A valid pointer to a network interface
 */
void net_ppp_init(struct net_if *iface);

/* Management API for PPP */

/** @cond INTERNAL_HIDDEN */

#define PPP_L2_CTX_TYPE	struct ppp_context

#define _NET_PPP_LAYER	NET_MGMT_LAYER_L2
#define _NET_PPP_CODE	0x209
#define _NET_PPP_BASE	(NET_MGMT_IFACE_BIT |				\
			 NET_MGMT_LAYER(_NET_PPP_LAYER) |		\
			 NET_MGMT_LAYER_CODE(_NET_PPP_CODE))
#define _NET_PPP_EVENT	(_NET_PPP_BASE | NET_MGMT_EVENT_BIT)

enum net_event_ppp_cmd {
	NET_EVENT_PPP_CMD_CARRIER_ON = 1,
	NET_EVENT_PPP_CMD_CARRIER_OFF,
	NET_EVENT_PPP_CMD_PHASE_RUNNING,
	NET_EVENT_PPP_CMD_PHASE_DEAD,
};

struct net_if;

/** @endcond */

/** Event emitted when PPP carrier is on */
#define NET_EVENT_PPP_CARRIER_ON					\
	(_NET_PPP_EVENT | NET_EVENT_PPP_CMD_CARRIER_ON)

/** Event emitted when PPP carrier is off */
#define NET_EVENT_PPP_CARRIER_OFF					\
	(_NET_PPP_EVENT | NET_EVENT_PPP_CMD_CARRIER_OFF)

/** Event emitted when PPP goes into running phase */
#define NET_EVENT_PPP_PHASE_RUNNING					\
	(_NET_PPP_EVENT | NET_EVENT_PPP_CMD_PHASE_RUNNING)

/** Event emitted when PPP goes into dead phase */
#define NET_EVENT_PPP_PHASE_DEAD					\
	(_NET_PPP_EVENT | NET_EVENT_PPP_CMD_PHASE_DEAD)

/**
 * @brief Raise CARRIER_ON event when PPP is connected.
 *
 * @param iface PPP network interface.
 */
#if defined(CONFIG_NET_L2_PPP_MGMT)
void ppp_mgmt_raise_carrier_on_event(struct net_if *iface);
#else
static inline void ppp_mgmt_raise_carrier_on_event(struct net_if *iface)
{
	ARG_UNUSED(iface);
}
#endif

/**
 * @brief Raise CARRIER_OFF event when PPP is disconnected.
 *
 * @param iface PPP network interface.
 */
#if defined(CONFIG_NET_L2_PPP_MGMT)
void ppp_mgmt_raise_carrier_off_event(struct net_if *iface);
#else
static inline void ppp_mgmt_raise_carrier_off_event(struct net_if *iface)
{
	ARG_UNUSED(iface);
}
#endif

/**
 * @brief Raise PHASE_RUNNING event when PPP reaching RUNNING phase
 *
 * @param iface PPP network interface.
 */
#if defined(CONFIG_NET_L2_PPP_MGMT)
void ppp_mgmt_raise_phase_running_event(struct net_if *iface);
#else
static inline void ppp_mgmt_raise_phase_running_event(struct net_if *iface)
{
	ARG_UNUSED(iface);
}
#endif

/**
 * @brief Raise PHASE_DEAD event when PPP reaching DEAD phase
 *
 * @param iface PPP network interface.
 */
#if defined(CONFIG_NET_L2_PPP_MGMT)
void ppp_mgmt_raise_phase_dead_event(struct net_if *iface);
#else
static inline void ppp_mgmt_raise_phase_dead_event(struct net_if *iface)
{
	ARG_UNUSED(iface);
}
#endif

/**
 * @brief Send PPP Echo-Request to peer. We expect to receive Echo-Reply back.
 *
 * @param idx PPP network interface index
 * @param timeout Amount of time to wait Echo-Reply. The value is in
 * milliseconds.
 *
 * @return 0 if Echo-Reply was received, < 0 if there is a timeout or network
 * index is not a valid PPP network index.
 */
#if defined(CONFIG_NET_L2_PPP)
int net_ppp_ping(int idx, int32_t timeout);
#else
static inline int net_ppp_ping(int idx, int32_t timeout)
{
	ARG_UNUSED(idx);
	ARG_UNUSED(timeout);

	return -ENOTSUP;
}
#endif

/**
 * @brief Get PPP context information. This is only used by net-shell to
 * print information about PPP.
 *
 * @param idx PPP network interface index
 *
 * @return PPP context or NULL if idx is invalid.
 */
#if defined(CONFIG_NET_L2_PPP) && defined(CONFIG_NET_SHELL)
struct ppp_context *net_ppp_context_get(int idx);
#else
static inline struct ppp_context *net_ppp_context_get(int idx)
{
	ARG_UNUSED(idx);

	return NULL;
}
#endif

#ifdef __cplusplus
}
#endif

/**
 * @}
 */

#endif /* ZEPHYR_INCLUDE_NET_PPP_H_ */
