Пример #1
0
    def setUp(self):
        # Add a dummy extensions that modifies the marks
        server_extension_registry['dummy'] = DummyExtension()

        # Some mock objects to use
        self.dummy_handler = DeepCopyMagicMock(spec=Handler)
        unicast_me_filter = MarkedWithFilter(filter_condition='unicast-me',
                                             sub_handlers=[ServerUnicastOptionHandler(
                                                 address=IPv6Address('2001:db8::1')
                                             )])
        ignore_me_filter = MarkedWithFilter(filter_condition='ignore-me', sub_handlers=[IgnoreRequestHandler()])
        reject_me_filter = MarkedWithFilter(filter_condition='reject-me', sub_handlers=[BadExceptionHandler()])

        # Prove to PyCharm that this is really a handler
        self.assertIsInstance(self.dummy_handler, Handler)

        # This is the DUID that is used in the message fixtures
        self.duid = LinkLayerTimeDUID(hardware_type=1, time=488458703, link_layer_address=bytes.fromhex('00137265ca42'))

        # Create some message handlers
        self.message_handler = MessageHandler(server_id=self.duid,
                                              sub_filters=[unicast_me_filter, ignore_me_filter, reject_me_filter],
                                              sub_handlers=[self.dummy_handler],
                                              allow_rapid_commit=False,
                                              rapid_commit_rejections=False)
        self.rapid_message_handler = MessageHandler(server_id=self.duid,
                                                    sub_handlers=[self.dummy_handler],
                                                    allow_rapid_commit=True,
                                                    rapid_commit_rejections=False)
        self.very_rapid_message_handler = MessageHandler(server_id=self.duid,
                                                         sub_handlers=[self.dummy_handler],
                                                         allow_rapid_commit=True,
                                                         rapid_commit_rejections=True)
Пример #2
0
    def setUp(self):
        # Add a dummy extensions that modifies the marks
        server_extension_registry['dummy'] = DummyExtension()

        # Some mock objects to use
        self.dummy_handler = DeepCopyMagicMock(spec=Handler)
        unicast_me_filter = MarkedWithFilter(
            filter_condition='unicast-me',
            sub_handlers=[
                ServerUnicastOptionHandler(address=IPv6Address('2001:db8::1'))
            ])
        ignore_me_filter = MarkedWithFilter(
            filter_condition='ignore-me',
            sub_handlers=[IgnoreRequestHandler()])
        reject_me_filter = MarkedWithFilter(
            filter_condition='reject-me', sub_handlers=[BadExceptionHandler()])

        # Prove to PyCharm that this is really a handler
        self.assertIsInstance(self.dummy_handler, Handler)

        # This is the DUID that is used in the message fixtures
        self.duid = LinkLayerTimeDUID(
            hardware_type=1,
            time=488458703,
            link_layer_address=bytes.fromhex('00137265ca42'))

        # Create some message handlers
        self.message_handler = MessageHandler(
            server_id=self.duid,
            sub_filters=[
                unicast_me_filter, ignore_me_filter, reject_me_filter
            ],
            sub_handlers=[self.dummy_handler],
            allow_rapid_commit=False,
            rapid_commit_rejections=False)
        self.rapid_message_handler = MessageHandler(
            server_id=self.duid,
            sub_handlers=[self.dummy_handler],
            allow_rapid_commit=True,
            rapid_commit_rejections=False)
        self.very_rapid_message_handler = MessageHandler(
            server_id=self.duid,
            sub_handlers=[self.dummy_handler],
            allow_rapid_commit=True,
            rapid_commit_rejections=True)
Пример #3
0
class MessageHandlerTestCase(unittest.TestCase):
    def setUp(self):
        # Add a dummy extensions that modifies the marks
        server_extension_registry['dummy'] = DummyExtension()

        # Some mock objects to use
        self.dummy_handler = DeepCopyMagicMock(spec=Handler)
        unicast_me_filter = MarkedWithFilter(
            filter_condition='unicast-me',
            sub_handlers=[
                ServerUnicastOptionHandler(address=IPv6Address('2001:db8::1'))
            ])
        ignore_me_filter = MarkedWithFilter(
            filter_condition='ignore-me',
            sub_handlers=[IgnoreRequestHandler()])
        reject_me_filter = MarkedWithFilter(
            filter_condition='reject-me', sub_handlers=[BadExceptionHandler()])

        # Prove to PyCharm that this is really a handler
        self.assertIsInstance(self.dummy_handler, Handler)

        # This is the DUID that is used in the message fixtures
        self.duid = LinkLayerTimeDUID(
            hardware_type=1,
            time=488458703,
            link_layer_address=bytes.fromhex('00137265ca42'))

        # Create some message handlers
        self.message_handler = MessageHandler(
            server_id=self.duid,
            sub_filters=[
                unicast_me_filter, ignore_me_filter, reject_me_filter
            ],
            sub_handlers=[self.dummy_handler],
            allow_rapid_commit=False,
            rapid_commit_rejections=False)
        self.rapid_message_handler = MessageHandler(
            server_id=self.duid,
            sub_handlers=[self.dummy_handler],
            allow_rapid_commit=True,
            rapid_commit_rejections=False)
        self.very_rapid_message_handler = MessageHandler(
            server_id=self.duid,
            sub_handlers=[self.dummy_handler],
            allow_rapid_commit=True,
            rapid_commit_rejections=True)

    def test_worker_init(self):
        self.message_handler.worker_init()
        self.dummy_handler.assert_has_calls([call.worker_init()])

    def test_empty_message(self):
        with self.assertLogs(level=logging.WARNING) as cm:
            bundle = TransactionBundle(incoming_message=RelayForwardMessage(),
                                       received_over_multicast=True)
            result = self.message_handler.handle(bundle, StatisticsSet())
            self.assertIsNone(result)

        self.assertEqual(len(cm.output), 1)
        self.assertRegex(cm.output[0],
                         '^WARNING:.*:A server should not receive')

    def test_ignorable_multicast_message(self):
        with self.assertLogs(level=logging.DEBUG) as cm:
            bundle = TransactionBundle(incoming_message=solicit_message,
                                       received_over_multicast=True,
                                       marks=['ignore-me'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message
            self.assertIsNone(result)

        self.assertEqual(len(cm.output), 3)
        self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage')
        self.assertRegex(cm.output[1],
                         '^INFO:.*:Configured to ignore SolicitMessage')
        self.assertRegex(cm.output[2], '^WARNING:.*:.*ignoring')

    def test_reject_unicast_message(self):
        with self.assertLogs(level=logging.DEBUG) as cm:
            bundle = TransactionBundle(incoming_message=solicit_message,
                                       received_over_multicast=False)
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message
            self.assertIsInstance(result, ReplyMessage)
            self.assertEqual(
                result.get_option_of_type(StatusCodeOption).status_code,
                STATUS_USE_MULTICAST)

        self.assertEqual(len(cm.output), 3)
        self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage')
        self.assertRegex(cm.output[1],
                         '^INFO:.*:Rejecting unicast SolicitMessage')
        self.assertRegex(cm.output[2], '^DEBUG:.*:.*multicast is required')

    def test_accept_unicast_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=False,
                                   marks=['unicast-me'])
        self.message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message
        self.assertIsInstance(result, AdvertiseMessage)
        self.assertIsNone(result.get_option_of_type(StatusCodeOption))

    def test_badly_rejected_multicast_message(self):
        with self.assertLogs(level=logging.DEBUG) as cm:
            bundle = TransactionBundle(incoming_message=solicit_message,
                                       received_over_multicast=True,
                                       marks=['reject-me'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message
            self.assertIsNone(result)

        self.assertEqual(len(cm.output), 3)
        self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage')
        self.assertRegex(cm.output[1], '^DEBUG:.*:.*multicast is required')
        self.assertRegex(cm.output[2],
                         '^ERROR:.*:Not telling client to use multicast')

    def test_solicit_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=True,
                                   marks=['one', 'two', 'one'])
        self.message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, AdvertiseMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption),
                         solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(
            result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(
            result.get_option_of_type(IANAOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(
            result.get_option_of_type(IAPDOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_PREFIX_AVAIL)

        # Check if the handlers are called correctly
        for method_name in ['pre', 'handle', 'post']:
            method = getattr(self.dummy_handler, method_name)

            self.assertEqual(method.call_count, 1)
            args, kwargs = method.call_args
            self.assertEqual(len(args), 1)
            self.assertEqual(len(kwargs), 0)
            self.assertIsInstance(args[0], TransactionBundle)

        # Check the types and values at various stages
        # In the pre phase there is no response yet
        bundle = self.dummy_handler.pre.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two', 'pre-setup'})
        self.assertIsNone(bundle.response)
        self.assertIsNone(bundle.outgoing_relay_messages)

        # In the handle phase there is an AdvertiseMessage
        bundle = self.dummy_handler.handle.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(
            bundle.marks,
            {'one', 'two', 'pre-setup', 'pre-cleanup', 'handle-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

        # In the post phase there is still an AdvertiseMessage (no rapid commit)
        bundle = self.dummy_handler.post.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(
            bundle.marks, {
                'one', 'two', 'pre-setup', 'pre-cleanup', 'handle-setup',
                'handle-cleanup', 'post-setup'
            })
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

    def test_rapid_solicit_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=True,
                                   marks=['one', 'two'])
        self.rapid_message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, AdvertiseMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption),
                         solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(
            result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(
            result.get_option_of_type(IANAOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(
            result.get_option_of_type(IAPDOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_PREFIX_AVAIL)

        # Check if the handlers are called correctly
        for method_name in ['pre', 'handle', 'post']:
            method = getattr(self.dummy_handler, method_name)

            self.assertEqual(method.call_count, 1)
            args, kwargs = method.call_args
            self.assertEqual(len(args), 1)
            self.assertEqual(len(kwargs), 0)
            self.assertIsInstance(args[0], TransactionBundle)

        # Check the types and values at various stages
        # In the pre phase there is no response yet
        bundle = self.dummy_handler.pre.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two', 'pre-setup'})
        self.assertIsNone(bundle.response)
        self.assertIsNone(bundle.outgoing_relay_messages)

        # In the handle phase there is an AdvertiseMessage
        bundle = self.dummy_handler.handle.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(
            bundle.marks,
            {'one', 'two', 'pre-setup', 'pre-cleanup', 'handle-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

        # In the post phase there is still an AdvertiseMessage (rapid commit, but no rapid commit rejections)
        bundle = self.dummy_handler.post.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(
            bundle.marks, {
                'one', 'two', 'pre-setup', 'pre-cleanup', 'handle-setup',
                'handle-cleanup', 'post-setup'
            })
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

    def test_very_rapid_solicit_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=True,
                                   marks=['one'])
        self.very_rapid_message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, ReplyMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption),
                         solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(
            result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(
            result.get_option_of_type(IANAOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(
            result.get_option_of_type(IAPDOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_PREFIX_AVAIL)

        # Check if the handlers are called correctly
        for method_name in ['pre', 'handle', 'post']:
            method = getattr(self.dummy_handler, method_name)

            self.assertEqual(method.call_count, 1)
            args, kwargs = method.call_args
            self.assertEqual(len(args), 1)
            self.assertEqual(len(kwargs), 0)
            self.assertIsInstance(args[0], TransactionBundle)

        # Check the types and values at various stages
        # In the pre phase there is no response yet
        bundle = self.dummy_handler.pre.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'pre-setup'})
        self.assertIsNone(bundle.response)
        self.assertIsNone(bundle.outgoing_relay_messages)

        # In the handle phase there is an AdvertiseMessage
        bundle = self.dummy_handler.handle.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks,
                         {'one', 'pre-setup', 'pre-cleanup', 'handle-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

        # In the post phase there is a ReplyMessage(rapid commit rejections)
        bundle = self.dummy_handler.post.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(
            bundle.marks, {
                'one', 'pre-setup', 'pre-cleanup', 'handle-setup',
                'handle-cleanup', 'post-setup'
            })
        self.assertIsInstance(bundle.response, ReplyMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

    def test_request_message(self):
        bundle = TransactionBundle(incoming_message=request_message,
                                   received_over_multicast=True,
                                   marks=['one'])
        self.message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, ReplyMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption),
                         solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(
            result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(
            result.get_option_of_type(IANAOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(
            result.get_option_of_type(IAPDOption).get_option_of_type(
                StatusCodeOption).status_code, STATUS_NO_PREFIX_AVAIL)

    def test_confirm_message(self):
        with self.assertLogs() as cm:
            bundle = TransactionBundle(incoming_message=confirm_message,
                                       received_over_multicast=True,
                                       marks=['one'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message

        self.assertEqual(len(cm.output), 1)
        self.assertRegex(cm.output[0], '^WARNING:.*:No handler confirmed')

        self.assertIsInstance(result, ReplyMessage)
        self.assertEqual(result.transaction_id, request_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption),
                         solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(
            result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(
            result.get_option_of_type(StatusCodeOption).status_code,
            STATUS_NOT_ON_LINK)

    def test_empty_confirm_message(self):
        with self.assertLogs() as cm:
            bundle = TransactionBundle(
                incoming_message=ConfirmMessage(transaction_id=b'abcd'),
                received_over_multicast=True,
                marks=['one'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message

        self.assertEqual(len(cm.output), 1)
        self.assertRegex(cm.output[0],
                         '^WARNING:.*:No IAs present in confirm reply')

        # ConfirmMessage without IANAOption/IATAOption/IAPDOption must be ignored
        self.assertIsNone(result)

    def test_not_implemented_message(self):
        class NotImplementedMessage(ClientServerMessage):
            """
            A non-existent message type to check how we handle unknown messages
            """
            message_type = 255
            from_client_to_server = True

        with self.assertLogs() as cm:
            bundle = TransactionBundle(
                incoming_message=NotImplementedMessage(transaction_id=b'abcd'),
                received_over_multicast=True,
                marks=['one'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message

        self.assertEqual(len(cm.output), 1)
        self.assertRegex(cm.output[0], '^WARNING:.*:Do not know how to reply')

        self.assertIsNone(result)
Пример #4
0
class MessageHandlerTestCase(unittest.TestCase):
    def setUp(self):
        # Add a dummy extensions that modifies the marks
        server_extension_registry['dummy'] = DummyExtension()

        # Some mock objects to use
        self.dummy_handler = DeepCopyMagicMock(spec=Handler)
        unicast_me_filter = MarkedWithFilter(filter_condition='unicast-me',
                                             sub_handlers=[ServerUnicastOptionHandler(
                                                 address=IPv6Address('2001:db8::1')
                                             )])
        ignore_me_filter = MarkedWithFilter(filter_condition='ignore-me', sub_handlers=[IgnoreRequestHandler()])
        reject_me_filter = MarkedWithFilter(filter_condition='reject-me', sub_handlers=[BadExceptionHandler()])

        # Prove to PyCharm that this is really a handler
        self.assertIsInstance(self.dummy_handler, Handler)

        # This is the DUID that is used in the message fixtures
        self.duid = LinkLayerTimeDUID(hardware_type=1, time=488458703, link_layer_address=bytes.fromhex('00137265ca42'))

        # Create some message handlers
        self.message_handler = MessageHandler(server_id=self.duid,
                                              sub_filters=[unicast_me_filter, ignore_me_filter, reject_me_filter],
                                              sub_handlers=[self.dummy_handler],
                                              allow_rapid_commit=False,
                                              rapid_commit_rejections=False)
        self.rapid_message_handler = MessageHandler(server_id=self.duid,
                                                    sub_handlers=[self.dummy_handler],
                                                    allow_rapid_commit=True,
                                                    rapid_commit_rejections=False)
        self.very_rapid_message_handler = MessageHandler(server_id=self.duid,
                                                         sub_handlers=[self.dummy_handler],
                                                         allow_rapid_commit=True,
                                                         rapid_commit_rejections=True)

    def test_worker_init(self):
        self.message_handler.worker_init()
        self.dummy_handler.assert_has_calls([
            call.worker_init()
        ])

    def test_empty_message(self):
        with self.assertLogs(level=logging.WARNING) as cm:
            bundle = TransactionBundle(incoming_message=RelayForwardMessage(),
                                       received_over_multicast=True)
            result = self.message_handler.handle(bundle, StatisticsSet())
            self.assertIsNone(result)

        self.assertEqual(len(cm.output), 1)
        self.assertRegex(cm.output[0], '^WARNING:.*:A server should not receive')

    def test_ignorable_multicast_message(self):
        with self.assertLogs(level=logging.DEBUG) as cm:
            bundle = TransactionBundle(incoming_message=solicit_message,
                                       received_over_multicast=True,
                                       marks=['ignore-me'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message
            self.assertIsNone(result)

        self.assertEqual(len(cm.output), 3)
        self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage')
        self.assertRegex(cm.output[1], '^INFO:.*:Configured to ignore SolicitMessage')
        self.assertRegex(cm.output[2], '^WARNING:.*:.*ignoring')

    def test_reject_unicast_message(self):
        with self.assertLogs(level=logging.DEBUG) as cm:
            bundle = TransactionBundle(incoming_message=solicit_message,
                                       received_over_multicast=False)
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message
            self.assertIsInstance(result, ReplyMessage)
            self.assertEqual(result.get_option_of_type(StatusCodeOption).status_code, STATUS_USE_MULTICAST)

        self.assertEqual(len(cm.output), 3)
        self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage')
        self.assertRegex(cm.output[1], '^INFO:.*:Rejecting unicast SolicitMessage')
        self.assertRegex(cm.output[2], '^DEBUG:.*:.*multicast is required')

    def test_accept_unicast_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=False,
                                   marks=['unicast-me'])
        self.message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message
        self.assertIsInstance(result, AdvertiseMessage)
        self.assertIsNone(result.get_option_of_type(StatusCodeOption))

    def test_badly_rejected_multicast_message(self):
        with self.assertLogs(level=logging.DEBUG) as cm:
            bundle = TransactionBundle(incoming_message=solicit_message,
                                       received_over_multicast=True,
                                       marks=['reject-me'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message
            self.assertIsNone(result)

        self.assertEqual(len(cm.output), 3)
        self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage')
        self.assertRegex(cm.output[1], '^DEBUG:.*:.*multicast is required')
        self.assertRegex(cm.output[2], '^ERROR:.*:Not telling client to use multicast')

    def test_solicit_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=True,
                                   marks=['one', 'two', 'one'])
        self.message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, AdvertiseMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(result.get_option_of_type(IANAOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(result.get_option_of_type(IAPDOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_PREFIX_AVAIL)

        # Check if the handlers are called correctly
        for method_name in ['pre', 'handle', 'post']:
            method = getattr(self.dummy_handler, method_name)

            self.assertEqual(method.call_count, 1)
            args, kwargs = method.call_args
            self.assertEqual(len(args), 1)
            self.assertEqual(len(kwargs), 0)
            self.assertIsInstance(args[0], TransactionBundle)

        # Check the types and values at various stages
        # In the pre phase there is no response yet
        bundle = self.dummy_handler.pre.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two',
                                        'pre-setup'})
        self.assertIsNone(bundle.response)
        self.assertIsNone(bundle.outgoing_relay_messages)

        # In the handle phase there is an AdvertiseMessage
        bundle = self.dummy_handler.handle.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two',
                                        'pre-setup', 'pre-cleanup',
                                        'handle-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

        # In the post phase there is still an AdvertiseMessage (no rapid commit)
        bundle = self.dummy_handler.post.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two',
                                        'pre-setup', 'pre-cleanup',
                                        'handle-setup', 'handle-cleanup',
                                        'post-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

    def test_rapid_solicit_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=True,
                                   marks=['one', 'two'])
        self.rapid_message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, AdvertiseMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(result.get_option_of_type(IANAOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(result.get_option_of_type(IAPDOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_PREFIX_AVAIL)

        # Check if the handlers are called correctly
        for method_name in ['pre', 'handle', 'post']:
            method = getattr(self.dummy_handler, method_name)

            self.assertEqual(method.call_count, 1)
            args, kwargs = method.call_args
            self.assertEqual(len(args), 1)
            self.assertEqual(len(kwargs), 0)
            self.assertIsInstance(args[0], TransactionBundle)

        # Check the types and values at various stages
        # In the pre phase there is no response yet
        bundle = self.dummy_handler.pre.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two',
                                        'pre-setup'})
        self.assertIsNone(bundle.response)
        self.assertIsNone(bundle.outgoing_relay_messages)

        # In the handle phase there is an AdvertiseMessage
        bundle = self.dummy_handler.handle.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two',
                                        'pre-setup', 'pre-cleanup',
                                        'handle-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

        # In the post phase there is still an AdvertiseMessage (rapid commit, but no rapid commit rejections)
        bundle = self.dummy_handler.post.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one', 'two',
                                        'pre-setup', 'pre-cleanup',
                                        'handle-setup', 'handle-cleanup',
                                        'post-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

    def test_very_rapid_solicit_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=True,
                                   marks=['one'])
        self.very_rapid_message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, ReplyMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(result.get_option_of_type(IANAOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(result.get_option_of_type(IAPDOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_PREFIX_AVAIL)

        # Check if the handlers are called correctly
        for method_name in ['pre', 'handle', 'post']:
            method = getattr(self.dummy_handler, method_name)

            self.assertEqual(method.call_count, 1)
            args, kwargs = method.call_args
            self.assertEqual(len(args), 1)
            self.assertEqual(len(kwargs), 0)
            self.assertIsInstance(args[0], TransactionBundle)

        # Check the types and values at various stages
        # In the pre phase there is no response yet
        bundle = self.dummy_handler.pre.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one',
                                        'pre-setup'})
        self.assertIsNone(bundle.response)
        self.assertIsNone(bundle.outgoing_relay_messages)

        # In the handle phase there is an AdvertiseMessage
        bundle = self.dummy_handler.handle.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one',
                                        'pre-setup', 'pre-cleanup',
                                        'handle-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

        # In the post phase there is a ReplyMessage(rapid commit rejections)
        bundle = self.dummy_handler.post.call_args[0][0]
        self.assertEqual(bundle.request, solicit_message)
        self.assertEqual(bundle.incoming_relay_messages, [])
        self.assertEqual(bundle.marks, {'one',
                                        'pre-setup', 'pre-cleanup',
                                        'handle-setup', 'handle-cleanup',
                                        'post-setup'})
        self.assertIsInstance(bundle.response, ReplyMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

    def test_request_message(self):
        bundle = TransactionBundle(incoming_message=request_message,
                                   received_over_multicast=True,
                                   marks=['one'])
        self.message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, ReplyMessage)
        self.assertEqual(result.transaction_id, solicit_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(result.get_option_of_type(IANAOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_ADDRS_AVAIL)
        self.assertEqual(result.get_option_of_type(IAPDOption).get_option_of_type(StatusCodeOption).status_code,
                         STATUS_NO_PREFIX_AVAIL)

    def test_confirm_message(self):
        with self.assertLogs() as cm:
            bundle = TransactionBundle(incoming_message=confirm_message,
                                       received_over_multicast=True,
                                       marks=['one'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message

        self.assertEqual(len(cm.output), 1)
        self.assertRegex(cm.output[0], '^WARNING:.*:No handler confirmed')

        self.assertIsInstance(result, ReplyMessage)
        self.assertEqual(result.transaction_id, request_message.transaction_id)
        self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption))
        self.assertEqual(result.get_option_of_type(ServerIdOption).duid, self.duid)
        self.assertEqual(result.get_option_of_type(StatusCodeOption).status_code, STATUS_NOT_ON_LINK)

    def test_empty_confirm_message(self):
        bundle = TransactionBundle(incoming_message=ConfirmMessage(transaction_id=b'abcd'),
                                   received_over_multicast=True,
                                   marks=['one'])
        self.message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        # ConfirmMessage without IANAOption/IATAOption/IAPDOption must be ignored
        self.assertIsNone(result)

    def test_not_implemented_message(self):
        class NotImplementedMessage(ClientServerMessage):
            """
            A non-existent message type to check how we handle unknown messages
            """
            message_type = 255
            from_client_to_server = True

        with self.assertLogs() as cm:
            bundle = TransactionBundle(incoming_message=NotImplementedMessage(transaction_id=b'abcd'),
                                       received_over_multicast=True,
                                       marks=['one'])
            self.message_handler.handle(bundle, StatisticsSet())
            result = bundle.outgoing_message

        self.assertEqual(len(cm.output), 1)
        self.assertRegex(cm.output[0], '^WARNING:.*:Do not know how to reply')

        self.assertIsNone(result)