# NB NB NB NB NB NB NB NB NB NB NB
#
# NOTE: The API binary wrappers in this file are in the process of being
# deprecated. DO NOT ADD NEW WRAPPERS HERE. Call the functions using
# named arguments directly instead.
#

import os
import time
import queue
from six import moves, iteritems
from config import config
from vpp_papi import VPPApiClient
from hook import Hook
from vpp_papi_exceptions import (
    CliFailedCommandError,
    CliSyntaxError,
    UnexpectedApiReturnValueError,
)

#
# Dictionary keyed on message name to override default values for
# named parameters
#
defaultmapping = {
    "acl_interface_add_del": {"is_add": 1, "is_input": 1},
    "bd_ip_mac_add_del": {
        "is_add": 1,
    },
    "bfd_udp_add": {"is_authenticated": False, "bfd_key_id": None, "conf_key_id": None},
    "bfd_udp_auth_activate": {
        "bfd_key_id": None,
        "conf_key_id": None,
        "is_delayed": False,
    },
    "bier_disp_entry_add_del": {
        "next_hop_rpf_id": -1,
        "next_hop_is_ip4": 1,
        "is_add": 1,
    },
    "bier_disp_table_add_del": {
        "is_add": 1,
    },
    "bier_imp_add": {
        "is_add": 1,
    },
    "bier_route_add_del": {
        "is_add": 1,
    },
    "bier_table_add_del": {
        "is_add": 1,
    },
    "bvi_delete": {},
    "geneve_add_del_tunnel": {
        "mcast_sw_if_index": 4294967295,
        "is_add": 1,
        "decap_next_index": 4294967295,
    },
    "input_acl_set_interface": {
        "ip4_table_index": 4294967295,
        "ip6_table_index": 4294967295,
        "l2_table_index": 4294967295,
    },
    "ip6_add_del_address_using_prefix": {
        "is_add": 1,
    },
    "ip6nd_send_router_solicitation": {
        "irt": 1,
        "mrt": 120,
    },
    "ip_add_del_route": {
        "next_hop_sw_if_index": 4294967295,
        "next_hop_weight": 1,
        "next_hop_via_label": 1048576,
        "classify_table_index": 4294967295,
        "is_add": 1,
    },
    "ip_mroute_add_del": {
        "is_add": 1,
    },
    "ip_neighbor_add_del": {
        "is_add": 1,
    },
    "ipsec_interface_add_del_spd": {
        "is_add": 1,
    },
    "ipsec_spd_add_del": {
        "is_add": 1,
    },
    "ipsec_spd_dump": {
        "sa_id": 4294967295,
    },
    "ipsec_spd_entry_add_del": {
        "local_port_stop": 65535,
        "remote_port_stop": 65535,
        "priority": 100,
        "is_outbound": 1,
        "is_add": 1,
    },
    "ipsec_tunnel_if_add_del": {
        "is_add": 1,
        "anti_replay": 1,
    },
    "l2_emulation": {
        "enable": 1,
    },
    "l2fib_add_del": {
        "is_add": 1,
    },
    "lisp_add_del_adjacency": {
        "is_add": 1,
    },
    "lisp_add_del_local_eid": {
        "is_add": 1,
    },
    "lisp_add_del_locator": {
        "priority": 1,
        "weight": 1,
        "is_add": 1,
    },
    "lisp_add_del_locator_set": {
        "is_add": 1,
    },
    "lisp_add_del_remote_mapping": {
        "is_add": 1,
    },
    "macip_acl_interface_add_del": {
        "is_add": 1,
    },
    "mpls_ip_bind_unbind": {
        "is_ip4": 1,
        "is_bind": 1,
    },
    "mpls_route_add_del": {
        "mr_next_hop_sw_if_index": 4294967295,
        "mr_next_hop_weight": 1,
        "mr_next_hop_via_label": 1048576,
        "mr_is_add": 1,
        "mr_classify_table_index": 4294967295,
    },
    "mpls_table_add_del": {
        "is_add": 1,
    },
    "mpls_tunnel_add_del": {
        "next_hop_sw_if_index": 4294967295,
        "next_hop_weight": 1,
        "next_hop_via_label": 1048576,
        "is_add": 1,
    },
    "output_acl_set_interface": {
        "ip4_table_index": 4294967295,
        "ip6_table_index": 4294967295,
        "l2_table_index": 4294967295,
    },
    "pppoe_add_del_session": {
        "is_add": 1,
    },
    "policer_add_del": {
        "is_add": 1,
        "conform_action": {"type": 1},
    },
    "set_ipfix_exporter": {
        "collector_port": 4739,
    },
    "sr_policy_add": {
        "weight": 1,
        "is_encap": 1,
    },
    "sw_interface_add_del_address": {
        "is_add": 1,
    },
    "sw_interface_ip6nd_ra_prefix": {
        "val_lifetime": 4294967295,
        "pref_lifetime": 4294967295,
    },
    "sw_interface_set_ip_directed_broadcast": {
        "enable": 1,
    },
    "sw_interface_set_l2_bridge": {
        "enable": 1,
    },
    "sw_interface_set_mpls_enable": {
        "enable": 1,
    },
    "sw_interface_set_mtu": {
        "mtu": [0, 0, 0, 0],
    },
    "sw_interface_set_unnumbered": {
        "is_add": 1,
    },
    "sw_interface_span_enable_disable": {
        "state": 1,
    },
    "vxlan_add_del_tunnel": {
        "mcast_sw_if_index": 4294967295,
        "is_add": 1,
        "decap_next_index": 4294967295,
        "instance": 4294967295,
    },
    "want_bfd_events": {
        "enable_disable": 1,
    },
    "want_igmp_events": {
        "enable": 1,
    },
    "want_interface_events": {
        "enable_disable": 1,
    },
    "want_l2_macs_events": {
        "enable_disable": 1,
        "pid": os.getpid(),
    },
    "want_l2_macs_events2": {
        "enable_disable": 1,
        "pid": os.getpid(),
    },
}


def as_fn_signature(d):
    return ", ".join(f"{k}={v}" for k, v in d.items())


class VppPapiProvider(object):
    """VPP-api provider using vpp-papi

    @property hook: hook object providing before and after api/cli hooks
    """

    _zero, _negative = range(2)

    def __init__(self, name, test_class, read_timeout):
        self.hook = Hook(test_class)
        self.name = name
        self.test_class = test_class
        self._expect_api_retval = self._zero
        self._expect_stack = []

        # install_dir is a class attribute. We need to set it before
        # calling the constructor.
        VPPApiClient.apidir = config.vpp_install_dir

        self.vpp = VPPApiClient(
            logger=test_class.logger,
            read_timeout=read_timeout,
            use_socket=True,
            server_address=test_class.get_api_sock_path(),
        )
        self._events = queue.Queue()

    def __enter__(self):
        return self

    def assert_negative_api_retval(self):
        """Expect API failure - used with with, e.g.::

            with self.vapi.assert_negative_api_retval():
                self.vapi.<api call expected to fail>

        ..
        """
        self._expect_stack.append(self._expect_api_retval)
        self._expect_api_retval = self._negative
        return self

    def assert_zero_api_retval(self):
        """Expect API success - used with with, e.g.::

            with self.vapi.assert_negative_api_retval():
                self.vapi.<api call expected to succeed>

        :note: this is useful only inside another with block
             as success is the default expected value
        """
        self._expect_stack.append(self._expect_api_retval)
        self._expect_api_retval = self._zero
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self._expect_api_retval = self._expect_stack.pop()

    def register_hook(self, hook):
        """Replace hook registration with new hook

        :param hook:

        """
        self.hook = hook

    def collect_events(self):
        """Collect all events from the internal queue and clear the queue."""
        result = []
        while True:
            try:
                e = self._events.get(block=False)
                result.append(e)
            except queue.Empty:
                return result
        return result

    def wait_for_event(self, timeout, name=None):
        """Wait for and return next event."""
        if name:
            self.test_class.logger.debug(
                "Expecting event '%s' within %ss", name, timeout
            )
        else:
            self.test_class.logger.debug("Expecting event within %ss", timeout)
        try:
            e = self._events.get(timeout=timeout)
        except queue.Empty:
            raise Exception("Event did not occur within timeout")
        msgname = type(e).__name__
        if name and msgname != name:
            raise Exception("Unexpected event received: %s, expected: %s" % msgname)
        self.test_class.logger.debug("Returning event %s:%s" % (name, e))
        return e

    def __call__(self, name, event):
        """Enqueue event in the internal event queue."""
        self.test_class.logger.debug("New event: %s: %s" % (name, event))
        self._events.put(event)

    def factory(self, name, apifn):
        def f(*a, **ka):
            fields = apifn._func.msg.fields

            # add positional and kw arguments
            d = ka
            for i, o in enumerate(fields[3:]):
                try:
                    d[o] = a[i]
                except BaseException:
                    break

            # Default override
            if name in defaultmapping:
                for k, v in iteritems(defaultmapping[name]):
                    if k in d:
                        continue
                    d[k] = v
            return self.api(apifn, d)

        return f

    def __getattribute__(self, name):
        try:
            method = super(VppPapiProvider, self).__getattribute__(name)
        except AttributeError:
            method = self.factory(name, getattr(self.papi, name))
            # lazily load the method so we don't need to call factory
            # again for this name.
            setattr(self, name, method)
        return method

    def connect(self):
        """Connect the API to VPP"""
        # This might be called before VPP is prepared to listen to the socket
        retries = 0
        while not os.path.exists(self.test_class.get_api_sock_path()):
            time.sleep(0.5)
            retries += 1
            if retries > 120:
                break
        self.vpp.connect(self.name[:63])
        self.papi = self.vpp.api
        self.vpp.register_event_callback(self)

    def disconnect(self):
        """Disconnect the API from VPP"""
        self.vpp.disconnect()

    def api(self, api_fn, api_args, expected_retval=0):
        """Call API function and check it's return value.
        Call the appropriate hooks before and after the API call

        :param api_fn: API function to call
        :param api_args: tuple of API function arguments
        :param expected_retval: Expected return value (Default value = 0)
        :returns: reply from the API

        """
        self.hook.before_api(api_fn.__name__, api_args)
        reply = api_fn(**api_args)
        if self._expect_api_retval == self._negative:
            if hasattr(reply, "retval") and reply.retval >= 0:
                msg = (
                    "%s(%s) passed unexpectedly: expected negative "
                    "return value instead of %d in %s"
                    % (
                        api_fn.__name__,
                        as_fn_signature(api_args),
                        reply.retval,
                        moves.reprlib.repr(reply),
                    )
                )
                self.test_class.logger.info(msg)
                raise UnexpectedApiReturnValueError(reply.retval, msg)
        elif self._expect_api_retval == self._zero:
            if hasattr(reply, "retval") and reply.retval != expected_retval:
                msg = (
                    "%s(%s) failed, expected %d return value instead "
                    "of %d in %s"
                    % (
                        api_fn.__name__,
                        as_fn_signature(api_args),
                        expected_retval,
                        reply.retval,
                        repr(reply),
                    )
                )
                self.test_class.logger.info(msg)
                raise UnexpectedApiReturnValueError(reply.retval, msg)
        else:
            raise Exception(
                "Internal error, unexpected value for "
                "self._expect_api_retval %s" % self._expect_api_retval
            )
        self.hook.after_api(api_fn.__name__, api_args)
        return reply

    def cli_return_response(self, cli):
        """Execute a CLI, calling the before/after hooks appropriately.
        Return the reply without examining it

        :param cli: CLI to execute
        :returns: response object

        """
        self.hook.before_cli(cli)
        cli += "\n"
        r = self.papi.cli_inband(cmd=cli)
        self.hook.after_cli(cli)
        return r

    def cli(self, cli):
        """Execute a CLI, calling the before/after hooks appropriately.

        :param cli: CLI to execute
        :returns: CLI output

        """
        r = self.cli_return_response(cli)
        if r.retval == -156:
            raise CliSyntaxError(r.reply)
        if r.retval != 0:
            raise CliFailedCommandError(r.reply)
        if hasattr(r, "reply"):
            return r.reply

    def ppcli(self, cli):
        """Helper method to print CLI command in case of info logging level.

        :param cli: CLI to execute
        :returns: CLI output
        """
        return cli + "\n" + self.cli(cli)

    def ip6nd_send_router_solicitation(self, sw_if_index, irt=1, mrt=120, mrc=0, mrd=0):
        return self.api(
            self.papi.ip6nd_send_router_solicitation,
            {
                "irt": irt,
                "mrt": mrt,
                "mrc": mrc,
                "mrd": mrd,
                "sw_if_index": sw_if_index,
            },
        )

    def want_interface_events(self, enable_disable=1):
        return self.api(
            self.papi.want_interface_events,
            {
                "enable_disable": enable_disable,
                "pid": os.getpid(),
            },
        )

    def sw_interface_set_mac_address(self, sw_if_index, mac):
        return self.api(
            self.papi.sw_interface_set_mac_address,
            {"sw_if_index": sw_if_index, "mac_address": mac},
        )

    def p2p_ethernet_add(self, sw_if_index, remote_mac, subif_id):
        """Create p2p ethernet subinterface

        :param sw_if_index: main (parent) interface
        :param remote_mac: client (remote) mac address

        """
        return self.api(
            self.papi.p2p_ethernet_add,
            {
                "parent_if_index": sw_if_index,
                "remote_mac": remote_mac,
                "subif_id": subif_id,
            },
        )

    def p2p_ethernet_del(self, sw_if_index, remote_mac):
        """Delete p2p ethernet subinterface

        :param sw_if_index: main (parent) interface
        :param remote_mac: client (remote) mac address

        """
        return self.api(
            self.papi.p2p_ethernet_del,
            {"parent_if_index": sw_if_index, "remote_mac": remote_mac},
        )

    def create_vlan_subif(self, sw_if_index, vlan):
        """

        :param vlan:
        :param sw_if_index:

        """
        return self.api(
            self.papi.create_vlan_subif, {"sw_if_index": sw_if_index, "vlan_id": vlan}
        )

    def create_loopback(self, mac=""):
        """

        :param mac: (Optional)
        """
        return self.api(self.papi.create_loopback, {"mac_address": mac})

    def ip_route_dump(self, table_id, is_ip6=False):
        return self.api(
            self.papi.ip_route_dump, {"table": {"table_id": table_id, "is_ip6": is_ip6}}
        )

    def ip_route_v2_dump(self, table_id, is_ip6=False, src=0):
        return self.api(
            self.papi.ip_route_v2_dump,
            {"src": src, "table": {"table_id": table_id, "is_ip6": is_ip6}},
        )

    def ip_neighbor_add_del(
        self, sw_if_index, mac_address, ip_address, is_add=1, flags=0
    ):
        """Add neighbor MAC to IPv4 or IPv6 address.

        :param sw_if_index:
        :param mac_address:
        :param dst_address:
        :param is_add:  (Default value = 1)
        :param flags:  (Default value = 0/NONE)
        """
        return self.api(
            self.papi.ip_neighbor_add_del,
            {
                "is_add": is_add,
                "neighbor": {
                    "sw_if_index": sw_if_index,
                    "flags": flags,
                    "mac_address": mac_address,
                    "ip_address": ip_address,
                },
            },
        )

    def udp_encap_add(self, src_ip, dst_ip, src_port, dst_port, table_id=0):
        """Add a GRE tunnel
        :param src_ip:
        :param dst_ip:
        :param src_port:
        :param dst_port:
        :param outer_fib_id:  (Default value = 0)
        """

        return self.api(
            self.papi.udp_encap_add,
            {
                "udp_encap": {
                    "src_ip": src_ip,
                    "dst_ip": dst_ip,
                    "src_port": src_port,
                    "dst_port": dst_port,
                    "table_id": table_id,
                }
            },
        )

    def udp_encap_del(self, id):
        return self.api(self.papi.udp_encap_del, {"id": id})

    def udp_encap_dump(self):
        return self.api(self.papi.udp_encap_dump, {})

    def want_udp_encap_stats(self, enable=1):
        return self.api(
            self.papi.want_udp_encap_stats, {"enable": enable, "pid": os.getpid()}
        )

    def mpls_route_dump(self, table_id):
        return self.api(self.papi.mpls_route_dump, {"table": {"mt_table_id": table_id}})

    def mpls_table_dump(self):
        return self.api(self.papi.mpls_table_dump, {})

    def mpls_table_add_del(self, table_id, is_add=1):
        """

        :param table_id
        :param is_add:  (Default value = 1)

        """

        return self.api(
            self.papi.mpls_table_add_del,
            {
                "mt_table": {
                    "mt_table_id": table_id,
                },
                "mt_is_add": is_add,
            },
        )

    def mpls_route_add_del(
        self, table_id, label, eos, eos_proto, is_multicast, paths, is_add, is_multipath
    ):
        """MPLS Route add/del"""
        return self.api(
            self.papi.mpls_route_add_del,
            {
                "mr_route": {
                    "mr_table_id": table_id,
                    "mr_label": label,
                    "mr_eos": eos,
                    "mr_eos_proto": eos_proto,
                    "mr_is_multicast": is_multicast,
                    "mr_n_paths": len(paths),
                    "mr_paths": paths,
                },
                "mr_is_add": is_add,
                "mr_is_multipath": is_multipath,
            },
        )

    def mpls_ip_bind_unbind(self, label, prefix, table_id=0, ip_table_id=0, is_bind=1):
        """ """
        return self.api(
            self.papi.mpls_ip_bind_unbind,
            {
                "mb_mpls_table_id": table_id,
                "mb_label": label,
                "mb_ip_table_id": ip_table_id,
                "mb_is_bind": is_bind,
                "mb_prefix": prefix,
            },
        )

    def mpls_tunnel_add_del(
        self, tun_sw_if_index, paths, is_add=1, l2_only=0, is_multicast=0
    ):
        """ """
        return self.api(
            self.papi.mpls_tunnel_add_del,
            {
                "mt_is_add": is_add,
                "mt_tunnel": {
                    "mt_sw_if_index": tun_sw_if_index,
                    "mt_l2_only": l2_only,
                    "mt_is_multicast": is_multicast,
                    "mt_n_paths": len(paths),
                    "mt_paths": paths,
                },
            },
        )

    def input_acl_set_interface(
        self,
        is_add,
        sw_if_index,
        ip4_table_index=0xFFFFFFFF,
        ip6_table_index=0xFFFFFFFF,
        l2_table_index=0xFFFFFFFF,
    ):
        """
        :param is_add:
        :param sw_if_index:
        :param ip4_table_index:  (Default value = 0xFFFFFFFF)
        :param ip6_table_index:  (Default value = 0xFFFFFFFF)
        :param l2_table_index:  (Default value = 0xFFFFFFFF)
        """

        return self.api(
            self.papi.input_acl_set_interface,
            {
                "sw_if_index": sw_if_index,
                "ip4_table_index": ip4_table_index,
                "ip6_table_index": ip6_table_index,
                "l2_table_index": l2_table_index,
                "is_add": is_add,
            },
        )

    def output_acl_set_interface(
        self,
        is_add,
        sw_if_index,
        ip4_table_index=0xFFFFFFFF,
        ip6_table_index=0xFFFFFFFF,
        l2_table_index=0xFFFFFFFF,
    ):
        """
        :param is_add:
        :param sw_if_index:
        :param ip4_table_index:  (Default value = 0xFFFFFFFF)
        :param ip6_table_index:  (Default value = 0xFFFFFFFF)
        :param l2_table_index:  (Default value = 0xFFFFFFFF)
        """

        return self.api(
            self.papi.output_acl_set_interface,
            {
                "sw_if_index": sw_if_index,
                "ip4_table_index": ip4_table_index,
                "ip6_table_index": ip6_table_index,
                "l2_table_index": l2_table_index,
                "is_add": is_add,
            },
        )

    def set_ipfix_exporter(
        self,
        collector_address,
        src_address,
        path_mtu,
        template_interval,
        vrf_id=0,
        collector_port=4739,
        udp_checksum=0,
    ):
        return self.api(
            self.papi.set_ipfix_exporter,
            {
                "collector_address": collector_address,
                "collector_port": collector_port,
                "src_address": src_address,
                "vrf_id": vrf_id,
                "path_mtu": path_mtu,
                "template_interval": template_interval,
                "udp_checksum": udp_checksum,
            },
        )

    def mfib_signal_dump(self):
        return self.api(self.papi.mfib_signal_dump, {})

    def ip_mroute_dump(self, table_id, is_ip6=False):
        return self.api(
            self.papi.ip_mroute_dump,
            {"table": {"table_id": table_id, "is_ip6": is_ip6}},
        )

    def pppoe_add_del_session(
        self, client_ip, client_mac, session_id=0, is_add=1, decap_vrf_id=0
    ):
        """

        :param is_add:  (Default value = 1)
        :param is_ipv6:  (Default value = 0)
        :param client_ip:
        :param session_id:  (Default value = 0)
        :param client_mac:
        :param decap_vrf_id:  (Default value = 0)

        """
        return self.api(
            self.papi.pppoe_add_del_session,
            {
                "is_add": is_add,
                "session_id": session_id,
                "client_ip": client_ip,
                "decap_vrf_id": decap_vrf_id,
                "client_mac": client_mac,
            },
        )

    def sr_mpls_policy_add(self, bsid, weight, type, segments):
        return self.api(
            self.papi.sr_mpls_policy_add,
            {
                "bsid": bsid,
                "weight": weight,
                "is_spray": type,
                "n_segments": len(segments),
                "segments": segments,
            },
        )

    def sr_mpls_policy_del(self, bsid):
        return self.api(self.papi.sr_mpls_policy_del, {"bsid": bsid})

    def bier_table_add_del(self, bti, mpls_label, is_add=1):
        """BIER Table add/del"""
        return self.api(
            self.papi.bier_table_add_del,
            {
                "bt_tbl_id": {
                    "bt_set": bti.set_id,
                    "bt_sub_domain": bti.sub_domain_id,
                    "bt_hdr_len_id": bti.hdr_len_id,
                },
                "bt_label": mpls_label,
                "bt_is_add": is_add,
            },
        )

    def bier_table_dump(self):
        return self.api(self.papi.bier_table_dump, {})

    def bier_route_add_del(self, bti, bp, paths, is_add=1, is_replace=0):
        """BIER Route add/del"""
        return self.api(
            self.papi.bier_route_add_del,
            {
                "br_route": {
                    "br_tbl_id": {
                        "bt_set": bti.set_id,
                        "bt_sub_domain": bti.sub_domain_id,
                        "bt_hdr_len_id": bti.hdr_len_id,
                    },
                    "br_bp": bp,
                    "br_n_paths": len(paths),
                    "br_paths": paths,
                },
                "br_is_add": is_add,
                "br_is_replace": is_replace,
            },
        )

    def bier_route_dump(self, bti):
        return self.api(
            self.papi.bier_route_dump,
            {
                "br_tbl_id": {
                    "bt_set": bti.set_id,
                    "bt_sub_domain": bti.sub_domain_id,
                    "bt_hdr_len_id": bti.hdr_len_id,
                }
            },
        )

    def bier_imp_add(self, bti, src, ibytes, is_add=1):
        """BIER Imposition Add"""
        return self.api(
            self.papi.bier_imp_add,
            {
                "bi_tbl_id": {
                    "bt_set": bti.set_id,
                    "bt_sub_domain": bti.sub_domain_id,
                    "bt_hdr_len_id": bti.hdr_len_id,
                },
                "bi_src": src,
                "bi_n_bytes": len(ibytes),
                "bi_bytes": ibytes,
            },
        )

    def bier_imp_del(self, bi_index):
        """BIER Imposition del"""
        return self.api(self.papi.bier_imp_del, {"bi_index": bi_index})

    def bier_imp_dump(self):
        return self.api(self.papi.bier_imp_dump, {})

    def bier_disp_table_add_del(self, bdti, is_add=1):
        """BIER Disposition Table add/del"""
        return self.api(
            self.papi.bier_disp_table_add_del,
            {"bdt_tbl_id": bdti, "bdt_is_add": is_add},
        )

    def bier_disp_table_dump(self):
        return self.api(self.papi.bier_disp_table_dump, {})

    def bier_disp_entry_add_del(
        self,
        bdti,
        bp,
        payload_proto,
        next_hop_afi,
        next_hop,
        next_hop_tbl_id=0,
        next_hop_rpf_id=~0,
        next_hop_is_ip4=1,
        is_add=1,
    ):
        """BIER Route add/del"""
        lstack = []
        while len(lstack) < 16:
            lstack.append({})
        return self.api(
            self.papi.bier_disp_entry_add_del,
            {
                "bde_tbl_id": bdti,
                "bde_bp": bp,
                "bde_payload_proto": payload_proto,
                "bde_n_paths": 1,
                "bde_paths": [
                    {
                        "table_id": next_hop_tbl_id,
                        "rpf_id": next_hop_rpf_id,
                        "n_labels": 0,
                        "label_stack": lstack,
                    }
                ],
                "bde_is_add": is_add,
            },
        )

    def bier_disp_entry_dump(self, bdti):
        return self.api(self.papi.bier_disp_entry_dump, {"bde_tbl_id": bdti})

    def ipsec_spd_add_del(self, spd_id, is_add=1):
        """SPD add/del - Wrapper to add or del ipsec SPD
        Sample CLI : 'ipsec spd add 1'

        :param spd_id - SPD ID to be created in the vpp . mandatory
        :param is_add - create (1) or delete(0) SPD (Default 1 - add) .
        optional
        :returns: reply from the API
        """
        return self.api(
            self.papi.ipsec_spd_add_del, {"spd_id": spd_id, "is_add": is_add}
        )

    def ipsec_spds_dump(self):
        return self.api(self.papi.ipsec_spds_dump, {})

    def ipsec_interface_add_del_spd(self, spd_id, sw_if_index, is_add=1):
        """ IPSEC interface SPD add/del - \
             Wrapper to associate/disassociate SPD to interface in VPP
        Sample CLI : 'set interface ipsec spd GigabitEthernet0/6/0 1'

        :param spd_id - SPD ID to associate with the interface . mandatory
        :param sw_if_index - Interface Index which needs to ipsec \
            association mandatory
        :param is_add - add(1) or del(0) association with interface \
                (Default 1 - add) . optional
        :returns: reply from the API
        """
        return self.api(
            self.papi.ipsec_interface_add_del_spd,
            {"spd_id": spd_id, "sw_if_index": sw_if_index, "is_add": is_add},
        )

    def ipsec_spd_interface_dump(self, spd_index=None):
        return self.api(
            self.papi.ipsec_spd_interface_dump,
            {
                "spd_index": spd_index if spd_index else 0,
                "spd_index_valid": 1 if spd_index else 0,
            },
        )

    def ipsec_spd_entry_add_del(
        self,
        spd_id,
        sa_id,
        local_address_start,
        local_address_stop,
        remote_address_start,
        remote_address_stop,
        local_port_start=0,
        local_port_stop=65535,
        remote_port_start=0,
        remote_port_stop=65535,
        protocol=socket.IPPROTO_RAW,
        policy=0,
        priority=100,
        is_outbound=1,
        is_add=1,
        is_ipv6=0,
        is_ip_any=0,
    ):
        """IPSEC policy SPD add/del   -
                    Wrapper to configure ipsec SPD policy entries in VPP

        :param spd_id: SPD ID for the policy
        :param local_address_start: local-ip-range start address
        :param local_address_stop: local-ip-range stop address
        :param remote_address_start: remote-ip-range start address
        :param remote_address_stop: remote-ip-range stop address
        :param local_port_start: (Default value = 0)
        :param local_port_stop: (Default value = 65535)
        :param remote_port_start: (Default value = 0)
        :param remote_port_stop: (Default value = 65535)
        :param protocol: Any(0), AH(51) & ESP(50) protocol (Default value = 0)
        :param sa_id: Security Association ID for mapping it to SPD
        :param policy: bypass(0), discard(1), resolve(2) or protect(3) action
            (Default value = 0)
        :param priority: value for the spd action (Default value = 100)
        :param is_outbound: flag for inbound(0) or outbound(1)
            (Default value = 1)
        :param is_add: (Default value = 1)
        """
        return self.api(
            self.papi.ipsec_spd_entry_add_del_v2,
            {
                "is_add": is_add,
                "entry": {
                    "spd_id": spd_id,
                    "sa_id": sa_id,
                    "local_address_start": local_address_start,
                    "local_address_stop": local_address_stop,
                    "remote_address_start": remote_address_start,
                    "remote_address_stop": remote_address_stop,
                    "local_port_start": local_port_start,
                    "local_port_stop": local_port_stop,
                    "remote_port_start": remote_port_start,
                    "remote_port_stop": remote_port_stop,
                    "protocol": protocol,
                    "policy": policy,
                    "priority": priority,
                    "is_outbound": is_outbound,
                },
            },
        )

    def ipsec_spd_dump(self, spd_id, sa_id=0xFFFFFFFF):
        return self.api(self.papi.ipsec_spd_dump, {"spd_id": spd_id, "sa_id": sa_id})

    def ipsec_tunnel_if_add_del(
        self,
        local_ip,
        remote_ip,
        local_spi,
        remote_spi,
        crypto_alg,
        local_crypto_key,
        remote_crypto_key,
        integ_alg,
        local_integ_key,
        remote_integ_key,
        is_add=1,
        esn=0,
        salt=0,
        anti_replay=1,
        renumber=0,
        udp_encap=0,
        show_instance=0xFFFFFFFF,
    ):
        return self.api(
            self.papi.ipsec_tunnel_if_add_del,
            {
                "local_ip": local_ip,
                "remote_ip": remote_ip,
                "local_spi": local_spi,
                "remote_spi": remote_spi,
                "crypto_alg": crypto_alg,
                "local_crypto_key_len": len(local_crypto_key),
                "local_crypto_key": local_crypto_key,
                "remote_crypto_key_len": len(remote_crypto_key),
                "remote_crypto_key": remote_crypto_key,
                "integ_alg": integ_alg,
                "local_integ_key_len": len(local_integ_key),
                "local_integ_key": local_integ_key,
                "remote_integ_key_len": len(remote_integ_key),
                "remote_integ_key": remote_integ_key,
                "is_add": is_add,
                "esn": esn,
                "anti_replay": anti_replay,
                "renumber": renumber,
                "show_instance": show_instance,
                "udp_encap": udp_encap,
                "salt": salt,
            },
        )

    def ipsec_select_backend(self, protocol, index):
        return self.api(
            self.papi.ipsec_select_backend, {"protocol": protocol, "index": index}
        )

    def ipsec_backend_dump(self):
        return self.api(self.papi.ipsec_backend_dump, {})

    def punt_socket_register(self, reg, pathname, header_version=1):
        """Register punt socket"""
        return self.api(
            self.papi.punt_socket_register,
            {"header_version": header_version, "punt": reg, "pathname": pathname},
        )

    def punt_socket_deregister(self, reg):
        """Unregister punt socket"""
        return self.api(self.papi.punt_socket_deregister, {"punt": reg})

    def igmp_enable_disable(self, sw_if_index, enable, host):
        """Enable/disable IGMP on a given interface"""
        return self.api(
            self.papi.igmp_enable_disable,
            {"enable": enable, "mode": host, "sw_if_index": sw_if_index},
        )

    def igmp_proxy_device_add_del(self, vrf_id, sw_if_index, add):
        """Add/del IGMP proxy device"""
        return self.api(
            self.papi.igmp_proxy_device_add_del,
            {"vrf_id": vrf_id, "sw_if_index": sw_if_index, "add": add},
        )

    def igmp_proxy_device_add_del_interface(self, vrf_id, sw_if_index, add):
        """Add/del interface to/from IGMP proxy device"""
        return self.api(
            self.papi.igmp_proxy_device_add_del_interface,
            {"vrf_id": vrf_id, "sw_if_index": sw_if_index, "add": add},
        )

    def igmp_listen(self, filter, sw_if_index, saddrs, gaddr):
        """Listen for new (S,G) on specified interface

        :param enable: add/delas
        :param sw_if_index: interface sw index
        :param saddr: source ip4 addr
        :param gaddr: group ip4 addr
        """
        return self.api(
            self.papi.igmp_listen,
            {
                "group": {
                    "filter": filter,
                    "sw_if_index": sw_if_index,
                    "n_srcs": len(saddrs),
                    "saddrs": saddrs,
                    "gaddr": gaddr,
                }
            },
        )

    def igmp_clear_interface(self, sw_if_index):
        """Remove all (S,G)s from specified interface
        doesn't send IGMP report!
        """
        return self.api(self.papi.igmp_clear_interface, {"sw_if_index": sw_if_index})

    def want_igmp_events(self, enable=1):
        return self.api(
            self.papi.want_igmp_events, {"enable": enable, "pid": os.getpid()}
        )