#!/usr/bin/env python3
#
#  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.
#

import unittest

import thread_cert
import config

LEADER_1_1 = 1
BBR_1 = 2
BBR_2 = 3

WAIT_ATTACH = 5
WAIT_REDUNDANCE = 3
ROUTER_SELECTION_JITTER = 1
BBR_REGISTRATION_JITTER = 5
"""
 Topology

 LEADER_1_1 --- BBR_1
          \        |
            \      |
              \    |
                BBR_2

 1) Bring up Leader_1_1 and then BBR_1, BBR_1 becomes Primary Backbone Router.
 2) Reset BBR_1, if bring back soon, it could restore the Backbone Router Service
    from the network, after increasing sequence number, it will reregister its
    Backbone Router Service to the Leader and become Primary.
 3) Reset BBR_1, if bring back after it is released in the network, BBR_1 will
    choose a random sequence number, register its Backbone Router Service to
    Leader and become Primary.
 4) Configure BBR_2 with highest sequence number and explicitly trigger SRV_DATA.ntf.
    BBR_2 would become Primary and BBR_1 would change to Secondary with sequence
    number increased by 1.
    a) Check communication via DUA.
 5) Stop BBR_2, BBR_1 would become Primary after detecting there is no available
    Backbone Router Service in Thread Network.
 6) Bring back BBR_2, and it would become Secondary.
    a) Check the uniqueness of DUA by comparing the one in above 4a).
    b) Check communication via DUA.

"""


class TestBackboneRouterService(thread_cert.TestCase):
    TOPOLOGY = {
        LEADER_1_1: {
            'version': '1.1',
            'allowlist': [BBR_1, BBR_2],
        },
        BBR_1: {
            'version': '1.2',
            'allowlist': [LEADER_1_1, BBR_2],
            'is_bbr': True
        },
        BBR_2: {
            'version': '1.2',
            'allowlist': [LEADER_1_1, BBR_1],
            'is_bbr': True
        },
    }
    """All nodes are created with default configurations"""

    def test(self):

        self.nodes[LEADER_1_1].start()
        WAIT_TIME = WAIT_ATTACH
        self.simulator.go(WAIT_TIME * 2)
        self.assertEqual(self.nodes[LEADER_1_1].get_state(), 'leader')
        self.simulator.set_lowpan_context(1, config.DOMAIN_PREFIX)

        # 1) First Backbone Router would become the Primary.
        self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
        self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
        self.nodes[BBR_1].set_backbone_router(seqno=1)
        self.nodes[BBR_1].start()
        WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_1].get_state(), 'router')
        self.nodes[BBR_1].enable_backbone_router()
        WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), 'Primary')

        self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX)
        WAIT_TIME = WAIT_REDUNDANCE
        self.simulator.go(WAIT_TIME)

        # 2) Reset BBR_1 and bring it back soon.
        # Verify that it restores Primary State with sequence number
        # increased by 1.
        self.nodes[BBR_1].reset()
        self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
        self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
        self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX)
        self.nodes[BBR_1].enable_backbone_router()
        self.nodes[BBR_1].start()
        WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_1].get_state(), 'router')
        WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), 'Primary')
        assert self.nodes[BBR_1].get_backbone_router()['seqno'] == 2

        # 3) Reset BBR_1 and bring it back after its original router id is released
        # 200s (100s MaxNeighborAge + 90s InfiniteCost + 10s redundance)
        # Verify it becomes Primary again.
        # Note: To ensure test in next step, here Step 3) will repeat until
        # the random sequence number is not the highest value 255.
        while True:
            self.nodes[BBR_1].reset()
            WAIT_TIME = 200
            self.simulator.go(WAIT_TIME)
            self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
            self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
            self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX)
            self.nodes[BBR_1].enable_backbone_router()
            self.nodes[BBR_1].start()
            WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
            self.simulator.go(WAIT_TIME)
            self.assertEqual(self.nodes[BBR_1].get_state(), 'router')
            WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
            self.simulator.go(WAIT_TIME)
            self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), 'Primary')
            BBR_1_SEQNO = self.nodes[BBR_1].get_backbone_router()['seqno']
            if (BBR_1_SEQNO != 255):
                break

        #4) Configure BBR_2 with highest sequence number (255) and
        #   explicitly trigger SRV_DATA.ntf.
        #   Verify BBR_2 would become Primary and BBR_1 would change to
        #   Secondary with sequence number increased by 1.

        # Bring up BBR_2, it becomes Router with backbone function disabled
        # by default.
        self.nodes[BBR_2].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
        self.nodes[BBR_2].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
        self.nodes[BBR_2].set_domain_prefix(config.DOMAIN_PREFIX)
        self.nodes[BBR_2].start()
        WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_2].get_state(), 'router')
        WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), 'Disabled')

        # Enable Backbone function, it will stay at Secondary state as
        # there is Primary Backbone Router already.
        # Here removes the Domain Prefix before enabling backbone function
        # intentionally to avoid SRV_DATA.ntf due to prefix inconsistency.
        self.nodes[BBR_2].remove_domain_prefix(config.DOMAIN_PREFIX)
        self.nodes[BBR_2].enable_backbone_router()
        self.nodes[BBR_2].set_backbone_router(seqno=255)
        WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), 'Secondary')

        # Check no SRV_DATA.ntf.
        messages = self.simulator.get_messages_sent_by(BBR_2)
        msg = messages.next_coap_message('0.02', '/a/sd', False)
        assert (msg is None), "Error: %d sent unexpected SRV_DATA.ntf when there is PBbr already"

        # Flush relative message queue.
        self.flush_nodes([BBR_1])

        # BBR_2 registers SRV_DATA.ntf explicitly.
        self.nodes[BBR_2].register_backbone_router()
        WAIT_TIME = WAIT_REDUNDANCE
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), 'Primary')

        # Verify BBR_1 becomes Secondary and sends SRV_DATA.ntf to deregister
        # its service.
        messages = self.simulator.get_messages_sent_by(BBR_1)
        messages.next_coap_message('0.02', '/a/sd', True)
        self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), 'Secondary')
        # Verify Sequence number increases when become Secondary from Primary.
        assert self.nodes[BBR_1].get_backbone_router()['seqno'] == (BBR_1_SEQNO + 1)

        # 4a) Check communication via DUA.
        bbr2_dua = self.nodes[BBR_2].get_addr(config.DOMAIN_PREFIX)
        self.assertTrue(self.nodes[BBR_1].ping(bbr2_dua))

        # 5) Stop BBR_2, BBR_1 becomes Primary after detecting there is no
        #    available Backbone Router Service.
        self.nodes[BBR_2].reset()
        self.nodes[LEADER_1_1].release_router_id(self.nodes[BBR_2].get_router_id())
        # Wait for the dissemination of Network Data without Backbone Router service
        self.simulator.go(10)

        # BBR_1 becomes Primary.
        self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), 'Primary')
        messages = self.simulator.get_messages_sent_by(BBR_1)
        messages.next_coap_message('0.02', '/a/sd', True)

        # 6) Bring back BBR_2.
        #    Verify that BBR_2 stays at Secondary.
        self.nodes[BBR_2].set_router_selection_jitter(ROUTER_SELECTION_JITTER)
        self.nodes[BBR_2].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER)
        self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX)
        self.nodes[BBR_2].enable_backbone_router()
        self.nodes[BBR_2].interface_up()
        self.nodes[BBR_2].thread_start()
        WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_2].get_state(), 'router')
        WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE
        self.simulator.go(WAIT_TIME)
        self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), 'Secondary')

        # 6a) Check the uniqueness of DUA by comparing the one in above 4a).
        bbr2_dua2 = self.nodes[BBR_2].get_addr(config.DOMAIN_PREFIX)
        assert bbr2_dua == bbr2_dua2, 'Error: Unexpected different DUA ({} v.s. {})'.format(bbr2_dua, bbr2_dua2)

        # 6b) Check communication via DUA
        self.assertTrue(self.nodes[BBR_1].ping(bbr2_dua))


if __name__ == '__main__':
    unittest.main()
