/*
 * Copyright (c) 2001-2018 Mellanox Technologies, Ltd. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


#ifndef NEIGHBOUR_H
#define NEIGHBOUR_H

#include <rdma/rdma_cma.h>
#include <tr1/unordered_map>

#include "state_machine/sm.h"
#include "vma/util/sys_vars.h"
#include "vma/util/to_str.h"
#include "vma/infra/cache_subject_observer.h"
#include "vma/infra/sender.h"
#include "vma/event/event_handler_ibverbs.h"
#include "vma/event/event_handler_rdma_cm.h"
#include "vma/event/event_handler_manager.h"
#include "vma/event/timer_handler.h"
#include "vma/event/netlink_event.h"
#include "vma/proto/ip_address.h"
#include "vma/proto/L2_address.h"

#include "vma/proto/header.h"
#include "vma/dev/ring_allocation_logic.h"
#include "vma/dev/net_device_val.h"
#include "vma/dev/ring.h"
#include "vma/proto/arp.h"

class neigh_key : public tostr
{
public:
	neigh_key(ip_address addr, net_device_val* p_ndvl): m_ip_addrs(addr), m_p_net_dev_val(p_ndvl) {};
	virtual ~neigh_key() {};

	const std::string to_str() const
	{
		return(m_ip_addrs.to_str() + " " + m_p_net_dev_val->to_str());
	}
	in_addr_t 	get_in_addr() const { return m_ip_addrs.get_in_addr(); };
	net_device_val*	get_net_device_val() const { return m_p_net_dev_val; };

	virtual size_t 	hash(void)
	{
		uint8_t csum = 0;
		uint8_t* pval = (uint8_t*)this;
		for (size_t i = 0; i < sizeof(ip_address); ++i, ++pval) { csum ^= *pval; }
		return csum;
	}

	virtual bool operator==(neigh_key const& other) const
	{
		return ((m_ip_addrs == other.m_ip_addrs) && (m_p_net_dev_val == other.m_p_net_dev_val));
	}

private:
	ip_address m_ip_addrs;
	net_device_val* m_p_net_dev_val;
};

namespace std { namespace tr1 {
template<>
class hash<neigh_key>
{
public:
	size_t operator()(const neigh_key &key) const
	{
		neigh_key* tmp_key = (neigh_key*)&key;
		return tmp_key->hash();
	}
};
}}

class neigh_val : public tostr
{
public:
				neigh_val(): m_trans_type(VMA_TRANSPORT_UNKNOWN), m_l2_address(NULL){};
	virtual 		~neigh_val(){};

	virtual void    	zero_all_members()
	{ 				if(m_l2_address)
						delete m_l2_address;
					m_l2_address = NULL;
	};
	const L2_address*	get_l2_address() const { return m_l2_address; };

	virtual neigh_val & operator=(const neigh_val & val)
	{
		if (this != &val) {
			m_l2_address = val.m_l2_address;
			m_trans_type = val.m_trans_type;
		}
		return *this;
	}

protected:
	friend	class		neigh_entry;
	friend  class		neigh_ib;
	friend  class		neigh_eth;
	friend 	class		neigh_ib_broadcast;
	transport_type_t	m_trans_type;
	L2_address*		m_l2_address;
};

class neigh_eth_val : public neigh_val
{
public:
	neigh_eth_val()
	{
		m_trans_type = VMA_TRANSPORT_ETH;
		zero_all_members();
	}

	neigh_val & operator=(const neigh_val & val)
	{
		return neigh_val::operator=(val);
	}

private:
	friend	class 		neigh_eth;
};

class neigh_ib_val : public neigh_val
{
public:
				neigh_ib_val() : m_ah(NULL) { zero_all_members(); };

	ibv_ah* 		get_ah()const		{ return m_ah; };
	ibv_ah_attr 		get_ah_attr() const	{ return m_ah_attr; };
	uint32_t 		get_qkey() const	{ return m_qkey; };
	uint32_t		get_qpn() const
	{
				if (m_l2_address)
					return(((IPoIB_addr *) m_l2_address)->get_qpn());
				else
					return 0;
	}

#if _BullseyeCoverage
    #pragma BullseyeCoverage off
#endif
	void 			set_ah(ibv_ah* ah) 	{ m_ah = ah; };
	void			set_qkey(uint32_t qkey) { m_qkey = qkey; };
	void			set_ah_attr(ibv_ah_attr & ah_attr){ m_ah_attr = ah_attr; };
#if _BullseyeCoverage
    #pragma BullseyeCoverage on
#endif

	neigh_val & operator=(const neigh_val & val);


private:
	friend			class neigh_ib;
	friend 			class neigh_ib_broadcast;

	ibv_ah_attr		m_ah_attr;
	ibv_ah*			m_ah;
	uint32_t		m_qkey;

	void 			zero_all_members()
	{
				memset(&m_ah_attr, 0, sizeof(m_ah_attr));
				//m_ah 	= NULL;
				m_qkey 	= 0;
				neigh_val::zero_all_members();
	}
};

/* neigh_entry inherits from cache_entry_subject where
 * Key = address (peer IP)
 * Val = class neigh_val
 */
typedef std::deque<neigh_send_data *> unsent_queue_t;

class neigh_entry : public cache_entry_subject<neigh_key, neigh_val *>, public event_handler_rdma_cm, public timer_handler
{
public:
	enum type
	{
		UNKNOWN,
		MC,
		UC
	};

	enum state_t
	{
		ST_NOT_ACTIVE = 0,
		ST_INIT = 1,
		ST_INIT_RESOLUTION,
		ST_ADDR_RESOLVED,
		ST_ARP_RESOLVED,
		ST_PATH_RESOLVED,
		ST_READY,
		ST_ERROR,
		ST_LAST
	};

	enum event_t
	{
		EV_KICK_START = 0,
		EV_START_RESOLUTION,
		EV_ARP_RESOLVED,
		EV_ADDR_RESOLVED,
		EV_PATH_RESOLVED,
		EV_ERROR,
		EV_TIMEOUT_EXPIRED, // For IB MC join
		EV_UNHANDLED,
		EV_LAST
	};

	friend 	class		neighbour_table_mgr;

	neigh_entry (neigh_key key, transport_type_t type, bool is_init_resources = true);
	virtual 		~neigh_entry();

	//Overwrite cach_entry virtual function
	virtual bool 		is_deletable();
	virtual void 		clean_obj();

#if _BullseyeCoverage
    #pragma BullseyeCoverage off
#endif
	//Implementation of pure virtual function: Don't use get_val function, instead use get_peer_info
	virtual bool 		get_val(INOUT neigh_val * & val){ NOT_IN_USE(val); return false;};
#if _BullseyeCoverage
    #pragma BullseyeCoverage on
#endif

	virtual bool 		get_peer_info(neigh_val * val);
	// Overriding subject's register_observer
	virtual bool 		register_observer(const observer* const new_observer);
	//Overriding tostr to_str()
	virtual const std::string to_str() const;

#if _BullseyeCoverage
    #pragma BullseyeCoverage off
#endif
	transport_type_t 	get_type() { return m_trans_type; };
#if _BullseyeCoverage
    #pragma BullseyeCoverage on
#endif

	const char* 		event_to_str(event_t event) const;
	const char* 		state_to_str(state_t state) const;

	void    		handle_event_rdma_cm_cb(struct rdma_cm_event* p_event);
	void			handle_neigh_event(neigh_nl_event* nl_ev);

	static void		general_st_entry(const sm_info_t& func_info);
	static void		general_st_leave(const sm_info_t& func_info);
	static void 		print_event_info(int state, int event, void* app_data);
	static void 		dofunc_enter_init(const sm_info_t& func_info);
	static void 		dofunc_enter_init_resolution(const sm_info_t& func_info);
	static void 		dofunc_enter_addr_resolved(const sm_info_t& func_info);
	static void 		dofunc_enter_error(const sm_info_t& func_info);
	static void		dofunc_enter_not_active(const sm_info_t& func_info);
	static void		dofunc_enter_ready(const sm_info_t& func_info);

	//Implementing pure virtual function of sender
	virtual int		send(neigh_send_info &s_info);

protected:
	rdma_cm_id*		m_cma_id;
	sockaddr_in  		m_dst_addr;
	sockaddr_in  		m_src_addr;
	enum rdma_port_space 	m_rdma_port_space;
	state_machine*		m_state_machine;
	type			m_type; // UC  / MC
	transport_type_t	m_trans_type;
	bool			m_state;
	unsent_queue_t 		m_unsent_queue;
	//Counter to sign that KickStart was already generated in ERROR_ST
	uint32_t 		m_err_counter;

	void*			m_timer_handle;
	// members for sending arp
	uint32_t		m_arp_counter;
	net_device_val*		m_p_dev;
	ring* 			m_p_ring;
	vma_ibv_send_wr 	m_send_wqe;
	ibv_sge 		m_sge;
	bool 			m_is_loopback;

	const std::string	m_to_str;
	ring_user_id_t		m_id;

	virtual void 		priv_general_st_entry(const sm_info_t& func_info);
	virtual void 		priv_general_st_leave(const sm_info_t& func_info);
	virtual void		priv_print_event_info(state_t state, event_t event);
	virtual void		priv_kick_start_sm();
	virtual void		priv_enter_not_active();
	virtual void		priv_enter_error();
	virtual int 		priv_enter_init();
	virtual int 		priv_enter_init_resolution();
	virtual int 		priv_enter_addr_resolved();
	virtual int 		priv_enter_ready();

	bool 			priv_get_neigh_state(int & state);
	bool 			priv_get_neigh_l2(address_t & l2_addr);
	bool 			priv_is_reachable(int state) { return state & (NUD_REACHABLE | NUD_PERMANENT); }
	bool 			priv_is_failed(int state) { return state & (NUD_FAILED | NUD_INCOMPLETE); }

	void			event_handler(event_t event, void* p_event_info = NULL);
	void			priv_event_handler_no_locks(event_t event, void* p_event_info = NULL);
#if _BullseyeCoverage
    #pragma BullseyeCoverage off
#endif
	virtual bool 		priv_handle_neigh_is_l2_changed(address_t) { return false; };
#if _BullseyeCoverage
    #pragma BullseyeCoverage on
#endif
	void 			priv_handle_neigh_reachable_event();
	void 			priv_destroy_cma_id();
	virtual void* 		priv_register_timer_event(int timeout_msec, timer_handler* handler, timer_req_type_t req_type, void* user_data);
	void			priv_unregister_timer();

	virtual void 		send_arp();
	virtual bool 		post_send_arp(bool) { return true;};
	virtual bool 		prepare_to_send_packet(header *) {return true;};
	void			handle_timer_expired(void* user_data);

	virtual ring_user_id_t	generate_ring_user_id(header *h = NULL) { NOT_IN_USE(h); return m_p_ring->generate_id(); };

	lock_mutex_recursive    m_sm_lock;

private:
	bool 			m_is_first_send_arp;
	const uint32_t		m_n_sysvar_neigh_wait_till_send_arp_msec;
	const uint32_t		m_n_sysvar_neigh_uc_arp_quata;
	const uint32_t		m_n_sysvar_neigh_num_err_retries;
	ring_allocation_logic_tx m_ring_allocation_logic;
	event_t 		rdma_event_mapping(struct rdma_cm_event* p_event);
	void 			empty_unsent_queue();
	bool 			post_send_packet(neigh_send_data *n_send_data);
	bool			post_send_udp(neigh_send_data *n_send_data);
	bool			post_send_tcp(neigh_send_data *n_send_data);
};

class neigh_ib : public neigh_entry, public event_handler_ibverbs
{
public:
	friend 	class		neighbour_table_mgr;
				neigh_ib(neigh_key key, bool is_init_resources = true);
				~neigh_ib();

	static void		dofunc_enter_arp_resolved(const sm_info_t& func_info);
	static void		dofunc_enter_path_resolved(const sm_info_t& func_info);

protected:
	ibv_pd* 		m_pd;

	int			find_pd();
	int			create_ah();
	int			destroy_ah();
	virtual int 		build_mc_neigh_val(struct rdma_cm_event* event_data, uint32_t & wait_after_join_msec);

private:

	//Implementation of pure virtual functions
	void			handle_event_ibverbs_cb(void* ev_data, void* ctx);
	void			handle_timer_expired(void* user_data);

	// Overriding neigh_entry priv_enter_not_active
	void			priv_enter_not_active();
	void 			priv_enter_error();
	int			priv_enter_arp_resolved();
	int 			priv_enter_path_resolved(struct rdma_cm_event* event_data, uint32_t & wait_after_join_msec);
	virtual bool		priv_handle_neigh_is_l2_changed(address_t);
	// Overriding neigh_entry priv_enter_ready
	int			priv_enter_ready();

	int			handle_enter_arp_resolved_uc();
	int			handle_enter_arp_resolved_mc();
	int 			build_uc_neigh_val(struct rdma_cm_event* event_data, uint32_t & wait_after_join_msec);

	event_t 		ibverbs_event_mapping(void* p_event_info);
	virtual bool 		post_send_arp(bool);
	virtual bool 		prepare_to_send_packet(header *);

	const uint32_t		m_n_sysvar_wait_after_join_msec;
};

class neigh_ib_broadcast : public neigh_ib
{
public:
				neigh_ib_broadcast(neigh_key key);
	virtual int		send(neigh_send_info & s_info);
	virtual bool 		get_peer_info(neigh_val * p_val);
	virtual bool 		is_deletable() { return false; };

private:
	void 			build_mc_neigh_val();
	virtual void 		send_arp();
};

class neigh_eth : public neigh_entry
{
public:
	friend 	class		neighbour_table_mgr;
				neigh_eth(neigh_key key);
				~neigh_eth();
	virtual bool 		get_peer_info(neigh_val * val);
	//Overriding neigh_entry register_observer
	bool 			register_observer(const observer* const new_observer);
	//Overriding neigh_entry is_deletable
	virtual bool 		is_deletable();

protected:
	virtual ring_user_id_t	generate_ring_user_id(header * h = NULL);

private:

	int 			build_mc_neigh_val();
	int			build_uc_neigh_val();
	//Overriding neigh_entry priv_enter_ready
	virtual int 		priv_enter_ready();
	virtual int 		priv_enter_init();
	virtual int 		priv_enter_init_resolution();
	virtual bool 		priv_handle_neigh_is_l2_changed(address_t);
	virtual bool 		post_send_arp(bool is_broadcast);
	virtual bool 		prepare_to_send_packet(header *);
};

#endif /* NEIGHBOUR_H */
