/*
 * Copyright (c) 2001-2022 NVIDIA CORPORATION & AFFILIATES. 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 "utils/bullseye.h"
#include "vma/dev/rfs_uc.h"
#include "vma/proto/L2_address.h"
#include "vma/dev/ring_simple.h"
#include "util/instrumentation.h"

#define MODULE_NAME 		"rfs_uc"


rfs_uc::rfs_uc(flow_tuple *flow_spec_5t, ring_slave *p_ring, rfs_rule_filter* rule_filter, uint32_t flow_tag_id) :
	rfs(flow_spec_5t, p_ring, rule_filter, flow_tag_id)
{
	BULLSEYE_EXCLUDE_BLOCK_START
	if (m_flow_tuple.is_udp_mc()) {
		throw_vma_exception("rfs_uc called with MC destination ip");
	}
	BULLSEYE_EXCLUDE_BLOCK_END

	if (m_p_ring->is_simple() && !prepare_flow_spec()) {
		throw_vma_exception("rfs_uc: Incompatible transport type");
	}
}

bool rfs_uc::prepare_flow_spec()
{
	ring_simple* p_ring = dynamic_cast<ring_simple*>(m_p_ring);

	if (!p_ring) {
		rfs_logpanic("Incompatible ring type");
	}

	transport_type_t type = p_ring->get_transport_type();
	/*
	 * todo note that ring is not locked here.
	 * we touch members that should not change during the ring life.
	 * the ring will not be deleted as we increased refcnt.
	 * if one of these assumptions change, we must lock.
	 */
	attach_flow_data_t* 		       p_attach_flow_data = NULL;
	vma_ibv_flow_spec_ipv4*             p_ipv4 = NULL;
	vma_ibv_flow_spec_tcp_udp*          p_tcp_udp = NULL;
	vma_ibv_flow_spec_action_tag*       p_flow_tag = NULL;

	attach_flow_data_eth_ipv4_tcp_udp_t*   attach_flow_data_eth = NULL;

	switch (type) {
		case VMA_TRANSPORT_IB:
			{
			attach_flow_data_ib_ipv4_tcp_udp_v2_t* attach_flow_data_ib_v2 = NULL;

#ifdef DEFINED_IBV_FLOW_SPEC_IB
			if (0 == p_ring->m_p_qp_mgr->get_underly_qpn()) {
				attach_flow_data_ib_ipv4_tcp_udp_v1_t* attach_flow_data_ib_v1 = NULL;

				attach_flow_data_ib_v1 = new attach_flow_data_ib_ipv4_tcp_udp_v1_t(p_ring->m_p_qp_mgr);
				ibv_flow_spec_ib_set_by_dst_qpn(&(attach_flow_data_ib_v1->ibv_flow_attr.ib),
							htonl(((IPoIB_addr*)p_ring->m_p_l2_addr)->get_qpn()));
				p_ipv4 = &(attach_flow_data_ib_v1->ibv_flow_attr.ipv4);
				p_tcp_udp = &(attach_flow_data_ib_v1->ibv_flow_attr.tcp_udp);
				p_attach_flow_data = (attach_flow_data_t*)attach_flow_data_ib_v1;
				break;
			}
#endif
			attach_flow_data_ib_v2 = new attach_flow_data_ib_ipv4_tcp_udp_v2_t(p_ring->m_p_qp_mgr);

			p_ipv4 = &(attach_flow_data_ib_v2->ibv_flow_attr.ipv4);
			p_tcp_udp = &(attach_flow_data_ib_v2->ibv_flow_attr.tcp_udp);
			p_attach_flow_data = (attach_flow_data_t*)attach_flow_data_ib_v2;
			break;
			}
		case VMA_TRANSPORT_ETH:
			{
			attach_flow_data_eth = new attach_flow_data_eth_ipv4_tcp_udp_t(p_ring->m_p_qp_mgr);

			ibv_flow_spec_eth_set(&(attach_flow_data_eth->ibv_flow_attr.eth),
					p_ring->m_p_l2_addr->get_address(),
						htons(p_ring->m_p_qp_mgr->get_partiton()));
			p_ipv4 = &(attach_flow_data_eth->ibv_flow_attr.ipv4);
			p_tcp_udp = &(attach_flow_data_eth->ibv_flow_attr.tcp_udp);
			p_flow_tag = &(attach_flow_data_eth->ibv_flow_attr.flow_tag);
			p_attach_flow_data = (attach_flow_data_t*)attach_flow_data_eth;
			break;
			}
		BULLSEYE_EXCLUDE_BLOCK_START
		default:
			return false;
			break;
		BULLSEYE_EXCLUDE_BLOCK_END
	}

	ibv_flow_spec_ipv4_set(p_ipv4,
				m_flow_tuple.get_dst_ip(),
				m_flow_tuple.get_src_ip());

	ibv_flow_spec_tcp_udp_set(p_tcp_udp,
				(m_flow_tuple.get_protocol() == PROTO_TCP),
				m_flow_tuple.get_dst_port(),
				m_flow_tuple.get_src_port());

	if (m_flow_tuple.get_src_port() || m_flow_tuple.get_src_ip()) {
		// set priority of 5-tuple to be higher than 3-tuple
		// to make sure 5-tuple have higher priority on ConnectX-4
		p_attach_flow_data->ibv_flow_attr.priority = 0;
	}

	if (m_flow_tag_id && attach_flow_data_eth) { // Will not attach flow_tag spec to rule for tag_id==0
		ibv_flow_spec_flow_tag_set(p_flow_tag, m_flow_tag_id);
		attach_flow_data_eth->ibv_flow_attr.add_flow_tag_spec();
                rfs_logdbg("Adding flow_tag spec to rule, num_of_specs: %d flow_tag_id: %d",
			   attach_flow_data_eth->ibv_flow_attr.attr.num_of_specs,
			   m_flow_tag_id);
	}
        rfs_logfunc("transport type: %s, num_of_specs: %d flow_tag_id: %d",
			   priv_vma_transport_type_str(type),
			   p_attach_flow_data->ibv_flow_attr.num_of_specs,
			   m_flow_tag_id);

	m_attach_flow_data_vector.push_back(p_attach_flow_data);
	return true;
}

bool rfs_uc::rx_dispatch_packet(mem_buf_desc_t* p_rx_wc_buf_desc, void* pv_fd_ready_array)
{
	static int enable_socketxtreme = safe_mce_sys().enable_socketxtreme;
	uint32_t num_sinks = (enable_socketxtreme ?
			1 : m_n_sinks_list_entries);

	p_rx_wc_buf_desc->reset_ref_count();

	for (uint32_t i=0; i < num_sinks; ++i) {
		if (likely(m_sinks_list[i])) {
#ifdef RDTSC_MEASURE_RX_DISPATCH_PACKET
	RDTSC_TAKE_START(g_rdtsc_instr_info_arr[RDTSC_FLOW_RX_DISPATCH_PACKET]);
#endif //RDTSC_MEASURE_RX_DISPATCH_PACKET
			p_rx_wc_buf_desc->inc_ref_count();
			m_sinks_list[i]->rx_input_cb(p_rx_wc_buf_desc, pv_fd_ready_array);
#ifdef RDTSC_MEASURE_RX_DISPATCH_PACKET
	RDTSC_TAKE_END(g_rdtsc_instr_info_arr[RDTSC_FLOW_RX_DISPATCH_PACKET]);
#endif //RDTSC_MEASURE_RX_DISPATCH_PACKET
			// Check packet ref_count to see the last receiver is interested in this packet
			if (p_rx_wc_buf_desc->dec_ref_count() > 1) {
				// The sink will be responsible to return the buffer to CQ for reuse
				return true;
			}
		}
	}
	// Reuse this data buffer & mem_buf_desc
	return false;
}
