/*
 *  Copyright (c) 2019, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. 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.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdarg.h>

#include "test_platform.h"

#include <openthread/config.h>

#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/instance.hpp"
#include "net/netif.hpp"

#include "test_util.h"

namespace ot {

class TestNetif : public Ip6::Netif
{
public:
    explicit TestNetif(Instance &aInstance)
        : Ip6::Netif(aInstance)
    {
    }

    // Provide `protected` methods in `Netif` as `public` from `TestNetif`
    // so that we can verify their behavior in this test
    void SubscribeAllNodesMulticast(void) { Ip6::Netif::SubscribeAllNodesMulticast(); }
    void UnsubscribeAllNodesMulticast(void) { Ip6::Netif::UnsubscribeAllNodesMulticast(); }
};

// This function verifies the multicast addresses on Netif matches the list of given addresses.
void VerifyMulticastAddressList(const Ip6::Netif &aNetif, Ip6::Address aAddresses[], uint8_t aLength)
{
    uint8_t count = 0;

    for (uint8_t i = 0; i < aLength; i++)
    {
        VerifyOrQuit(aNetif.IsMulticastSubscribed(aAddresses[i]));
    }

    for (const Ip6::Netif::MulticastAddress &addr : aNetif.GetMulticastAddresses())
    {
        bool didFind = false;

        for (uint8_t i = 0; i < aLength; i++)
        {
            if (addr.GetAddress() == aAddresses[i])
            {
                didFind = true;
                break;
            }
        }

        VerifyOrQuit(didFind, "Netif multicast address is missing from expected address list");
        count++;
    }

    VerifyOrQuit(count == aLength, "Expected address is missing from Netif address list");
}

void TestNetifMulticastAddresses(void)
{
    const uint8_t kMaxAddresses = 8;

    Instance *   instance = testInitInstance();
    TestNetif    netif(*instance);
    Ip6::Address addresses[kMaxAddresses];

    Ip6::Address                 address;
    Ip6::Netif::MulticastAddress netifAddress;

    const char *kLinkLocalAllNodes    = "ff02::01";
    const char *kRealmLocalAllNodes   = "ff03::01";
    const char *kRealmLocalAllMpl     = "ff03::fc";
    const char *kLinkLocalAllRouters  = "ff02::02";
    const char *kRealmLocalAllRouters = "ff03::02";
    const char *kTestAddress1         = "ff02::114";
    const char *kTestAddress2         = "ff03::114";
    const char *kTestAddress3         = "ff04::114";

    IgnoreError(addresses[0].FromString(kLinkLocalAllRouters));
    IgnoreError(addresses[1].FromString(kRealmLocalAllRouters));
    IgnoreError(addresses[2].FromString(kLinkLocalAllNodes));
    IgnoreError(addresses[3].FromString(kRealmLocalAllNodes));
    IgnoreError(addresses[4].FromString(kRealmLocalAllMpl));
    IgnoreError(addresses[5].FromString(kTestAddress1));
    IgnoreError(addresses[6].FromString(kTestAddress2));
    IgnoreError(addresses[7].FromString(kTestAddress3));

    VerifyMulticastAddressList(netif, addresses, 0);

    netif.SubscribeAllNodesMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 3);

    netif.SubscribeAllNodesMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 3);

    netif.SubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[0], 5);

    netif.SubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[0], 5);

    netif.UnsubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 3);

    netif.UnsubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 3);

    IgnoreError(netifAddress.GetAddress().FromString(kTestAddress1));
    netif.SubscribeMulticast(netifAddress);
    VerifyMulticastAddressList(netif, &addresses[2], 4);

    netif.SubscribeMulticast(netifAddress);
    VerifyMulticastAddressList(netif, &addresses[2], 4);

    netif.UnsubscribeAllNodesMulticast();
    VerifyMulticastAddressList(netif, &addresses[5], 1);

    netif.UnsubscribeAllNodesMulticast();
    VerifyMulticastAddressList(netif, &addresses[5], 1);

    IgnoreError(address.FromString(kTestAddress2));
    SuccessOrQuit(netif.SubscribeExternalMulticast(address));
    VerifyMulticastAddressList(netif, &addresses[5], 2);

    netif.SubscribeAllNodesMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 5);

    netif.SubscribeAllNodesMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 5);

    netif.SubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[0], 7);

    netif.SubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[0], 7);

    IgnoreError(address.FromString(kTestAddress3));
    SuccessOrQuit(netif.SubscribeExternalMulticast(address));
    VerifyMulticastAddressList(netif, &addresses[0], 8);

    IgnoreError(address.FromString(kTestAddress1)); // same as netifAddress (internal)
    VerifyOrQuit(netif.UnsubscribeExternalMulticast(address) == kErrorRejected,
                 "UnsubscribeExternalMulticast() did not fail when address was not external");

    IgnoreError(address.FromString(kRealmLocalAllMpl));
    VerifyOrQuit(netif.UnsubscribeExternalMulticast(address) == kErrorRejected,
                 "UnsubscribeExternalMulticast() did not fail when address was fixed address");

    netif.UnsubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 6);

    netif.UnsubscribeAllRoutersMulticast();
    VerifyMulticastAddressList(netif, &addresses[2], 6);

    netif.UnsubscribeAllExternalMulticastAddresses();
    VerifyMulticastAddressList(netif, &addresses[2], 4);

    netif.UnsubscribeMulticast(netifAddress);
    VerifyMulticastAddressList(netif, &addresses[2], 3);

    netif.UnsubscribeMulticast(netifAddress);
    VerifyMulticastAddressList(netif, &addresses[2], 3);

    netif.UnsubscribeAllNodesMulticast();
    VerifyMulticastAddressList(netif, nullptr, 0);

    // The first five elements in `addresses[]` are the default/fixed addresses:
    // kLinkLocalAllRouters, kRealmLocalAllRouters, kLinkLocalAllNodes,
    // kRealmLocalAllNodes, and kRealmLocalAllMpl. Verify that we cannot add
    // any of them as an external multicast address.

    for (uint8_t i = 0; i < 5; i++)
    {
        VerifyOrQuit(netif.SubscribeExternalMulticast(addresses[i]) == kErrorRejected,
                     "SubscribeExternalMulticast() did not fail when address was a default/fixed address");
    }
}

} // namespace ot

int main(void)
{
    ot::TestNetifMulticastAddresses();
    printf("All tests passed\n");
    return 0;
}
