/*
 * 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.
 */


#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <linux/netlink.h>
#include <linux/fib_rules.h>
#include <netinet/in.h>
#include <netinet/ether.h>
#include <arpa/inet.h>

#include "utils/bullseye.h"
#include "utils/lock_wrapper.h"
#include "vlogger/vlogger.h"
#include "vma/util/vtypes.h"
#include "vma/util/utils.h"
#include "vma/util/if.h"
#include "rule_table_mgr.h"
#include "vma/sock/socket_fd_api.h"
#include "vma/sock/sock-redirect.h"
#include "ip_address.h"

// debugging macros
#define MODULE_NAME 		"rrm:"

#define rr_mgr_if_logpanic	__log_panic
#define	rr_mgr_logerr		__log_err
#define rr_mgr_logwarn		__log_warn
#define rr_mgr_loginfo		__log_info
#define rr_mgr_logdbg		__log_dbg
#define rr_mgr_logfunc		__log_func
#define rr_mgr_logfuncall	__log_funcall
	
rule_table_mgr* g_p_rule_table_mgr = NULL;

rule_table_mgr::rule_table_mgr() : netlink_socket_mgr<rule_val>(RULE_DATA_TYPE), cache_table_mgr<route_rule_table_key, std::deque<rule_val*>*>("rule_table_mgr")
{

	rr_mgr_logdbg("");

	//Read Rule table from kernel and save it in local variable. 
	update_tbl();
	
	//Print table
	print_val_tbl();
	
	rr_mgr_logdbg("Done");
}

//This function uses Netlink to get routing rules saved in kernel then saved it locally.
void rule_table_mgr::update_tbl()
{
	auto_unlocker lock(m_lock);

	netlink_socket_mgr<rule_val>::update_tbl();

	return;
}

// Parse received rule entry into custom object (rule_val).
// Parameters: 
//		nl_header	: object that contain rule entry.
//		p_val		: custom object that contain parsed rule data.
// return true if its not related to local or default table, false otherwise.
bool rule_table_mgr::parse_enrty(nlmsghdr *nl_header, rule_val *p_val)
{
	int len;
	struct rtmsg *rt_msg;
	struct rtattr *rt_attribute;

	// get rule entry header
	rt_msg = (struct rtmsg *) NLMSG_DATA(nl_header);

	// we are not concerned about the local and default rule table
	if (rt_msg->rtm_family != AF_INET || rt_msg->rtm_table == RT_TABLE_LOCAL)
		return false;

	p_val->set_protocol(rt_msg->rtm_protocol);
	p_val->set_scope(rt_msg->rtm_scope);
	p_val->set_type(rt_msg->rtm_type);
	p_val->set_tos(rt_msg->rtm_tos);
	p_val->set_table_id(rt_msg->rtm_table);

	len = RTM_PAYLOAD(nl_header);
	rt_attribute = (struct rtattr *) RTM_RTA(rt_msg);

	for (;RTA_OK(rt_attribute, len);rt_attribute=RTA_NEXT(rt_attribute,len)) {
		parse_attr(rt_attribute, p_val);
	}
	p_val->set_state(true);
	p_val->set_str();
	return true;
}

// Parse received rule attribute for given rule.
// Parameters: 
//		rt_attribute	: object that contain rule attribute.
//		p_val			: custom object that contain parsed rule data.
void rule_table_mgr::parse_attr(struct rtattr *rt_attribute, rule_val *p_val)
{
	switch (rt_attribute->rta_type) {
		case FRA_PRIORITY:
			p_val->set_priority(*(uint32_t *)RTA_DATA(rt_attribute));
			break;
		case FRA_DST:
			p_val->set_dst_addr(*(in_addr_t *)RTA_DATA(rt_attribute));
			break;
		case FRA_SRC:
			p_val->set_src_addr(*(in_addr_t *)RTA_DATA(rt_attribute));
			break;
		case FRA_IFNAME:
			p_val->set_iif_name((char *)RTA_DATA(rt_attribute));
			break;
		case FRA_TABLE:
			p_val->set_table_id(*(uint32_t *)RTA_DATA(rt_attribute));
			break;
#if DEFINED_FRA_OIFNAME
		case FRA_OIFNAME:
			p_val->set_oif_name((char *)RTA_DATA(rt_attribute));
			break;
#endif
		default:
			rr_mgr_logdbg("got undetected rta_type %d %x", rt_attribute->rta_type, *(uint32_t *)RTA_DATA(rt_attribute));
			break;
	}
}


// Create rule entry object for given destination key and fill it with matching rule value from rule table.
// Parameters: 
//		key		: key object that contain information about destination.
//		obs		: object that contain observer for specific rule entry.
//	Returns created rule entry object.
rule_entry* rule_table_mgr::create_new_entry(route_rule_table_key key, const observer *obs)
{
	rr_mgr_logdbg("");
	NOT_IN_USE(obs);
	rule_entry* p_ent = new rule_entry(key);
	update_entry(p_ent);
	rr_mgr_logdbg("new entry %p created successfully", p_ent);
	return p_ent;
}

// Update invalid rule entry with matching rule value from rule table.
// Parameters: 
//		p_ent		: rule entry that will be updated if it is invalid.
void rule_table_mgr::update_entry(rule_entry* p_ent)
{
	rr_mgr_logdbg("entry [%p]", p_ent);
	auto_unlocker lock(m_lock);
	
	if (p_ent && !p_ent->is_valid()) { //if entry is found in the collection and is not valid
		
		rr_mgr_logdbg("rule_entry is not valid-> update value");
		std::deque<rule_val*>* p_rrv;
		p_ent->get_val(p_rrv);
		/* p_rrv->clear(); TODO for future rule live updates */
		if (!find_rule_val(p_ent->get_key(), p_rrv)) {
			rr_mgr_logdbg("ERROR: could not find rule val for rule_entry '%s'", p_ent->to_str().c_str());
		}
	} 
}

// Find rule form rule table that match given destination info. 
// Parameters: 
//		key		: key object that contain information about destination.
//		p_val	: list of rule_val object that will contain information about all rule that match destination info    
// Returns true if at least one rule match destination info, false otherwise.
bool rule_table_mgr::find_rule_val(route_rule_table_key key, std::deque<rule_val*>* &p_val)
{
	rr_mgr_logfunc("destination info %s:", key.to_str().c_str());

	for (int index = 0; index < m_tab.entries_num; index++) {
		rule_val* p_val_from_tbl = &m_tab.value[index];
		if (p_val_from_tbl->is_valid() && is_matching_rule(key, p_val_from_tbl)) {
			p_val->push_back(p_val_from_tbl);
			rr_mgr_logdbg("found rule val[%p]: %s", p_val_from_tbl, p_val_from_tbl->to_str());
		}
	}

	return !p_val->empty();
}

// Check matching between given destination info. and specific rule from rule table. 
// Parameters: 
//		key		: key object that contain information about destination.
//		p_val	: rule_val object that contain information about specific rule from rule table   
// Returns true if destination info match rule value, false otherwise.
bool rule_table_mgr::is_matching_rule(route_rule_table_key key, rule_val* p_val)
{

	in_addr_t	m_dst_ip	= key.get_dst_ip();
	in_addr_t	m_src_ip	= key.get_src_ip();
	uint8_t		m_tos		= key.get_tos();
	
	in_addr_t	rule_dst_ip	= p_val->get_dst_addr();
	in_addr_t	rule_src_ip	= p_val->get_src_addr();
	uint8_t		rule_tos	= p_val->get_tos();
	char*		rule_iif_name	= (char *)p_val->get_iif_name();
	char*		rule_oif_name	= (char *)p_val->get_oif_name();
	
	bool is_match = false;
	
	// Only destination IP, source IP and TOS are checked with rule, since IIF and OIF is not filled in dst_entry object.
	if ((rule_dst_ip == 0) || (rule_dst_ip == m_dst_ip)) { // Check match in destination IP
	
		if ((rule_src_ip == 0) || (rule_src_ip == m_src_ip)) { // Check match in source IP
		
			if ((rule_tos == 0) || (rule_tos == m_tos)) { // Check match in TOS value
			
				if (strcmp(rule_iif_name, "") == 0) { // Check that rule doesn't contain IIF since we can't check match with
				
					if (strcmp(rule_oif_name, "") == 0) { // Check that rule doesn't contain OIF since we can't check match with
						is_match = true;
					}
				}
			}
		}
	}

	return is_match;
}

// Find table ID for given destination info.
// Parameters: 
//		key			: key object that contain information about destination.
//		table_id_list	: list that will contain table ID for all rule that match destination info   
// Returns true if at least one rule match destination info, false otherwise.
bool rule_table_mgr::rule_resolve(route_rule_table_key key, std::deque<unsigned char> &table_id_list)
{
	rr_mgr_logdbg("dst info: '%s'", key.to_str().c_str());

	std::deque<rule_val*> values;
	std::deque<rule_val*>* p_values = &values;
	auto_unlocker lock(m_lock);
	if (find_rule_val(key, p_values)) {
		for (std::deque<rule_val*>::iterator val = values.begin(); val != values.end(); val++) {
			table_id_list.push_back((*val)->get_table_id());
			rr_mgr_logdbg("dst info: '%s' resolved to table ID '%u'", key.to_str().c_str(), (*val)->get_table_id());
		}
	}
	
	return !table_id_list.empty();
}

