# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2021 Nvidia, Inc. All rights reserved. See COPYING file

from enum import Enum
import unittest

from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr
from pyverbs.pyverbs_error import PyverbsRDMAError, PyverbsUserError
from tests.mlx5_base import Mlx5RDMATestCase, Mlx5RcResources
import pyverbs.providers.mlx5.mlx5_enums as dve
from pyverbs.pd import PD
from pyverbs.mr import MR
import pyverbs.enums as e
import tests.utils as u


class BadFlowType(Enum):
    DIFFERENT_PD = 1
    MR_ILLEGAL_ACCESS = 2


class Mlx5DmaResources(Mlx5RcResources):
    def create_send_ops_flags(self):
        self.dv_send_ops_flags = dve.MLX5DV_QP_EX_WITH_MEMCPY
        self.send_ops_flags = e.IBV_QP_EX_WITH_SEND


class DmaGgaMemcpy(Mlx5RDMATestCase):
    def create_resources(self, bad_flow_type=0, **resource_arg):
        """
        Creates DmaGga test resources that include a "server" resource that
        can be used to send the MEMCPY WR, and a destination MR to copy data to.
        The destination MR can be created on a different PD or with insufficient
        access permissions, according to the bad_flow_type.
        :param bad_flow_type: (Optional) An enum of BadFlowType that indicates
                              the bad flow type (default: 0 - good flow)
        :param resource_arg: Dict of args that specify the resource specific
                             attributes.
        :return: None
        """
        self.server = Mlx5DmaResources(**self.dev_info, **resource_arg)
        self.dest_pd = self.server.pd
        dest_mr_access = e.IBV_ACCESS_LOCAL_WRITE
        if bad_flow_type == BadFlowType.DIFFERENT_PD:
            self.dest_pd = PD(self.server.ctx)
        elif bad_flow_type == BadFlowType.MR_ILLEGAL_ACCESS:
            dest_mr_access = e.IBV_ACCESS_REMOTE_READ
        self.dest_mr = MR(self.dest_pd, self.server.msg_size, dest_mr_access)
        # No need to connect the QPs
        self.server.pre_run([0], [0])

    def dma_memcpy(self, msg_size=1024, bad_flow=False):
        """
        Creates resources and posts a memcpy WR.
        After posting the WR, the WC opcode and the data are verified.
        :param msg_size: Size of the data to be copied (in Bytes)
        :param bad_flow: If True, do not fill data in the MRs (default: False)
        :return: None
        """
        self.create_resources(msg_size=msg_size)
        if not bad_flow:
            self.dest_mr.write('0' * msg_size, msg_size)
            self.server.mr.write('s' * msg_size, msg_size)
        self.server.qp.wr_start()
        self.server.qp.wr_flags = e.IBV_SEND_SIGNALED
        self.server.qp.wr_memcpy(self.dest_mr.lkey, self.dest_mr.buf, self.server.mr.lkey,
                                 self.server.mr.buf, msg_size)
        self.server.qp.wr_complete()
        u.poll_cq_ex(self.server.cq)
        wc_opcode = self.server.cq.read_opcode()
        self.assertEqual(wc_opcode, dve.MLX5DV_WC_MEMCPY,
                         'WC opcode validation failed')
        self.assertEqual(self.dest_mr.read(msg_size, 0),
                         self.server.mr.read(msg_size, 0))

    def dma_memcpy_bad_protection_flow(self, bad_flow_type):
        """
        Creates resources with bad protection and posts a memcpy WR.
        The bad protection is either a destination MR created on a different PD
        or a destination MR created with insufficient access permissions.
        :param bad_flow_type: An enum of BadFlowType that indicates the bad flow type
        :return: None
        """
        self.create_resources(bad_flow_type)
        self.server.qp.wr_start()
        self.server.qp.wr_flags = e.IBV_SEND_SIGNALED
        self.server.qp.wr_memcpy(self.dest_mr.lkey, self.dest_mr.buf,
                                 self.server.mr.lkey,
                                 self.server.mr.buf, self.server.msg_size)
        self.server.qp.wr_complete()
        with self.assertRaises(PyverbsRDMAError):
            u.poll_cq_ex(self.server.cq)
        self.assertEqual(self.server.cq.status, e.IBV_WC_LOC_PROT_ERR,
                         'Expected CQE with Local Protection Error')

    def test_dma_memcpy_data(self):
        self.dma_memcpy()

    def test_dma_memcpy_different_pd_bad_flow(self):
        self.dma_memcpy_bad_protection_flow(BadFlowType.DIFFERENT_PD)

    def test_dma_memcpy_protection_bad_flow(self):
        self.dma_memcpy_bad_protection_flow(BadFlowType.MR_ILLEGAL_ACCESS)

    def test_dma_memcpy_large_data_bad_flow(self):
        """
        Bad flow test, testing DMA memcpy with data larger than the maximum
        allowed size, according to the HCA capabilities.
        :return: None
        """
        try:
            ctx = Mlx5Context(Mlx5DVContextAttr(), name=self.dev_name)
        except PyverbsUserError as ex:
            raise unittest.SkipTest(f'Could not open mlx5 context ({ex})')
        except PyverbsRDMAError:
            raise unittest.SkipTest('Opening mlx5 context is not supported')
        max_size = ctx.query_mlx5_device(
            dve.MLX5DV_CONTEXT_MASK_WR_MEMCPY_LENGTH).max_wr_memcpy_length
        max_size = max_size if max_size else 1024

        with self.assertRaises(PyverbsRDMAError):
            self.dma_memcpy(max_size + 1, bad_flow=True)
