#!/usr/bin/env python3
"""IGMP basic

Verify basic IGMP snooping behavior on a bridge.  Without any IGMP
membership, multicast should be flooded to all ports.  Once a host
joins a group, the bridge should learn the membership via IGMP and
only forward matching multicast to the member port, pruning it from
non-member ports.

....
              .1
 .---------------------------.
 |            DUT            |
 '-data1-----data2-----data3-'
     |         |         |
     |         |         |      10.0.0.0/24
     |         |         |
 .-data1-. .-data2-. .-data3-.
 | msend | | mrecv | | !memb |
 '-------' '-------' '-------'
    .2         .3        .4
             HOST
....

A multicast sender on `msend` sends to group 224.1.1.1.  First, with
no IGMP joins, verify the group is flooded to both `mrecv` and `!memb`.
Then `mrecv` joins the group and the test waits for the bridge MDB to
reflect the membership before verifying that `!memb` no longer receives
the group.

"""

import infamy
import infamy.iface as iface
import infamy.multicast as mcast
from infamy.util import until

query_interval = 4

with infamy.Test() as test:
    with test.step("Set up topology and attach to target DUTs"):
        env = infamy.Env()
        target = env.attach("target", "mgmt")
        _, msend = env.ltop.xlate("target", "data1")
        _, mreceive = env.ltop.xlate("target", "data2")
        _, nojoin = env.ltop.xlate("target", "data3")

    with test.step("Configure device"):
        target.put_config_dict("ietf-interfaces",
                        {
                            "interfaces": {
                                "interface": [
                                    {
                                        "name": msend,
                                        "enabled": True,
                                        "infix-interfaces:bridge-port": {
                                            "bridge": "br0"
                                        }
                                    },
                                    {
                                        "name": mreceive,
                                        "enabled": True,
                                        "infix-interfaces:bridge-port": {
                                            "bridge": "br0"
                                        }
                                    },
                                    {
                                        "name": nojoin,
                                        "enabled": True,
                                        "infix-interfaces:bridge-port": {
                                            "bridge": "br0"
                                        }
                                    },
                                    {
                                        "name": "br0",
                                        "enabled": True,
                                        "type": "infix-if-type:bridge",
                                        "ipv4": {
                                            "address": [
                                                {
                                                    "ip": "10.0.0.1",
                                                    "prefix-length": 24
                                                }
                                            ]
                                        },

                                        "bridge": {
                                            "multicast": {
                                                "query-interval": query_interval,
                                                "snooping": True
                                            }

                                        }
                                    }
                                ]

                            }
                        })

    _, hsend = env.ltop.xlate("host", "data1")
    _, hreceive = env.ltop.xlate("host", "data2")
    _, hnojoin = env.ltop.xlate("host", "data3")
    with infamy.IsolatedMacVlan(hsend) as send_ns, \
         infamy.IsolatedMacVlan(hreceive) as receive_ns, \
         infamy.IsolatedMacVlan(hnojoin) as nojoin_ns:

        with test.step("Start multicast sender on host:data0, group 224.1.1.1"):
            send_ns.addip("10.0.0.2")
            receive_ns.addip("10.0.0.3")
            nojoin_ns.addip("10.0.0.4")
            send_ns.must_reach("10.0.0.1")
            receive_ns.must_reach("10.0.0.1")
            nojoin_ns.must_reach("10.0.0.1")

            with mcast.MCastSender(send_ns, "224.1.1.1"):
                with test.step("Verify that the group 224.1.1.1 is flooded on host:data2 and host:data3"):
                    infamy.parallel(lambda: receive_ns.must_receive("ip dst 224.1.1.1"),
                                    lambda: nojoin_ns.must_receive("ip dst 224.1.1.1"))

                with test.step("Join multicast group 224.1.1.1 on host:data2"):
                    with mcast.MCastReceiver(receive_ns, "224.1.1.1"):
                        with test.step("Verify group 224.1.1.1 is received on host:data2"):
                            receive_ns.must_receive("ip dst 224.1.1.1")

                        with test.step("Verify that the group 224.1.1.1 is no longer received on host:data3"):
                            until(lambda: iface.exist_bridge_multicast_filter(target, "224.1.1.1", mreceive, "br0"), attempts=10)
                            nojoin_ns.must_not_receive("ip dst 224.1.1.1")

    test.succeed()
