/*
 * 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/util/utils.h"
#include "vma/dev/rfs_mc.h"
#include "vma/dev/ring_simple.h"

#define MODULE_NAME 		"rfs_mc"


rfs_mc::rfs_mc(flow_tuple *flow_spec_5t, ring_slave *p_ring, rfs_rule_filter* rule_filter /*= NULL*/, int flow_tag_id /*=0*/):
	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_mc called with non mc destination ip");
	}
	BULLSEYE_EXCLUDE_BLOCK_END

	if (m_p_ring->is_simple() && !prepare_flow_spec()) {
		throw_vma_exception("IB multicast offload is not supported");
	}
}

bool rfs_mc::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;

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

			if (0 == p_ring->m_p_qp_mgr->get_underly_qpn()) {
			// IB MC flow steering is done only on L2 --> need to zero other fields to get correct behaviour
			// CX3 HW does not support L3+L4 MC flow steering rule
#ifdef DEFINED_IBV_FLOW_SPEC_IB
				attach_flow_data_ib_v1_t*  attach_flow_data_ib_v1 = NULL;

				attach_flow_data_ib_v1 = new attach_flow_data_ib_v1_t(p_ring->m_p_qp_mgr);

				uint8_t dst_gid[16];
				create_mgid_from_ipv4_mc_ip(dst_gid, p_ring->m_p_qp_mgr->get_partiton(), m_flow_tuple.get_dst_ip());
				ibv_flow_spec_ib_set_by_dst_gid(&(attach_flow_data_ib_v1->ibv_flow_attr.ib),
							dst_gid);

				p_attach_flow_data = (attach_flow_data_t*)attach_flow_data_ib_v1;
				break;
#else
				return false;
#endif
			}

			attach_flow_data_ib_v2 = new attach_flow_data_ib_v2_t(p_ring->m_p_qp_mgr);

			ibv_flow_spec_ipv4_set(&(attach_flow_data_ib_v2->ibv_flow_attr.ipv4),
						m_flow_tuple.get_dst_ip(),
						0);

			ibv_flow_spec_tcp_udp_set(&(attach_flow_data_ib_v2->ibv_flow_attr.tcp_udp),
						(m_flow_tuple.get_protocol() == PROTO_TCP),
						m_flow_tuple.get_dst_port(),
						m_flow_tuple.get_src_port());

			p_attach_flow_data = (attach_flow_data_t*)attach_flow_data_ib_v2;
			break;
			}
		case VMA_TRANSPORT_ETH:
			{
			attach_flow_data_eth_ipv4_tcp_udp_t*  attach_flow_data_eth = NULL;

			attach_flow_data_eth = new attach_flow_data_eth_ipv4_tcp_udp_t(p_ring->m_p_qp_mgr);

			uint8_t dst_mac[6];
			create_multicast_mac_from_ip(dst_mac, m_flow_tuple.get_dst_ip());
			ibv_flow_spec_eth_set(&(attach_flow_data_eth->ibv_flow_attr.eth),
						dst_mac,
							htons(p_ring->m_p_qp_mgr->get_partiton()));

			if (safe_mce_sys().eth_mc_l2_only_rules) {
				ibv_flow_spec_ipv4_set(&(attach_flow_data_eth->ibv_flow_attr.ipv4), 0, 0);
				ibv_flow_spec_tcp_udp_set(&(attach_flow_data_eth->ibv_flow_attr.tcp_udp), 0, 0, 0);
				p_attach_flow_data = (attach_flow_data_t*)attach_flow_data_eth;
				break;
			}

			ibv_flow_spec_ipv4_set(&(attach_flow_data_eth->ibv_flow_attr.ipv4),
						m_flow_tuple.get_dst_ip(),
						0);

			ibv_flow_spec_tcp_udp_set(&(attach_flow_data_eth->ibv_flow_attr.tcp_udp),
						(m_flow_tuple.get_protocol() == PROTO_TCP),
						m_flow_tuple.get_dst_port(),
						m_flow_tuple.get_src_port());

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

			p_attach_flow_data = (attach_flow_data_t*)attach_flow_data_eth;
			break;
			}
		BULLSEYE_EXCLUDE_BLOCK_START
		default:
			rfs_logpanic("Incompatible transport type = %d", type);
			return false;
			break;
		BULLSEYE_EXCLUDE_BLOCK_END
	}

	m_attach_flow_data_vector.push_back(p_attach_flow_data);
	return true;
}

bool rfs_mc::rx_dispatch_packet(mem_buf_desc_t* p_rx_wc_buf_desc, void* pv_fd_ready_array)
{
	// Dispatching: Notify new packet to all registered receivers
	p_rx_wc_buf_desc->reset_ref_count();
	p_rx_wc_buf_desc->inc_ref_count();

	for (uint32_t i=0; i < m_n_sinks_list_entries; ++i) {
		if (m_sinks_list[i]) {
			m_sinks_list[i]->rx_input_cb(p_rx_wc_buf_desc, pv_fd_ready_array);
		}
	}

	// Check packet ref_count to see if any receivers are 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;
}
